imergpy 1.1.0__tar.gz
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.
- imergpy-1.1.0/LICENSE +21 -0
- imergpy-1.1.0/MANIFEST.in +12 -0
- imergpy-1.1.0/PKG-INFO +169 -0
- imergpy-1.1.0/README.md +132 -0
- imergpy-1.1.0/examples/basic_point_download.py +18 -0
- imergpy-1.1.0/imergpy/__init__.py +12 -0
- imergpy-1.1.0/imergpy/analyzer.py +87 -0
- imergpy-1.1.0/imergpy/cli.py +13 -0
- imergpy-1.1.0/imergpy/config.py +27 -0
- imergpy-1.1.0/imergpy/core.py +174 -0
- imergpy-1.1.0/imergpy/downloader.py +128 -0
- imergpy-1.1.0/imergpy/plotter.py +49 -0
- imergpy-1.1.0/imergpy/processor.py +157 -0
- imergpy-1.1.0/imergpy/server.py +126 -0
- imergpy-1.1.0/imergpy/static/logo.png +0 -0
- imergpy-1.1.0/imergpy/templates/index.html +1277 -0
- imergpy-1.1.0/imergpy.egg-info/PKG-INFO +169 -0
- imergpy-1.1.0/imergpy.egg-info/SOURCES.txt +26 -0
- imergpy-1.1.0/imergpy.egg-info/dependency_links.txt +1 -0
- imergpy-1.1.0/imergpy.egg-info/entry_points.txt +2 -0
- imergpy-1.1.0/imergpy.egg-info/requires.txt +14 -0
- imergpy-1.1.0/imergpy.egg-info/top_level.txt +1 -0
- imergpy-1.1.0/pyproject.toml +63 -0
- imergpy-1.1.0/setup.cfg +4 -0
- imergpy-1.1.0/tests/conftest.py +7 -0
- imergpy-1.1.0/tests/test_analyzer.py +45 -0
- imergpy-1.1.0/tests/test_core_validation.py +52 -0
- imergpy-1.1.0/tests/test_processor.py +115 -0
imergpy-1.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lakshitha S. Senavirathna
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
include imergpy/templates/*.html
|
|
2
|
+
include imergpy/static/*
|
|
3
|
+
include LICENSE
|
|
4
|
+
include README.md
|
|
5
|
+
recursive-include examples *.py
|
|
6
|
+
recursive-include tests *.py
|
|
7
|
+
global-exclude __pycache__/*
|
|
8
|
+
global-exclude *.py[cod]
|
|
9
|
+
global-exclude *.xlsx
|
|
10
|
+
global-exclude *.nc
|
|
11
|
+
global-exclude *.nc4
|
|
12
|
+
global-exclude .env
|
imergpy-1.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: imergpy
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: Download, extract, average, plot, and analyze NASA GPM IMERG precipitation data from Python or a local web UI.
|
|
5
|
+
Author-email: "Lakshitha S. Senavirathna" <lakshithasrimal256@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/LakshithaSenavirathna/imergpy
|
|
8
|
+
Keywords: IMERG,GPM,NASA,precipitation,rainfall,Earthdata,GES DISC
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Intended Audience :: Science/Research
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
20
|
+
Requires-Python: >=3.8
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: requests
|
|
24
|
+
Requires-Dist: xarray
|
|
25
|
+
Requires-Dist: netCDF4
|
|
26
|
+
Requires-Dist: pandas
|
|
27
|
+
Requires-Dist: openpyxl
|
|
28
|
+
Requires-Dist: matplotlib
|
|
29
|
+
Requires-Dist: flask
|
|
30
|
+
Requires-Dist: python-dateutil
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: build; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest; extra == "dev"
|
|
34
|
+
Requires-Dist: ruff; extra == "dev"
|
|
35
|
+
Requires-Dist: twine; extra == "dev"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# imergpy
|
|
39
|
+
|
|
40
|
+
`imergpy` is a Python package and local web interface for downloading NASA GPM IMERG precipitation data through NASA Earthdata/GES DISC. It can extract point rainfall time series and compute grid-cell average rainfall for selected countries or square areas.
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
- Local web UI launched with the `imergpy` command
|
|
45
|
+
- Python API for scripted workflows
|
|
46
|
+
- Point, country, and square-area selection in the web map
|
|
47
|
+
- Grid-cell average precipitation for country and square-area selections
|
|
48
|
+
- Half-hourly, daily, and monthly IMERG products
|
|
49
|
+
- Early, Late, and Final IMERG run types where available
|
|
50
|
+
- Excel export with separate `Start Time` and `End Time` columns
|
|
51
|
+
- Basic rainfall plotting and statistics utilities
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
Users only need two commands:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install imergpy
|
|
59
|
+
imergpy
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
For local development:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install -e ".[dev]"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The browser should open automatically. If it does not, copy the local URL printed in the terminal.
|
|
69
|
+
|
|
70
|
+
## Start The Web UI Manually
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
imergpy
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If the command is not available on Windows:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
python -m imergpy.cli
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
If port `5000` is busy:
|
|
83
|
+
|
|
84
|
+
```powershell
|
|
85
|
+
$env:IMERGPY_PORT = "5001"
|
|
86
|
+
python -m imergpy.cli
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Python API Example
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
import os
|
|
93
|
+
import imergpy
|
|
94
|
+
|
|
95
|
+
excel_path, records = imergpy.get_precipitation(
|
|
96
|
+
lat=6.9271,
|
|
97
|
+
lon=79.8612,
|
|
98
|
+
start_datetime="2025-01",
|
|
99
|
+
end_datetime="2025-01",
|
|
100
|
+
username=os.environ["EARTHDATA_USERNAME"],
|
|
101
|
+
password=os.environ["EARTHDATA_PASSWORD"],
|
|
102
|
+
run_type="final",
|
|
103
|
+
freq="monthly",
|
|
104
|
+
interp_method="nearest",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
print(excel_path)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Accepted date formats:
|
|
111
|
+
|
|
112
|
+
- `YYYY-MM`
|
|
113
|
+
- `YYYY-MM-DD`
|
|
114
|
+
- `YYYY-MM-DD HH:MM`
|
|
115
|
+
|
|
116
|
+
## NASA Earthdata Credentials
|
|
117
|
+
|
|
118
|
+
You need a free NASA Earthdata account. After creating the account, authorize GES DISC under Earthdata authorized applications.
|
|
119
|
+
|
|
120
|
+
Do not write credentials into scripts. Use environment variables:
|
|
121
|
+
|
|
122
|
+
```powershell
|
|
123
|
+
$env:EARTHDATA_USERNAME = "your_username"
|
|
124
|
+
$env:EARTHDATA_PASSWORD = "your_password"
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Legal And Data Use Notice
|
|
128
|
+
|
|
129
|
+
`imergpy` is an independent open-source tool. It is not developed, endorsed, or certified by NASA, GES DISC, or the GPM mission team.
|
|
130
|
+
|
|
131
|
+
Users are responsible for:
|
|
132
|
+
|
|
133
|
+
- creating and using their own NASA Earthdata account,
|
|
134
|
+
- accepting and following NASA/GES DISC data access terms,
|
|
135
|
+
- citing NASA GPM IMERG data correctly in reports, papers, and products,
|
|
136
|
+
- checking data quality, latency, and suitability before operational or scientific use,
|
|
137
|
+
- keeping Earthdata usernames, passwords, and tokens private.
|
|
138
|
+
|
|
139
|
+
This software is provided under the MIT License without warranty.
|
|
140
|
+
|
|
141
|
+
## Development
|
|
142
|
+
|
|
143
|
+
Run tests:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
pytest
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Build package files:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
python -m build
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Upload to TestPyPI first:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
twine upload --repository testpypi dist/*
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Upload to PyPI:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
twine upload dist/*
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
MIT License. Developed by Lakshitha S. Senavirathna.
|
imergpy-1.1.0/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# imergpy
|
|
2
|
+
|
|
3
|
+
`imergpy` is a Python package and local web interface for downloading NASA GPM IMERG precipitation data through NASA Earthdata/GES DISC. It can extract point rainfall time series and compute grid-cell average rainfall for selected countries or square areas.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Local web UI launched with the `imergpy` command
|
|
8
|
+
- Python API for scripted workflows
|
|
9
|
+
- Point, country, and square-area selection in the web map
|
|
10
|
+
- Grid-cell average precipitation for country and square-area selections
|
|
11
|
+
- Half-hourly, daily, and monthly IMERG products
|
|
12
|
+
- Early, Late, and Final IMERG run types where available
|
|
13
|
+
- Excel export with separate `Start Time` and `End Time` columns
|
|
14
|
+
- Basic rainfall plotting and statistics utilities
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
Users only need two commands:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install imergpy
|
|
22
|
+
imergpy
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
For local development:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install -e ".[dev]"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The browser should open automatically. If it does not, copy the local URL printed in the terminal.
|
|
32
|
+
|
|
33
|
+
## Start The Web UI Manually
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
imergpy
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
If the command is not available on Windows:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
python -m imergpy.cli
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
If port `5000` is busy:
|
|
46
|
+
|
|
47
|
+
```powershell
|
|
48
|
+
$env:IMERGPY_PORT = "5001"
|
|
49
|
+
python -m imergpy.cli
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Python API Example
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
import os
|
|
56
|
+
import imergpy
|
|
57
|
+
|
|
58
|
+
excel_path, records = imergpy.get_precipitation(
|
|
59
|
+
lat=6.9271,
|
|
60
|
+
lon=79.8612,
|
|
61
|
+
start_datetime="2025-01",
|
|
62
|
+
end_datetime="2025-01",
|
|
63
|
+
username=os.environ["EARTHDATA_USERNAME"],
|
|
64
|
+
password=os.environ["EARTHDATA_PASSWORD"],
|
|
65
|
+
run_type="final",
|
|
66
|
+
freq="monthly",
|
|
67
|
+
interp_method="nearest",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
print(excel_path)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Accepted date formats:
|
|
74
|
+
|
|
75
|
+
- `YYYY-MM`
|
|
76
|
+
- `YYYY-MM-DD`
|
|
77
|
+
- `YYYY-MM-DD HH:MM`
|
|
78
|
+
|
|
79
|
+
## NASA Earthdata Credentials
|
|
80
|
+
|
|
81
|
+
You need a free NASA Earthdata account. After creating the account, authorize GES DISC under Earthdata authorized applications.
|
|
82
|
+
|
|
83
|
+
Do not write credentials into scripts. Use environment variables:
|
|
84
|
+
|
|
85
|
+
```powershell
|
|
86
|
+
$env:EARTHDATA_USERNAME = "your_username"
|
|
87
|
+
$env:EARTHDATA_PASSWORD = "your_password"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Legal And Data Use Notice
|
|
91
|
+
|
|
92
|
+
`imergpy` is an independent open-source tool. It is not developed, endorsed, or certified by NASA, GES DISC, or the GPM mission team.
|
|
93
|
+
|
|
94
|
+
Users are responsible for:
|
|
95
|
+
|
|
96
|
+
- creating and using their own NASA Earthdata account,
|
|
97
|
+
- accepting and following NASA/GES DISC data access terms,
|
|
98
|
+
- citing NASA GPM IMERG data correctly in reports, papers, and products,
|
|
99
|
+
- checking data quality, latency, and suitability before operational or scientific use,
|
|
100
|
+
- keeping Earthdata usernames, passwords, and tokens private.
|
|
101
|
+
|
|
102
|
+
This software is provided under the MIT License without warranty.
|
|
103
|
+
|
|
104
|
+
## Development
|
|
105
|
+
|
|
106
|
+
Run tests:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
pytest
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Build package files:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
python -m build
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Upload to TestPyPI first:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
twine upload --repository testpypi dist/*
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Upload to PyPI:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
twine upload dist/*
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
MIT License. Developed by Lakshitha S. Senavirathna.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import imergpy
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
excel_path, records = imergpy.get_precipitation(
|
|
7
|
+
lat=6.9271,
|
|
8
|
+
lon=79.8612,
|
|
9
|
+
start_datetime="2024-05-20 14:30",
|
|
10
|
+
end_datetime="2024-05-20 15:00",
|
|
11
|
+
username=os.environ["EARTHDATA_USERNAME"],
|
|
12
|
+
password=os.environ["EARTHDATA_PASSWORD"],
|
|
13
|
+
run_type="final",
|
|
14
|
+
freq="hhr",
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
print(excel_path)
|
|
18
|
+
print(records[:1])
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# imergpy package
|
|
2
|
+
from .core import get_precipitation
|
|
3
|
+
from .plotter import plot_from_excel
|
|
4
|
+
from .analyzer import add_accumulation, resample_data, calculate_statistics
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"get_precipitation",
|
|
8
|
+
"plot_from_excel",
|
|
9
|
+
"add_accumulation",
|
|
10
|
+
"resample_data",
|
|
11
|
+
"calculate_statistics"
|
|
12
|
+
]
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
PRECIP_COLUMNS = [
|
|
5
|
+
"Precipitation_mm_per_half_hour",
|
|
6
|
+
"Precipitation_mm_per_day",
|
|
7
|
+
"Precipitation_mm_per_month",
|
|
8
|
+
"Precipitation_mm",
|
|
9
|
+
"Precipitation_mm_hr",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _find_column(df, candidates, label):
|
|
14
|
+
for column in candidates:
|
|
15
|
+
if column in df.columns:
|
|
16
|
+
return column
|
|
17
|
+
raise ValueError(f"Could not find {label}. Expected one of: {', '.join(candidates)}")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _time_column(df):
|
|
21
|
+
return _find_column(df, ["Start_Time", "Time"], "time column")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _precip_column(df):
|
|
25
|
+
return _find_column(df, PRECIP_COLUMNS, "precipitation column")
|
|
26
|
+
|
|
27
|
+
def add_accumulation(df):
|
|
28
|
+
"""
|
|
29
|
+
Takes a DataFrame with half-hourly IMERG data and adds:
|
|
30
|
+
1. 'Absolute_Precip_mm': Total mm fallen in that 30 min interval (Rate * 0.5)
|
|
31
|
+
2. 'Cumulative_Precip_mm': Running total of rainfall over the period.
|
|
32
|
+
"""
|
|
33
|
+
df = df.copy()
|
|
34
|
+
precip_col = _precip_column(df)
|
|
35
|
+
|
|
36
|
+
if precip_col == "Precipitation_mm_hr":
|
|
37
|
+
df['Absolute_Precip_mm'] = df[precip_col] * 0.5
|
|
38
|
+
else:
|
|
39
|
+
df['Absolute_Precip_mm'] = df[precip_col]
|
|
40
|
+
df['Cumulative_Precip_mm'] = df['Absolute_Precip_mm'].cumsum()
|
|
41
|
+
return df
|
|
42
|
+
|
|
43
|
+
def resample_data(df, freq='D'):
|
|
44
|
+
"""
|
|
45
|
+
Resamples the half-hourly data to Daily ('D') or Monthly ('M') totals.
|
|
46
|
+
Args:
|
|
47
|
+
df: Pandas DataFrame from IMERG excel
|
|
48
|
+
freq: 'D' for Daily, 'M' for Monthly
|
|
49
|
+
Returns:
|
|
50
|
+
Resampled DataFrame
|
|
51
|
+
"""
|
|
52
|
+
df = df.copy()
|
|
53
|
+
if 'Absolute_Precip_mm' not in df.columns:
|
|
54
|
+
df = add_accumulation(df)
|
|
55
|
+
|
|
56
|
+
time_col = _time_column(df)
|
|
57
|
+
df[time_col] = pd.to_datetime(df[time_col])
|
|
58
|
+
df.set_index(time_col, inplace=True)
|
|
59
|
+
|
|
60
|
+
# Resample and sum the absolute precipitation
|
|
61
|
+
resampled = df[['Absolute_Precip_mm']].resample(freq).sum()
|
|
62
|
+
resampled.rename(columns={'Absolute_Precip_mm': 'Total_Precip_mm'}, inplace=True)
|
|
63
|
+
|
|
64
|
+
return resampled.reset_index()
|
|
65
|
+
|
|
66
|
+
def calculate_statistics(df):
|
|
67
|
+
"""
|
|
68
|
+
Calculates extreme event statistics and thresholds for the given data.
|
|
69
|
+
"""
|
|
70
|
+
if 'Absolute_Precip_mm' not in df.columns:
|
|
71
|
+
df = add_accumulation(df)
|
|
72
|
+
|
|
73
|
+
# Get daily totals for threshold analysis
|
|
74
|
+
daily_df = resample_data(df, freq='D')
|
|
75
|
+
|
|
76
|
+
stats = {
|
|
77
|
+
"Total_Rainfall_mm": float(df['Absolute_Precip_mm'].sum()),
|
|
78
|
+
"Max_Interval_Precip_mm": float(df['Absolute_Precip_mm'].max()),
|
|
79
|
+
"Max_Daily_Rainfall_mm": float(daily_df['Total_Precip_mm'].max()),
|
|
80
|
+
"Total_Days_Analyzed": int(len(daily_df)),
|
|
81
|
+
"Dry_Days_(<1mm)": int(len(daily_df[daily_df['Total_Precip_mm'] < 1.0])),
|
|
82
|
+
"Wet_Days_(>=1mm)": int(len(daily_df[daily_df['Total_Precip_mm'] >= 1.0])),
|
|
83
|
+
"Heavy_Rain_Days_(>25mm)": int(len(daily_df[daily_df['Total_Precip_mm'] > 25.0])),
|
|
84
|
+
"Extreme_Rain_Days_(>50mm)": int(len(daily_df[daily_df['Total_Precip_mm'] > 50.0]))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return stats
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from .server import start_server
|
|
3
|
+
|
|
4
|
+
def main():
|
|
5
|
+
"""Entry point for the imergpy CLI."""
|
|
6
|
+
try:
|
|
7
|
+
start_server()
|
|
8
|
+
except KeyboardInterrupt:
|
|
9
|
+
print("\nShutting down imergpy interface...")
|
|
10
|
+
sys.exit(0)
|
|
11
|
+
|
|
12
|
+
if __name__ == "__main__":
|
|
13
|
+
main()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# List of exactly 48 time strings used by GES DISC OTF
|
|
2
|
+
IMERG_TIMES = [
|
|
3
|
+
"S000000-E002959.0000", "S003000-E005959.0030", "S010000-E012959.0060", "S013000-E015959.0090",
|
|
4
|
+
"S020000-E022959.0120", "S023000-E025959.0150", "S030000-E032959.0180", "S033000-E035959.0210",
|
|
5
|
+
"S040000-E042959.0240", "S043000-E045959.0270", "S050000-E052959.0300", "S053000-E055959.0330",
|
|
6
|
+
"S060000-E062959.0360", "S063000-E065959.0390", "S070000-E072959.0420", "S073000-E075959.0450",
|
|
7
|
+
"S080000-E082959.0480", "S083000-E085959.0510", "S090000-E092959.0540", "S093000-E095959.0570",
|
|
8
|
+
"S100000-E102959.0600", "S103000-E105959.0630", "S110000-E112959.0660", "S113000-E115959.0690",
|
|
9
|
+
"S120000-E122959.0720", "S123000-E125959.0750", "S130000-E132959.0780", "S133000-E135959.0810",
|
|
10
|
+
"S140000-E142959.0840", "S143000-E145959.0870", "S150000-E152959.0900", "S153000-E155959.0930",
|
|
11
|
+
"S160000-E162959.0960", "S163000-E165959.0990", "S170000-E172959.1020", "S173000-E175959.1050",
|
|
12
|
+
"S180000-E182959.1080", "S183000-E185959.1110", "S190000-E192959.1140", "S193000-E195959.1170",
|
|
13
|
+
"S200000-E202959.1200", "S203000-E205959.1230", "S210000-E212959.1260", "S213000-E215959.1290",
|
|
14
|
+
"S220000-E222959.1320", "S223000-E225959.1350", "S230000-E232959.1380", "S233000-E235959.1410"
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
def get_time_string(dt_obj):
|
|
18
|
+
"""
|
|
19
|
+
Given a datetime object, returns the correct IMERG time string interval.
|
|
20
|
+
IMERG files are 30 min increments starting at top of hour.
|
|
21
|
+
"""
|
|
22
|
+
hour = dt_obj.hour
|
|
23
|
+
minute = dt_obj.minute
|
|
24
|
+
|
|
25
|
+
# 0 to 29 mins maps to first half hour, 30 to 59 maps to second
|
|
26
|
+
idx = hour * 2 + (1 if minute >= 30 else 0)
|
|
27
|
+
return IMERG_TIMES[idx]
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tempfile
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from datetime import datetime, timedelta
|
|
5
|
+
from dateutil.relativedelta import relativedelta
|
|
6
|
+
from .downloader import DownloadError, EarthdataDownloader
|
|
7
|
+
from .processor import extract_area_average, extract_precipitation
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
VALID_RUN_TYPES = {"early", "late", "final"}
|
|
11
|
+
VALID_FREQUENCIES = {"hhr", "daily", "monthly"}
|
|
12
|
+
VALID_INTERPOLATION_METHODS = {"nearest", "linear", "cubic"}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _parse_datetime(value):
|
|
16
|
+
value = str(value).replace('T', ' ')
|
|
17
|
+
formats = {
|
|
18
|
+
7: "%Y-%m",
|
|
19
|
+
10: "%Y-%m-%d",
|
|
20
|
+
16: "%Y-%m-%d %H:%M",
|
|
21
|
+
}
|
|
22
|
+
try:
|
|
23
|
+
return datetime.strptime(value, formats[len(value)])
|
|
24
|
+
except (KeyError, ValueError) as e:
|
|
25
|
+
raise ValueError(f"Invalid date format: {value}. Use YYYY-MM, YYYY-MM-DD, or YYYY-MM-DD HH:MM.") from e
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _validate_inputs(lat, lon, run_type, freq, interp_method):
|
|
29
|
+
if not -90 <= float(lat) <= 90:
|
|
30
|
+
raise ValueError("lat must be between -90 and 90.")
|
|
31
|
+
if not -180 <= float(lon) <= 180:
|
|
32
|
+
raise ValueError("lon must be between -180 and 180.")
|
|
33
|
+
if run_type not in VALID_RUN_TYPES:
|
|
34
|
+
raise ValueError("run_type must be 'early', 'late', or 'final'.")
|
|
35
|
+
if freq not in VALID_FREQUENCIES:
|
|
36
|
+
raise ValueError("freq must be 'hhr', 'daily', or 'monthly'.")
|
|
37
|
+
if interp_method not in VALID_INTERPOLATION_METHODS:
|
|
38
|
+
raise ValueError("interp_method must be 'nearest', 'linear', or 'cubic'.")
|
|
39
|
+
if freq == "monthly" and run_type != "final":
|
|
40
|
+
raise ValueError("monthly frequency only supports run_type='final'.")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _excel_dataframe(results):
|
|
44
|
+
df = pd.DataFrame(results)
|
|
45
|
+
preferred_order = [
|
|
46
|
+
"Start_Time",
|
|
47
|
+
"End_Time",
|
|
48
|
+
"Requested_Lat",
|
|
49
|
+
"Requested_Lon",
|
|
50
|
+
"Actual_Lat",
|
|
51
|
+
"Actual_Lon",
|
|
52
|
+
"Interpolation",
|
|
53
|
+
"IMERG_Version",
|
|
54
|
+
"Run_Type",
|
|
55
|
+
"Region_Type",
|
|
56
|
+
"Region_Name",
|
|
57
|
+
"Min_Lat",
|
|
58
|
+
"Min_Lon",
|
|
59
|
+
"Max_Lat",
|
|
60
|
+
"Max_Lon",
|
|
61
|
+
"Grid_Cells_Averaged",
|
|
62
|
+
]
|
|
63
|
+
precip_cols = [c for c in df.columns if c.startswith("Precipitation_")]
|
|
64
|
+
ordered_cols = [c for c in preferred_order + precip_cols if c in df.columns]
|
|
65
|
+
remaining_cols = [c for c in df.columns if c not in ordered_cols]
|
|
66
|
+
df = df[ordered_cols + remaining_cols]
|
|
67
|
+
return df.rename(columns={"Start_Time": "Start Time", "End_Time": "End Time"})
|
|
68
|
+
|
|
69
|
+
def get_precipitation(lat, lon, start_datetime, end_datetime, username, password,
|
|
70
|
+
run_type="early", freq="hhr", interp_method="nearest", out_dir=".",
|
|
71
|
+
progress_callback=None, selection_mode="point", bbox=None,
|
|
72
|
+
geometry=None, region_name=None):
|
|
73
|
+
"""
|
|
74
|
+
Main function to download IMERG data for a time period and save to Excel.
|
|
75
|
+
Now includes dual Start_Time and End_Time columns.
|
|
76
|
+
"""
|
|
77
|
+
if selection_mode == "point":
|
|
78
|
+
_validate_inputs(lat, lon, run_type, freq, interp_method)
|
|
79
|
+
else:
|
|
80
|
+
_validate_inputs(lat, lon, run_type, freq, "nearest")
|
|
81
|
+
if not bbox:
|
|
82
|
+
raise ValueError("bbox is required for country and square-area downloads.")
|
|
83
|
+
dt_start = _parse_datetime(start_datetime)
|
|
84
|
+
dt_end = _parse_datetime(end_datetime)
|
|
85
|
+
|
|
86
|
+
if dt_end < dt_start:
|
|
87
|
+
raise ValueError("end_datetime must be after start_datetime")
|
|
88
|
+
|
|
89
|
+
downloader = EarthdataDownloader(username, password)
|
|
90
|
+
|
|
91
|
+
time_stamp_start = dt_start.strftime("%Y%m%d_%H%M")
|
|
92
|
+
time_stamp_end = dt_end.strftime("%Y%m%d_%H%M")
|
|
93
|
+
region_label = region_name or f"{lat}_{lon}"
|
|
94
|
+
region_label = "".join(c if c.isalnum() or c in "._-" else "_" for c in str(region_label))
|
|
95
|
+
excel_filename = f"IMERG_{run_type}_{freq}_{selection_mode}_{region_label}_{time_stamp_start}_to_{time_stamp_end}.xlsx"
|
|
96
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
97
|
+
excel_path = os.path.join(out_dir, excel_filename)
|
|
98
|
+
|
|
99
|
+
# Snap start time appropriately
|
|
100
|
+
if freq == "hhr":
|
|
101
|
+
minute = 0 if dt_start.minute < 30 else 30
|
|
102
|
+
current_dt = dt_start.replace(minute=minute, second=0, microsecond=0)
|
|
103
|
+
elif freq == "daily":
|
|
104
|
+
current_dt = dt_start.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
105
|
+
dt_end = dt_end.replace(hour=23, minute=59)
|
|
106
|
+
elif freq == "monthly":
|
|
107
|
+
current_dt = dt_start.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
|
108
|
+
total_steps = 0
|
|
109
|
+
temp_dt = current_dt
|
|
110
|
+
while temp_dt <= dt_end:
|
|
111
|
+
total_steps += 1
|
|
112
|
+
if freq == "hhr": temp_dt += timedelta(minutes=30)
|
|
113
|
+
elif freq == "daily": temp_dt += timedelta(days=1)
|
|
114
|
+
elif freq == "monthly": temp_dt += relativedelta(months=1)
|
|
115
|
+
|
|
116
|
+
results = []
|
|
117
|
+
failures = []
|
|
118
|
+
step_count = 0
|
|
119
|
+
if progress_callback:
|
|
120
|
+
progress_callback(0)
|
|
121
|
+
|
|
122
|
+
while current_dt <= dt_end:
|
|
123
|
+
step_count += 1
|
|
124
|
+
|
|
125
|
+
fd, temp_nc_path = tempfile.mkstemp(suffix=".nc4")
|
|
126
|
+
os.close(fd)
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
_, version_used = downloader.download_granule(lat, lon, current_dt, temp_nc_path, run_type, freq, bbox=bbox)
|
|
130
|
+
if selection_mode == "point":
|
|
131
|
+
data_dict = extract_precipitation(temp_nc_path, lat, lon, method=interp_method, freq=freq, current_dt=current_dt)
|
|
132
|
+
else:
|
|
133
|
+
data_dict = extract_area_average(
|
|
134
|
+
temp_nc_path,
|
|
135
|
+
bbox=bbox,
|
|
136
|
+
freq=freq,
|
|
137
|
+
current_dt=current_dt,
|
|
138
|
+
geometry=geometry,
|
|
139
|
+
region_name=region_name,
|
|
140
|
+
region_type=selection_mode,
|
|
141
|
+
)
|
|
142
|
+
data_dict["IMERG_Version"] = version_used
|
|
143
|
+
data_dict["Run_Type"] = run_type
|
|
144
|
+
results.append(data_dict)
|
|
145
|
+
except DownloadError as e:
|
|
146
|
+
failures.append({"datetime": current_dt.isoformat(), "error": str(e)})
|
|
147
|
+
print(f" -> Warning: {e}")
|
|
148
|
+
finally:
|
|
149
|
+
if os.path.exists(temp_nc_path):
|
|
150
|
+
os.remove(temp_nc_path)
|
|
151
|
+
if progress_callback:
|
|
152
|
+
progress_callback(int((step_count / total_steps) * 100))
|
|
153
|
+
|
|
154
|
+
if freq == "hhr": current_dt += timedelta(minutes=30)
|
|
155
|
+
elif freq == "daily": current_dt += timedelta(days=1)
|
|
156
|
+
elif freq == "monthly": current_dt += relativedelta(months=1)
|
|
157
|
+
|
|
158
|
+
if not results:
|
|
159
|
+
details = "; ".join(f"{f['datetime']}: {f['error']}" for f in failures[:3])
|
|
160
|
+
raise RuntimeError(f"No data could be successfully downloaded. {details}")
|
|
161
|
+
|
|
162
|
+
df = _excel_dataframe(results)
|
|
163
|
+
df.to_excel(excel_path, index=False)
|
|
164
|
+
|
|
165
|
+
# JSON-friendly results
|
|
166
|
+
serializable_results = []
|
|
167
|
+
for r in results:
|
|
168
|
+
entry = r.copy()
|
|
169
|
+
for k in ["Start_Time", "End_Time"]:
|
|
170
|
+
if hasattr(entry[k], 'isoformat'): entry[k] = entry[k].isoformat()
|
|
171
|
+
else: entry[k] = str(entry[k])
|
|
172
|
+
serializable_results.append(entry)
|
|
173
|
+
|
|
174
|
+
return excel_path, serializable_results
|