hydroanomaly 0.3.0__tar.gz → 0.5.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.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hydroanomaly
3
- Version: 0.3.0
4
- Summary: A Python package for hydro anomaly detection
3
+ Version: 0.5.0
4
+ Summary: A Python package for hydro anomaly detection with simple USGS data retrieval
5
5
  Home-page: https://github.com/yourusername/hydroanomaly
6
6
  Author: Your Name
7
7
  Author-email: Your Name <your.email@example.com>
@@ -20,6 +20,11 @@ Requires-Dist: numpy>=1.20.0
20
20
  Requires-Dist: requests>=2.25.1
21
21
  Requires-Dist: matplotlib>=3.3.0
22
22
  Requires-Dist: seaborn>=0.11.0
23
+ Requires-Dist: earthengine-api>=0.1.300
24
+ Requires-Dist: geemap>=0.20.0
25
+ Requires-Dist: tqdm>=4.60.0
26
+ Requires-Dist: scipy>=1.7.0
27
+ Requires-Dist: scikit-learn>=1.0.0
23
28
  Provides-Extra: dev
24
29
  Requires-Dist: pytest>=6.0; extra == "dev"
25
30
  Requires-Dist: black>=21.0; extra == "dev"
@@ -1,19 +1,71 @@
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.5.0"
8
9
  __author__ = "Your Name"
9
10
  __email__ = "your.email@example.com"
10
11
 
11
12
  # Import main modules for easy access
12
13
  from .hello import greet
13
14
  from .math_utils import add, multiply
14
- from .usgs_data import get_usgs_data, USGSDataRetriever
15
+ from .usgs_data import get_usgs_data, get_usgs_simple, USGSDataRetriever
15
16
  from .plotting import plot_usgs_data, plot_multiple_gages, quick_plot, WaterDataPlotter
16
17
 
18
+ # Base exports
19
+ __all__ = [
20
+ 'greet',
21
+ 'add', 'multiply',
22
+ 'get_usgs_data', 'get_usgs_simple', '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
+
17
69
  # Convenience functions for common use cases
18
70
  def get_discharge(gage_number, start_date, end_date, save_file=None):
19
71
  """
@@ -0,0 +1,516 @@
1
+ """
2
+ Google Earth Engine Sentinel Data Retrieval Module
3
+
4
+ This module provides functionality to retrieve and process Sentinel satellite data
5
+ from Google Earth Engine for water-related analysis and monitoring.
6
+
7
+ Features:
8
+ - User-configurable authentication and project setup
9
+ - Sentinel-2 surface reflectance data retrieval with custom masking
10
+ - Water body analysis using spectral indices
11
+ - Time series extraction for specific locations
12
+ - Anomaly detection capabilities
13
+ - Interactive mapping and visualization
14
+
15
+ Requirements:
16
+ - Google Earth Engine Python API (earthengine-api)
17
+ - geemap for visualization
18
+ - Valid GEE account and authentication
19
+ """
20
+
21
+ import ee
22
+ import pandas as pd
23
+ import numpy as np
24
+ import geemap
25
+ import warnings
26
+ from datetime import datetime, timedelta
27
+ from typing import Dict, List, Tuple, Optional, Union
28
+ from tqdm import tqdm
29
+ import matplotlib.pyplot as plt
30
+ import seaborn as sns
31
+
32
+ warnings.filterwarnings('ignore')
33
+
34
+
35
+ class SentinelConfig:
36
+ """
37
+ Configuration class for Sentinel data retrieval.
38
+ Users can customize these settings for their specific needs.
39
+ """
40
+
41
+ def __init__(self):
42
+ # Authentication settings (user must configure)
43
+ self.gee_project = None # User must set their GEE project ID
44
+
45
+ # Default masks to apply
46
+ self.masks_to_apply = [
47
+ "water", # Keep only SCL = 6 (water)
48
+ "no_cloud_shadow", # Remove cloud shadow (SCL = 3)
49
+ "no_clouds", # Remove clouds (SCL = 8, 9, 10)
50
+ "no_snow_ice", # Remove snow/ice (SCL = 11)
51
+ "no_saturated", # Remove saturated/defective pixels (SCL = 1)
52
+ ]
53
+
54
+ # Processing parameters
55
+ self.cloudy_pixel_percentage = 20 # Max allowed cloud percentage
56
+ self.buffer_meters = 20 # Buffer size around point, in meters
57
+ self.scale = 20 # Processing scale in meters
58
+
59
+ # Sentinel-2 bands to retrieve
60
+ self.bands = ['B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B8A', 'B9', 'B11', 'B12', 'SCL']
61
+
62
+ # Visualization parameters
63
+ self.ndwi_vis = {
64
+ 'min': 0,
65
+ 'max': 1,
66
+ 'palette': ['white', 'cyan', 'blue']
67
+ }
68
+
69
+ def set_project(self, project_id: str):
70
+ """Set the Google Earth Engine project ID."""
71
+ self.gee_project = project_id
72
+
73
+ def set_masks(self, masks: List[str]):
74
+ """Set the masks to apply during processing."""
75
+ self.masks_to_apply = masks
76
+
77
+ def set_buffer(self, buffer_meters: int):
78
+ """Set the buffer size around points."""
79
+ self.buffer_meters = buffer_meters
80
+
81
+ def set_cloud_threshold(self, threshold: int):
82
+ """Set the maximum cloud percentage."""
83
+ self.cloudy_pixel_percentage = threshold
84
+
85
+
86
+ class SentinelDataRetriever:
87
+ """
88
+ A class to retrieve and process Sentinel satellite data from Google Earth Engine.
89
+ """
90
+
91
+ def __init__(self, config: Optional[SentinelConfig] = None):
92
+ """
93
+ Initialize the Sentinel Data Retriever.
94
+
95
+ Parameters:
96
+ -----------
97
+ config : SentinelConfig, optional
98
+ Configuration object with user settings
99
+ """
100
+ self.config = config if config else SentinelConfig()
101
+ self.initialized = False
102
+
103
+ def authenticate_and_initialize(self, project_id: str, force_auth: bool = False):
104
+ """
105
+ Authenticate with Google Earth Engine and initialize.
106
+
107
+ Parameters:
108
+ -----------
109
+ project_id : str
110
+ Your Google Earth Engine project ID
111
+ force_auth : bool, default False
112
+ Whether to force re-authentication
113
+
114
+ Returns:
115
+ --------
116
+ bool : True if successful, False otherwise
117
+ """
118
+ try:
119
+ if force_auth:
120
+ print("🔐 Starting authentication process...")
121
+ ee.Authenticate()
122
+
123
+ print(f"🚀 Initializing Google Earth Engine with project: {project_id}")
124
+ ee.Initialize(project=project_id)
125
+
126
+ self.config.set_project(project_id)
127
+ self.initialized = True
128
+
129
+ print("✅ Google Earth Engine initialized successfully!")
130
+ return True
131
+
132
+ except Exception as e:
133
+ print(f"❌ Failed to initialize Google Earth Engine: {e}")
134
+ print("💡 Make sure you have:")
135
+ print(" 1. Valid GEE account access")
136
+ print(" 2. Correct project ID")
137
+ print(" 3. Run: earthengine authenticate (if needed)")
138
+ return False
139
+
140
+ def create_point_geometry(self, longitude: float, latitude: float) -> ee.Geometry.Point:
141
+ """
142
+ Create a point geometry for data extraction.
143
+
144
+ Parameters:
145
+ -----------
146
+ longitude : float
147
+ Longitude coordinate
148
+ latitude : float
149
+ Latitude coordinate
150
+
151
+ Returns:
152
+ --------
153
+ ee.Geometry.Point : Earth Engine point geometry
154
+ """
155
+ if not self.initialized:
156
+ raise RuntimeError("GEE not initialized. Call authenticate_and_initialize() first.")
157
+
158
+ return ee.Geometry.Point([longitude, latitude])
159
+
160
+ def get_available_dates(self, point: ee.Geometry.Point, start_date: str, end_date: str) -> List[str]:
161
+ """
162
+ Get available Sentinel-2 dates for a location.
163
+
164
+ Parameters:
165
+ -----------
166
+ point : ee.Geometry.Point
167
+ Location point
168
+ start_date : str
169
+ Start date in 'YYYY-MM-DD' format
170
+ end_date : str
171
+ End date in 'YYYY-MM-DD' format
172
+
173
+ Returns:
174
+ --------
175
+ List[str] : List of available dates
176
+ """
177
+ buffered_point = point.buffer(self.config.buffer_meters)
178
+
179
+ s2 = (ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
180
+ .filterBounds(buffered_point)
181
+ .filterDate(start_date, end_date))
182
+
183
+ def get_date(image):
184
+ return ee.Feature(None, {'date': image.date().format('YYYY-MM-dd HH:mm:ss')})
185
+
186
+ dates_fc = ee.FeatureCollection(s2.map(get_date))
187
+ date_list = dates_fc.aggregate_array('date').getInfo()
188
+
189
+ print(f'Available Sentinel-2 dates at this site:')
190
+ for d in date_list:
191
+ print(d)
192
+ print(f"Total: {len(date_list)} images from {start_date} to {end_date}")
193
+
194
+ return date_list
195
+
196
+ def dynamic_scl_mask(self, image):
197
+ """
198
+ Apply dynamic Scene Classification Layer (SCL) masks based on user configuration.
199
+
200
+ Parameters:
201
+ -----------
202
+ image : ee.Image
203
+ Sentinel-2 image
204
+
205
+ Returns:
206
+ --------
207
+ ee.Image : Masked image
208
+ """
209
+ scl = image.select('SCL')
210
+ mask = ee.Image.constant(1)
211
+
212
+ if "water" in self.config.masks_to_apply:
213
+ mask = mask.And(scl.eq(6))
214
+ if "no_cloud_shadow" in self.config.masks_to_apply:
215
+ mask = mask.And(scl.neq(3))
216
+ if "no_clouds" in self.config.masks_to_apply:
217
+ cloud_mask = scl.neq(8).And(scl.neq(9)).And(scl.neq(10))
218
+ mask = mask.And(cloud_mask)
219
+ if "no_snow_ice" in self.config.masks_to_apply:
220
+ mask = mask.And(scl.neq(11))
221
+ if "no_saturated" in self.config.masks_to_apply:
222
+ mask = mask.And(scl.neq(1))
223
+
224
+ return image.updateMask(mask)
225
+
226
+ def extract_time_series(self,
227
+ longitude: float,
228
+ latitude: float,
229
+ start_date: str,
230
+ end_date: str) -> pd.DataFrame:
231
+ """
232
+ Extract time series data for a specific location.
233
+
234
+ Parameters:
235
+ -----------
236
+ longitude : float
237
+ Longitude coordinate
238
+ latitude : float
239
+ Latitude coordinate
240
+ start_date : str
241
+ Start date in 'YYYY-MM-DD' format
242
+ end_date : str
243
+ End date in 'YYYY-MM-DD' format
244
+
245
+ Returns:
246
+ --------
247
+ pd.DataFrame : Time series data with spectral bands
248
+ """
249
+ if not self.initialized:
250
+ raise RuntimeError("GEE not initialized. Call authenticate_and_initialize() first.")
251
+
252
+ print(f"🛰️ Extracting Sentinel-2 time series...")
253
+ print(f"📍 Location: {latitude:.6f}°N, {longitude:.6f}°W")
254
+ print(f"📅 Period: {start_date} to {end_date}")
255
+ print(f"🎭 Masks: {self.config.masks_to_apply}")
256
+
257
+ point = self.create_point_geometry(longitude, latitude)
258
+ buffered_point = point.buffer(self.config.buffer_meters)
259
+
260
+ # Load and filter image collection
261
+ s2 = (ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
262
+ .filterBounds(buffered_point)
263
+ .filterDate(start_date, end_date)
264
+ .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', self.config.cloudy_pixel_percentage))
265
+ .select(self.config.bands)
266
+ .map(self.dynamic_scl_mask))
267
+
268
+ def extract_features(image):
269
+ date = image.date().format('YYYY-MM-dd HH:mm:ss')
270
+ values = image.reduceRegion(
271
+ reducer=ee.Reducer.mean(),
272
+ geometry=buffered_point,
273
+ scale=self.config.scale,
274
+ maxPixels=1e8
275
+ )
276
+ return ee.Feature(None, values.set('date', date))
277
+
278
+ print("🔄 Processing images and extracting features...")
279
+ features = s2.map(extract_features)
280
+ fc = ee.FeatureCollection(features).filter(ee.Filter.notNull(['B2']))
281
+
282
+ # Convert to pandas DataFrame
283
+ data = fc.getInfo()
284
+ print(f"Number of features: {len(data['features'])}")
285
+
286
+ rows = [f['properties'] for f in data['features']]
287
+ df = pd.DataFrame(rows)
288
+
289
+ if len(df) > 0:
290
+ df['date'] = pd.to_datetime(df['date'])
291
+ df = df.sort_values('date').reset_index(drop=True)
292
+
293
+ print(f"✅ Retrieved {len(df)} records")
294
+ print(f"📊 Date range: {df['date'].min()} to {df['date'].max()}")
295
+ else:
296
+ print("⚠️ No data retrieved. Try adjusting date range or masks.")
297
+
298
+ return df
299
+
300
+ def create_interactive_map(self,
301
+ longitude: float,
302
+ latitude: float,
303
+ start_date: str,
304
+ end_date: str,
305
+ zoom: int = 15) -> geemap.Map:
306
+ """
307
+ Create an interactive map showing the location and NDWI visualization.
308
+
309
+ Parameters:
310
+ -----------
311
+ longitude : float
312
+ Longitude coordinate
313
+ latitude : float
314
+ Latitude coordinate
315
+ start_date : str
316
+ Start date for image composite
317
+ end_date : str
318
+ End date for image composite
319
+ zoom : int, default 15
320
+ Map zoom level
321
+
322
+ Returns:
323
+ --------
324
+ geemap.Map : Interactive map object
325
+ """
326
+ if not self.initialized:
327
+ raise RuntimeError("GEE not initialized. Call authenticate_and_initialize() first.")
328
+
329
+ print("🗺️ Creating interactive map...")
330
+
331
+ point = self.create_point_geometry(longitude, latitude)
332
+ buffer = point.buffer(self.config.buffer_meters)
333
+
334
+ # Create median composite
335
+ image = (ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED")
336
+ .filterBounds(buffer)
337
+ .filterDate(start_date, end_date)
338
+ .filter(ee.Filter.lt("CLOUDY_PIXEL_PERCENTAGE", self.config.cloudy_pixel_percentage))
339
+ .median())
340
+
341
+ # Calculate NDWI
342
+ ndwi = image.normalizedDifference(["B3", "B8"]).rename("NDWI")
343
+
344
+ # Create map
345
+ Map = geemap.Map()
346
+ Map.centerObject(buffer, zoom=zoom)
347
+ Map.addLayer(ndwi, self.config.ndwi_vis, "NDWI (Water)")
348
+ Map.addLayer(point, {'color': 'yellow'}, 'Point')
349
+ Map.addLayer(buffer, {'color': 'red'}, f'Buffer ({self.config.buffer_meters}m)')
350
+
351
+ print(f"✅ Map created with NDWI visualization")
352
+ print(f"🎯 Centered on: {latitude:.6f}°N, {longitude:.6f}°W")
353
+
354
+ return Map
355
+
356
+ def calculate_water_indices(self, df: pd.DataFrame) -> pd.DataFrame:
357
+ """
358
+ Calculate water-related spectral indices from the DataFrame.
359
+
360
+ Parameters:
361
+ -----------
362
+ df : pd.DataFrame
363
+ DataFrame with Sentinel-2 band data
364
+
365
+ Returns:
366
+ --------
367
+ pd.DataFrame : DataFrame with added indices
368
+ """
369
+ if len(df) == 0:
370
+ return df
371
+
372
+ df_copy = df.copy()
373
+
374
+ # NDWI (Normalized Difference Water Index)
375
+ if 'B3' in df_copy.columns and 'B8' in df_copy.columns:
376
+ df_copy['NDWI'] = (df_copy['B3'] - df_copy['B8']) / (df_copy['B3'] + df_copy['B8'])
377
+
378
+ # MNDWI (Modified NDWI)
379
+ if 'B3' in df_copy.columns and 'B11' in df_copy.columns:
380
+ df_copy['MNDWI'] = (df_copy['B3'] - df_copy['B11']) / (df_copy['B3'] + df_copy['B11'])
381
+
382
+ # NDVI (Normalized Difference Vegetation Index)
383
+ if 'B4' in df_copy.columns and 'B8' in df_copy.columns:
384
+ df_copy['NDVI'] = (df_copy['B8'] - df_copy['B4']) / (df_copy['B8'] + df_copy['B4'])
385
+
386
+ # Water turbidity proxy
387
+ if 'B4' in df_copy.columns and 'B2' in df_copy.columns:
388
+ df_copy['TURBIDITY'] = df_copy['B4'] / df_copy['B2']
389
+
390
+ print("✅ Water indices calculated: NDWI, MNDWI, NDVI, TURBIDITY")
391
+ return df_copy
392
+
393
+ def plot_time_series(self, df: pd.DataFrame, indices: List[str] = None, save_path: str = None):
394
+ """
395
+ Plot time series of spectral indices.
396
+
397
+ Parameters:
398
+ -----------
399
+ df : pd.DataFrame
400
+ DataFrame with time series data
401
+ indices : List[str], optional
402
+ List of indices to plot (default: ['NDWI', 'MNDWI', 'NDVI'])
403
+ save_path : str, optional
404
+ Path to save the plot
405
+ """
406
+ if len(df) == 0:
407
+ print("⚠️ No data to plot")
408
+ return
409
+
410
+ if indices is None:
411
+ indices = ['NDWI', 'MNDWI', 'NDVI']
412
+
413
+ # Filter available indices
414
+ available_indices = [idx for idx in indices if idx in df.columns]
415
+
416
+ if not available_indices:
417
+ print("⚠️ No indices available to plot")
418
+ return
419
+
420
+ fig, axes = plt.subplots(len(available_indices), 1, figsize=(12, 4*len(available_indices)))
421
+ if len(available_indices) == 1:
422
+ axes = [axes]
423
+
424
+ for i, idx in enumerate(available_indices):
425
+ axes[i].plot(df['date'], df[idx], 'o-', markersize=4, linewidth=1.5)
426
+ axes[i].set_ylabel(idx)
427
+ axes[i].set_title(f'{idx} Time Series')
428
+ axes[i].grid(True, alpha=0.3)
429
+ axes[i].tick_params(axis='x', rotation=45)
430
+
431
+ plt.tight_layout()
432
+
433
+ if save_path:
434
+ plt.savefig(save_path, dpi=300, bbox_inches='tight')
435
+ print(f"📊 Plot saved: {save_path}")
436
+
437
+ plt.show()
438
+
439
+
440
+ # Convenience functions for easy access
441
+ def setup_gee_authentication(project_id: str, force_auth: bool = False) -> SentinelDataRetriever:
442
+ """
443
+ Set up Google Earth Engine authentication and return a configured retriever.
444
+
445
+ Parameters:
446
+ -----------
447
+ project_id : str
448
+ Your Google Earth Engine project ID
449
+ force_auth : bool, default False
450
+ Whether to force re-authentication
451
+
452
+ Returns:
453
+ --------
454
+ SentinelDataRetriever : Configured and initialized retriever
455
+
456
+ Example:
457
+ --------
458
+ >>> retriever = setup_gee_authentication('your-project-id')
459
+ """
460
+ config = SentinelConfig()
461
+ retriever = SentinelDataRetriever(config)
462
+
463
+ if retriever.authenticate_and_initialize(project_id, force_auth):
464
+ return retriever
465
+ else:
466
+ raise RuntimeError("Failed to initialize Google Earth Engine")
467
+
468
+
469
+ def get_sentinel_time_series(project_id: str,
470
+ longitude: float,
471
+ latitude: float,
472
+ start_date: str,
473
+ end_date: str,
474
+ masks: List[str] = None,
475
+ buffer_meters: int = 20) -> pd.DataFrame:
476
+ """
477
+ Quick function to get Sentinel-2 time series data.
478
+
479
+ Parameters:
480
+ -----------
481
+ project_id : str
482
+ Your Google Earth Engine project ID
483
+ longitude : float
484
+ Longitude coordinate
485
+ latitude : float
486
+ Latitude coordinate
487
+ start_date : str
488
+ Start date in 'YYYY-MM-DD' format
489
+ end_date : str
490
+ End date in 'YYYY-MM-DD' format
491
+ masks : List[str], optional
492
+ Custom masks to apply
493
+ buffer_meters : int, default 20
494
+ Buffer size around point
495
+
496
+ Returns:
497
+ --------
498
+ pd.DataFrame : Time series data
499
+
500
+ Example:
501
+ --------
502
+ >>> df = get_sentinel_time_series(
503
+ ... 'your-project-id',
504
+ ... -95.0644278, 29.7785861,
505
+ ... '2023-01-01', '2023-12-31'
506
+ ... )
507
+ """
508
+ retriever = setup_gee_authentication(project_id)
509
+
510
+ if masks:
511
+ retriever.config.set_masks(masks)
512
+ if buffer_meters != 20:
513
+ retriever.config.set_buffer(buffer_meters)
514
+
515
+ df = retriever.extract_time_series(longitude, latitude, start_date, end_date)
516
+ return retriever.calculate_water_indices(df)
@@ -309,3 +309,81 @@ def get_usgs_data(
309
309
  retriever.save_data(data, save_to_file, parameter_name)
310
310
 
311
311
  return data
312
+
313
+
314
+ def get_usgs_simple(site_number: str, parameter_code: str, start_date: str, end_date: str, plot: bool = True) -> pd.DataFrame:
315
+ """
316
+ 🚀 SIMPLE USGS DATA RETRIEVAL WITH AUTOMATIC PLOTTING
317
+
318
+ This is the main function users should use for easy USGS data retrieval.
319
+ Just provide site, parameter, dates and get data + plot automatically!
320
+
321
+ Args:
322
+ site_number (str): USGS site number (e.g., "294643095035200")
323
+ parameter_code (str): Parameter code (e.g., "63680" for turbidity)
324
+ start_date (str): Start date as "YYYY-MM-DD"
325
+ end_date (str): End date as "YYYY-MM-DD"
326
+ plot (bool): Whether to automatically create a plot (default: True)
327
+
328
+ Returns:
329
+ pd.DataFrame: Time series data with datetime index and values
330
+
331
+ Example:
332
+ >>> data = get_usgs_simple("294643095035200", "63680", "2020-01-01", "2024-12-30")
333
+ >>> # Automatically retrieves turbidity data and shows a plot!
334
+ """
335
+ import matplotlib.pyplot as plt
336
+ import matplotlib.dates as mdates
337
+ from datetime import datetime
338
+
339
+ print("🌊 USGS Data Retrieval - Simple Mode")
340
+ print("=" * 50)
341
+ print(f"📍 Site: {site_number}")
342
+ print(f"📊 Parameter: {parameter_code}")
343
+ print(f"📅 Date Range: {start_date} to {end_date}")
344
+ print()
345
+
346
+ try:
347
+ # Get the data using existing function
348
+ retriever = USGSDataRetriever()
349
+ data = retriever.retrieve_data(site_number, parameter_code, start_date, end_date)
350
+
351
+ if data is None or len(data) == 0:
352
+ print("❌ No data retrieved")
353
+ return pd.DataFrame()
354
+
355
+ # Data summary
356
+ print(f"✅ Success! Retrieved {len(data)} data points")
357
+ print(f"📈 Data range: {data.index.min()} to {data.index.max()}")
358
+ print(f"📊 Values: {data.iloc[:, 0].min():.2f} to {data.iloc[:, 0].max():.2f}")
359
+ print()
360
+
361
+ # Create automatic plot if requested
362
+ if plot:
363
+ plt.figure(figsize=(12, 6))
364
+ plt.plot(data.index, data.iloc[:, 0], linewidth=1, alpha=0.8)
365
+
366
+ # Format the plot
367
+ plt.title(f'USGS Time Series Data\nSite: {site_number} | Parameter: {parameter_code}',
368
+ fontsize=14, fontweight='bold')
369
+ plt.xlabel('Date', fontsize=12)
370
+ plt.ylabel(f'Parameter {parameter_code}', fontsize=12)
371
+ plt.grid(True, alpha=0.3)
372
+
373
+ # Format x-axis dates
374
+ plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
375
+ plt.gca().xaxis.set_major_locator(mdates.MonthLocator(interval=6))
376
+ plt.xticks(rotation=45)
377
+
378
+ # Improve layout
379
+ plt.tight_layout()
380
+ plt.show()
381
+
382
+ print("📊 Plot created successfully!")
383
+
384
+ return data
385
+
386
+ except Exception as e:
387
+ print(f"❌ Error: {str(e)}")
388
+ print("💡 Please check your inputs and try again")
389
+ return pd.DataFrame()
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hydroanomaly
3
- Version: 0.3.0
4
- Summary: A Python package for hydro anomaly detection
3
+ Version: 0.5.0
4
+ Summary: A Python package for hydro anomaly detection with simple USGS data retrieval
5
5
  Home-page: https://github.com/yourusername/hydroanomaly
6
6
  Author: Your Name
7
7
  Author-email: Your Name <your.email@example.com>
@@ -20,6 +20,11 @@ Requires-Dist: numpy>=1.20.0
20
20
  Requires-Dist: requests>=2.25.1
21
21
  Requires-Dist: matplotlib>=3.3.0
22
22
  Requires-Dist: seaborn>=0.11.0
23
+ Requires-Dist: earthengine-api>=0.1.300
24
+ Requires-Dist: geemap>=0.20.0
25
+ Requires-Dist: tqdm>=4.60.0
26
+ Requires-Dist: scipy>=1.7.0
27
+ Requires-Dist: scikit-learn>=1.0.0
23
28
  Provides-Extra: dev
24
29
  Requires-Dist: pytest>=6.0; extra == "dev"
25
30
  Requires-Dist: black>=21.0; extra == "dev"
@@ -6,6 +6,7 @@ hydroanomaly/__init__.py
6
6
  hydroanomaly/hello.py
7
7
  hydroanomaly/math_utils.py
8
8
  hydroanomaly/plotting.py
9
+ hydroanomaly/sentinel_data.py
9
10
  hydroanomaly/usgs_data.py
10
11
  hydroanomaly.egg-info/PKG-INFO
11
12
  hydroanomaly.egg-info/SOURCES.txt
@@ -3,6 +3,11 @@ numpy>=1.20.0
3
3
  requests>=2.25.1
4
4
  matplotlib>=3.3.0
5
5
  seaborn>=0.11.0
6
+ earthengine-api>=0.1.300
7
+ geemap>=0.20.0
8
+ tqdm>=4.60.0
9
+ scipy>=1.7.0
10
+ scikit-learn>=1.0.0
6
11
 
7
12
  [dev]
8
13
  pytest>=6.0
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "hydroanomaly"
7
- version = "0.3.0"
7
+ version = "0.5.0"
8
8
  authors = [
9
9
  {name = "Your Name", email = "your.email@example.com"},
10
10
  ]
11
- description = "A Python package for hydro anomaly detection"
11
+ description = "A Python package for hydro anomaly detection with simple USGS data retrieval"
12
12
  readme = "README.md"
13
13
  license = "MIT"
14
14
  requires-python = ">=3.6"
@@ -23,6 +23,11 @@ dependencies = [
23
23
  "requests>=2.25.1",
24
24
  "matplotlib>=3.3.0",
25
25
  "seaborn>=0.11.0",
26
+ "earthengine-api>=0.1.300",
27
+ "geemap>=0.20.0",
28
+ "tqdm>=4.60.0",
29
+ "scipy>=1.7.0",
30
+ "scikit-learn>=1.0.0",
26
31
  ]
27
32
 
28
33
  [project.urls]
@@ -2,10 +2,10 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="hydroanomaly",
5
- version="0.3.0",
5
+ version="0.5.0",
6
6
  author="Your Name",
7
7
  author_email="your.email@example.com",
8
- description="A Python package for hydro anomaly detection, USGS data retrieval, and time series visualization",
8
+ description="A Python package for hydro anomaly detection with simple USGS data retrieval",
9
9
  long_description=open("README.md").read(),
10
10
  long_description_content_type="text/markdown",
11
11
  url="https://github.com/yourusername/hydroanomaly",
@@ -22,5 +22,10 @@ setup(
22
22
  "requests>=2.25.1",
23
23
  "matplotlib>=3.3.0",
24
24
  "seaborn>=0.11.0",
25
+ "earthengine-api>=0.1.300",
26
+ "geemap>=0.20.0",
27
+ "tqdm>=4.60.0",
28
+ "scipy>=1.7.0",
29
+ "scikit-learn>=1.0.0",
25
30
  ],
26
31
  )
File without changes
File without changes
File without changes