hydroanomaly 0.2.0__py3-none-any.whl → 0.4.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.
hydroanomaly/__init__.py CHANGED
@@ -1,10 +1,11 @@
1
1
  """
2
2
  HydroAnomaly
3
3
 
4
- A Python package for hydro anomaly detection and USGS data retrieval.
4
+ A Python package for hydro anomaly detection, USGS data retrieval,
5
+ time series visualization, and Sentinel satellite data analysis.
5
6
  """
6
7
 
7
- __version__ = "0.2.0"
8
+ __version__ = "0.4.0"
8
9
  __author__ = "Your Name"
9
10
  __email__ = "your.email@example.com"
10
11
 
@@ -12,5 +13,134 @@ __email__ = "your.email@example.com"
12
13
  from .hello import greet
13
14
  from .math_utils import add, multiply
14
15
  from .usgs_data import get_usgs_data, USGSDataRetriever
16
+ from .plotting import plot_usgs_data, plot_multiple_gages, quick_plot, WaterDataPlotter
15
17
 
16
- __all__ = ["greet", "add", "multiply", "get_usgs_data", "USGSDataRetriever"]
18
+ # Base exports
19
+ __all__ = [
20
+ 'greet',
21
+ 'add', 'multiply',
22
+ 'get_usgs_data', 'USGSDataRetriever',
23
+ 'plot_usgs_data', 'plot_multiple_gages', 'quick_plot', 'WaterDataPlotter',
24
+ 'get_discharge', 'get_temperature', 'get_water_level'
25
+ ]
26
+
27
+ # Try to import Sentinel functionality (optional GEE dependency)
28
+ try:
29
+ from .sentinel_data import (
30
+ SentinelDataRetriever,
31
+ SentinelConfig,
32
+ setup_gee_authentication,
33
+ initialize_gee,
34
+ get_water_area_time_series,
35
+ detect_water_changes
36
+ )
37
+ _SENTINEL_AVAILABLE = True
38
+
39
+ # Add Sentinel functions to exports
40
+ __all__.extend([
41
+ 'SentinelDataRetriever',
42
+ 'SentinelConfig',
43
+ 'setup_gee_authentication',
44
+ 'initialize_gee',
45
+ 'get_water_area_time_series',
46
+ 'detect_water_changes'
47
+ ])
48
+
49
+ except ImportError as e:
50
+ print("⚠️ Sentinel data functionality not available.")
51
+ print("💡 To use Google Earth Engine features, install:")
52
+ print(" pip install earthengine-api")
53
+ print(" Then authenticate: earthengine authenticate")
54
+ _SENTINEL_AVAILABLE = False
55
+
56
+ # Create placeholder functions for better error messages
57
+ def setup_gee_authentication(*args, **kwargs):
58
+ raise ImportError("Google Earth Engine not available. Install with: pip install earthengine-api")
59
+
60
+ def initialize_gee(*args, **kwargs):
61
+ raise ImportError("Google Earth Engine not available. Install with: pip install earthengine-api")
62
+
63
+ def get_water_area_time_series(*args, **kwargs):
64
+ raise ImportError("Google Earth Engine not available. Install with: pip install earthengine-api")
65
+
66
+ def detect_water_changes(*args, **kwargs):
67
+ raise ImportError("Google Earth Engine not available. Install with: pip install earthengine-api")
68
+
69
+ # Convenience functions for common use cases
70
+ def get_discharge(gage_number, start_date, end_date, save_file=None):
71
+ """
72
+ Quick function to get discharge data from any USGS gage.
73
+
74
+ Args:
75
+ gage_number (str): USGS gage number (e.g., "08158000")
76
+ start_date (str): Start date in YYYY-MM-DD format
77
+ end_date (str): End date in YYYY-MM-DD format
78
+ save_file (str, optional): Filename to save data
79
+
80
+ Returns:
81
+ pandas.DataFrame: Discharge data
82
+
83
+ Example:
84
+ >>> import hydroanomaly
85
+ >>> data = hydroanomaly.get_discharge("08158000", "2023-01-01", "2023-01-31")
86
+ >>> print(f"Got {len(data)} discharge measurements")
87
+ """
88
+ return get_usgs_data(
89
+ site_number=gage_number,
90
+ parameter_code="00060", # Discharge
91
+ start_date=start_date,
92
+ end_date=end_date,
93
+ save_to_file=save_file,
94
+ parameter_name="Discharge_cfs"
95
+ )
96
+
97
+ def get_water_level(gage_number, start_date, end_date, save_file=None):
98
+ """
99
+ Quick function to get water level data from any USGS gage.
100
+
101
+ Args:
102
+ gage_number (str): USGS gage number (e.g., "08158000")
103
+ start_date (str): Start date in YYYY-MM-DD format
104
+ end_date (str): End date in YYYY-MM-DD format
105
+ save_file (str, optional): Filename to save data
106
+
107
+ Returns:
108
+ pandas.DataFrame: Water level data
109
+ """
110
+ return get_usgs_data(
111
+ site_number=gage_number,
112
+ parameter_code="00065", # Gage height
113
+ start_date=start_date,
114
+ end_date=end_date,
115
+ save_to_file=save_file,
116
+ parameter_name="WaterLevel_ft"
117
+ )
118
+
119
+ def get_temperature(gage_number, start_date, end_date, save_file=None):
120
+ """
121
+ Quick function to get water temperature data from any USGS gage.
122
+
123
+ Args:
124
+ gage_number (str): USGS gage number (e.g., "08158000")
125
+ start_date (str): Start date in YYYY-MM-DD format
126
+ end_date (str): End date in YYYY-MM-DD format
127
+ save_file (str, optional): Filename to save data
128
+
129
+ Returns:
130
+ pandas.DataFrame: Temperature data
131
+ """
132
+ return get_usgs_data(
133
+ site_number=gage_number,
134
+ parameter_code="00010", # Temperature
135
+ start_date=start_date,
136
+ end_date=end_date,
137
+ save_to_file=save_file,
138
+ parameter_name="Temperature_C"
139
+ )
140
+
141
+ __all__ = [
142
+ "greet", "add", "multiply",
143
+ "get_usgs_data", "USGSDataRetriever",
144
+ "get_discharge", "get_water_level", "get_temperature",
145
+ "plot_usgs_data", "plot_multiple_gages", "quick_plot", "WaterDataPlotter"
146
+ ]
@@ -0,0 +1,389 @@
1
+ """
2
+ Plotting Module for HydroAnomaly
3
+
4
+ This module provides easy-to-use plotting functions for USGS water data time series.
5
+ Creates professional-looking plots with minimal code.
6
+ """
7
+
8
+ import matplotlib.pyplot as plt
9
+ import matplotlib.dates as mdates
10
+ import seaborn as sns
11
+ import pandas as pd
12
+ import numpy as np
13
+ from datetime import datetime
14
+ from typing import Optional, Tuple, List, Dict, Any
15
+ import warnings
16
+
17
+ # Set style
18
+ plt.style.use('default')
19
+ sns.set_palette("husl")
20
+
21
+
22
+ class WaterDataPlotter:
23
+ """
24
+ A class for creating professional time series plots of water data.
25
+
26
+ This class provides methods to create various types of plots including
27
+ basic time series, multi-parameter plots, and statistical visualizations.
28
+ """
29
+
30
+ def __init__(self, style: str = 'seaborn-v0_8', figsize: Tuple[int, int] = (12, 6)):
31
+ """
32
+ Initialize the plotter with default settings.
33
+
34
+ Args:
35
+ style (str): Matplotlib style to use
36
+ figsize (tuple): Default figure size (width, height)
37
+ """
38
+ self.default_figsize = figsize
39
+ self.colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd',
40
+ '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf']
41
+
42
+ # Set plotting style
43
+ try:
44
+ plt.style.use(style)
45
+ except:
46
+ plt.style.use('default')
47
+ warnings.warn(f"Style '{style}' not available, using default")
48
+
49
+ def plot_timeseries(
50
+ self,
51
+ data: pd.DataFrame,
52
+ parameter_name: str = "Value",
53
+ title: Optional[str] = None,
54
+ ylabel: Optional[str] = None,
55
+ color: str = '#1f77b4',
56
+ save_path: Optional[str] = None,
57
+ show_stats: bool = True,
58
+ figsize: Optional[Tuple[int, int]] = None
59
+ ) -> plt.Figure:
60
+ """
61
+ Create a basic time series plot.
62
+
63
+ Args:
64
+ data (pd.DataFrame): Data with 'datetime' and 'value' columns
65
+ parameter_name (str): Name of the parameter being plotted
66
+ title (str, optional): Plot title
67
+ ylabel (str, optional): Y-axis label
68
+ color (str): Line color
69
+ save_path (str, optional): Path to save the plot
70
+ show_stats (bool): Whether to show statistics on the plot
71
+ figsize (tuple, optional): Figure size
72
+
73
+ Returns:
74
+ matplotlib.figure.Figure: The created figure
75
+ """
76
+ if len(data) == 0:
77
+ raise ValueError("No data to plot")
78
+
79
+ figsize = figsize or self.default_figsize
80
+ fig, ax = plt.subplots(figsize=figsize)
81
+
82
+ # Plot the data
83
+ ax.plot(data['datetime'], data['value'], color=color, linewidth=1.5, alpha=0.8)
84
+
85
+ # Customize the plot
86
+ if title is None:
87
+ title = f"{parameter_name} Time Series"
88
+ ax.set_title(title, fontsize=14, fontweight='bold', pad=20)
89
+
90
+ if ylabel is None:
91
+ ylabel = parameter_name
92
+ ax.set_ylabel(ylabel, fontsize=12)
93
+ ax.set_xlabel('Date', fontsize=12)
94
+
95
+ # Format dates on x-axis
96
+ self._format_date_axis(ax, data['datetime'])
97
+
98
+ # Add grid
99
+ ax.grid(True, alpha=0.3, linestyle='--')
100
+
101
+ # Add statistics if requested
102
+ if show_stats:
103
+ self._add_statistics_text(ax, data['value'], parameter_name)
104
+
105
+ # Improve layout
106
+ plt.tight_layout()
107
+
108
+ # Save if requested
109
+ if save_path:
110
+ plt.savefig(save_path, dpi=300, bbox_inches='tight')
111
+ print(f"📊 Plot saved to: {save_path}")
112
+
113
+ return fig
114
+
115
+ def plot_multiple_parameters(
116
+ self,
117
+ data_dict: Dict[str, pd.DataFrame],
118
+ title: str = "Multiple Parameters Time Series",
119
+ save_path: Optional[str] = None,
120
+ figsize: Optional[Tuple[int, int]] = None
121
+ ) -> plt.Figure:
122
+ """
123
+ Plot multiple parameters on separate subplots.
124
+
125
+ Args:
126
+ data_dict (dict): Dictionary with parameter names as keys and DataFrames as values
127
+ title (str): Main plot title
128
+ save_path (str, optional): Path to save the plot
129
+ figsize (tuple, optional): Figure size
130
+
131
+ Returns:
132
+ matplotlib.figure.Figure: The created figure
133
+ """
134
+ n_params = len(data_dict)
135
+ if n_params == 0:
136
+ raise ValueError("No data provided")
137
+
138
+ figsize = figsize or (12, 4 * n_params)
139
+ fig, axes = plt.subplots(n_params, 1, figsize=figsize, sharex=True)
140
+
141
+ if n_params == 1:
142
+ axes = [axes]
143
+
144
+ colors = self.colors[:n_params]
145
+
146
+ for i, (param_name, data) in enumerate(data_dict.items()):
147
+ if len(data) == 0:
148
+ continue
149
+
150
+ ax = axes[i]
151
+ ax.plot(data['datetime'], data['value'],
152
+ color=colors[i], linewidth=1.5, alpha=0.8, label=param_name)
153
+
154
+ ax.set_ylabel(param_name, fontsize=11)
155
+ ax.grid(True, alpha=0.3, linestyle='--')
156
+ ax.legend(loc='upper right')
157
+
158
+ # Add basic stats
159
+ mean_val = data['value'].mean()
160
+ ax.axhline(y=mean_val, color=colors[i], linestyle=':', alpha=0.6,
161
+ label=f'Mean: {mean_val:.2f}')
162
+
163
+ # Format the bottom subplot x-axis
164
+ if data_dict:
165
+ sample_data = next(iter(data_dict.values()))
166
+ self._format_date_axis(axes[-1], sample_data['datetime'])
167
+
168
+ axes[-1].set_xlabel('Date', fontsize=12)
169
+ fig.suptitle(title, fontsize=14, fontweight='bold')
170
+
171
+ plt.tight_layout()
172
+
173
+ if save_path:
174
+ plt.savefig(save_path, dpi=300, bbox_inches='tight')
175
+ print(f"📊 Plot saved to: {save_path}")
176
+
177
+ return fig
178
+
179
+ def plot_comparison(
180
+ self,
181
+ data_list: List[Tuple[pd.DataFrame, str]],
182
+ title: str = "Data Comparison",
183
+ ylabel: str = "Value",
184
+ save_path: Optional[str] = None,
185
+ figsize: Optional[Tuple[int, int]] = None
186
+ ) -> plt.Figure:
187
+ """
188
+ Plot multiple datasets on the same axes for comparison.
189
+
190
+ Args:
191
+ data_list (list): List of tuples (DataFrame, label)
192
+ title (str): Plot title
193
+ ylabel (str): Y-axis label
194
+ save_path (str, optional): Path to save the plot
195
+ figsize (tuple, optional): Figure size
196
+
197
+ Returns:
198
+ matplotlib.figure.Figure: The created figure
199
+ """
200
+ if not data_list:
201
+ raise ValueError("No data provided")
202
+
203
+ figsize = figsize or self.default_figsize
204
+ fig, ax = plt.subplots(figsize=figsize)
205
+
206
+ colors = self.colors[:len(data_list)]
207
+
208
+ for i, (data, label) in enumerate(data_list):
209
+ if len(data) == 0:
210
+ continue
211
+
212
+ ax.plot(data['datetime'], data['value'],
213
+ color=colors[i], linewidth=1.5, alpha=0.8, label=label)
214
+
215
+ ax.set_title(title, fontsize=14, fontweight='bold', pad=20)
216
+ ax.set_ylabel(ylabel, fontsize=12)
217
+ ax.set_xlabel('Date', fontsize=12)
218
+ ax.grid(True, alpha=0.3, linestyle='--')
219
+ ax.legend()
220
+
221
+ # Format dates
222
+ if data_list and len(data_list[0][0]) > 0:
223
+ self._format_date_axis(ax, data_list[0][0]['datetime'])
224
+
225
+ plt.tight_layout()
226
+
227
+ if save_path:
228
+ plt.savefig(save_path, dpi=300, bbox_inches='tight')
229
+ print(f"📊 Plot saved to: {save_path}")
230
+
231
+ return fig
232
+
233
+ def plot_statistics(
234
+ self,
235
+ data: pd.DataFrame,
236
+ parameter_name: str = "Parameter",
237
+ save_path: Optional[str] = None
238
+ ) -> plt.Figure:
239
+ """
240
+ Create statistical plots (histogram and box plot).
241
+
242
+ Args:
243
+ data (pd.DataFrame): Data with 'datetime' and 'value' columns
244
+ parameter_name (str): Name of the parameter
245
+ save_path (str, optional): Path to save the plot
246
+
247
+ Returns:
248
+ matplotlib.figure.Figure: The created figure
249
+ """
250
+ if len(data) == 0:
251
+ raise ValueError("No data to plot")
252
+
253
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
254
+
255
+ # Histogram
256
+ ax1.hist(data['value'], bins=30, alpha=0.7, color=self.colors[0], edgecolor='black')
257
+ ax1.set_title(f'{parameter_name} Distribution', fontweight='bold')
258
+ ax1.set_xlabel(parameter_name)
259
+ ax1.set_ylabel('Frequency')
260
+ ax1.grid(True, alpha=0.3)
261
+
262
+ # Add statistics to histogram
263
+ mean_val = data['value'].mean()
264
+ median_val = data['value'].median()
265
+ ax1.axvline(mean_val, color='red', linestyle='--', alpha=0.8, label=f'Mean: {mean_val:.2f}')
266
+ ax1.axvline(median_val, color='orange', linestyle='--', alpha=0.8, label=f'Median: {median_val:.2f}')
267
+ ax1.legend()
268
+
269
+ # Box plot
270
+ box_data = ax2.boxplot(data['value'], patch_artist=True)
271
+ box_data['boxes'][0].set_facecolor(self.colors[1])
272
+ box_data['boxes'][0].set_alpha(0.7)
273
+
274
+ ax2.set_title(f'{parameter_name} Box Plot', fontweight='bold')
275
+ ax2.set_ylabel(parameter_name)
276
+ ax2.grid(True, alpha=0.3)
277
+
278
+ plt.tight_layout()
279
+
280
+ if save_path:
281
+ plt.savefig(save_path, dpi=300, bbox_inches='tight')
282
+ print(f"📊 Plot saved to: {save_path}")
283
+
284
+ return fig
285
+
286
+ def _format_date_axis(self, ax, dates):
287
+ """Format the date axis based on the date range."""
288
+ date_range = (dates.max() - dates.min()).days
289
+
290
+ if date_range <= 7: # Less than a week
291
+ ax.xaxis.set_major_formatter(mdates.DateFormatter('%m/%d %H:%M'))
292
+ ax.xaxis.set_major_locator(mdates.HourLocator(interval=6))
293
+ elif date_range <= 31: # Less than a month
294
+ ax.xaxis.set_major_formatter(mdates.DateFormatter('%m/%d'))
295
+ ax.xaxis.set_major_locator(mdates.DayLocator(interval=2))
296
+ elif date_range <= 365: # Less than a year
297
+ ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
298
+ ax.xaxis.set_major_locator(mdates.MonthLocator())
299
+ else: # More than a year
300
+ ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
301
+ ax.xaxis.set_major_locator(mdates.YearLocator())
302
+
303
+ plt.setp(ax.xaxis.get_majorticklabels(), rotation=45, ha='right')
304
+
305
+ def _add_statistics_text(self, ax, values, parameter_name):
306
+ """Add statistics text box to the plot."""
307
+ stats_text = (
308
+ f"Statistics:\n"
309
+ f"Mean: {values.mean():.2f}\n"
310
+ f"Median: {values.median():.2f}\n"
311
+ f"Min: {values.min():.2f}\n"
312
+ f"Max: {values.max():.2f}\n"
313
+ f"Std: {values.std():.2f}"
314
+ )
315
+
316
+ # Position the text box
317
+ ax.text(0.02, 0.98, stats_text, transform=ax.transAxes, fontsize=9,
318
+ verticalalignment='top', bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.8))
319
+
320
+
321
+ # Convenience functions for easy plotting
322
+ def plot_usgs_data(
323
+ data: pd.DataFrame,
324
+ parameter_name: str = "Value",
325
+ title: Optional[str] = None,
326
+ save_path: Optional[str] = None,
327
+ show_stats: bool = True
328
+ ) -> plt.Figure:
329
+ """
330
+ Quick function to plot USGS time series data.
331
+
332
+ Args:
333
+ data (pd.DataFrame): Data with 'datetime' and 'value' columns
334
+ parameter_name (str): Name of the parameter being plotted
335
+ title (str, optional): Plot title
336
+ save_path (str, optional): Path to save the plot
337
+ show_stats (bool): Whether to show statistics on the plot
338
+
339
+ Returns:
340
+ matplotlib.figure.Figure: The created figure
341
+
342
+ Example:
343
+ >>> import hydroanomaly
344
+ >>> data = hydroanomaly.get_discharge("08158000", "2023-01-01", "2023-01-31")
345
+ >>> hydroanomaly.plot_usgs_data(data, "Discharge (cfs)", "Colorado River Discharge")
346
+ """
347
+ plotter = WaterDataPlotter()
348
+ return plotter.plot_timeseries(
349
+ data=data,
350
+ parameter_name=parameter_name,
351
+ title=title,
352
+ save_path=save_path,
353
+ show_stats=show_stats
354
+ )
355
+
356
+
357
+ def plot_multiple_gages(
358
+ data_dict: Dict[str, pd.DataFrame],
359
+ title: str = "Multiple Gage Comparison",
360
+ parameter_name: str = "Value",
361
+ save_path: Optional[str] = None
362
+ ) -> plt.Figure:
363
+ """
364
+ Plot data from multiple gages for comparison.
365
+
366
+ Args:
367
+ data_dict (dict): Dictionary with gage IDs as keys and DataFrames as values
368
+ title (str): Plot title
369
+ parameter_name (str): Y-axis label
370
+ save_path (str, optional): Path to save the plot
371
+
372
+ Returns:
373
+ matplotlib.figure.Figure: The created figure
374
+ """
375
+ plotter = WaterDataPlotter()
376
+ data_list = [(data, f"Gage {gage_id}") for gage_id, data in data_dict.items()]
377
+ return plotter.plot_comparison(data_list, title, parameter_name, save_path)
378
+
379
+
380
+ def quick_plot(data: pd.DataFrame, title: str = "USGS Data") -> None:
381
+ """
382
+ Create a quick plot and show it immediately.
383
+
384
+ Args:
385
+ data (pd.DataFrame): Data to plot
386
+ title (str): Plot title
387
+ """
388
+ plot_usgs_data(data, title=title)
389
+ plt.show()