mirror of
https://github.com/SpudGunMan/meshing-around.git
synced 2026-06-28 05:51:10 +02:00
Add comprehensive error handling and documentation for xtide module
Co-authored-by: SpudGunMan <12676665+SpudGunMan@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
# xtide Module - Global Tide Predictions
|
||||
|
||||
This module provides global tide prediction capabilities using the [tidepredict](https://github.com/windcrusader/tidepredict) library, which uses the University of Hawaii's Research Quality Dataset for worldwide tide station coverage.
|
||||
|
||||
## Features
|
||||
|
||||
- Global tide predictions (not limited to US locations like NOAA)
|
||||
- Offline predictions once station data is initialized
|
||||
- Automatic selection of nearest tide station
|
||||
- Compatible with existing tide command interface
|
||||
|
||||
## Installation
|
||||
|
||||
1. Install tidepredict library:
|
||||
```bash
|
||||
pip install tidepredict
|
||||
```
|
||||
|
||||
2. Enable in `config.ini`:
|
||||
```ini
|
||||
[location]
|
||||
useTidePredict = True
|
||||
```
|
||||
|
||||
## First-Time Setup
|
||||
|
||||
On first use, tidepredict needs to download station data from the University of Hawaii FTP server. This requires internet access and happens automatically when you:
|
||||
|
||||
1. Run the tide command for the first time with `useTidePredict = True`
|
||||
2. Or manually initialize with:
|
||||
```bash
|
||||
python3 -m tidepredict -l <location> -genharm
|
||||
```
|
||||
|
||||
The station data is cached locally in `~/.tidepredict/` for offline use afterward.
|
||||
|
||||
## Usage
|
||||
|
||||
Once enabled, the existing `tide` command will automatically use tidepredict for global locations:
|
||||
|
||||
```
|
||||
tide
|
||||
```
|
||||
|
||||
The module will:
|
||||
1. Find the nearest tide station to your GPS coordinates
|
||||
2. Load harmonic constituents for that station
|
||||
3. Calculate tide predictions for today
|
||||
4. Format output compatible with mesh display
|
||||
|
||||
## Configuration
|
||||
|
||||
### config.ini Options
|
||||
|
||||
```ini
|
||||
[location]
|
||||
# Enable global tide predictions using tidepredict
|
||||
useTidePredict = True
|
||||
|
||||
# Standard location settings still apply
|
||||
lat = 48.50
|
||||
lon = -123.0
|
||||
useMetric = False
|
||||
```
|
||||
|
||||
## Fallback Behavior
|
||||
|
||||
If tidepredict is not available or encounters errors, the module will automatically fall back to the NOAA API for US locations.
|
||||
|
||||
## Limitations
|
||||
|
||||
- First-time setup requires internet access to download station database
|
||||
- Station coverage depends on University of Hawaii's dataset
|
||||
- Predictions may be less accurate for locations far from tide stations
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Station database not initialized" error
|
||||
|
||||
This means the station data hasn't been downloaded yet. Ensure internet access and:
|
||||
|
||||
```bash
|
||||
# Test station download
|
||||
python3 -m tidepredict -l Sydney
|
||||
|
||||
# Or manually run initialization
|
||||
python3 -c "from tidepredict import process_station_list; process_station_list.create_station_dataframe()"
|
||||
```
|
||||
|
||||
### "No tide station found nearby"
|
||||
|
||||
The module couldn't find a nearby station. This may happen if:
|
||||
- You're in a location without nearby tide monitoring stations
|
||||
- The station database hasn't been initialized
|
||||
- Network issues prevented loading the station list
|
||||
|
||||
## Data Source
|
||||
|
||||
Tide predictions are based on harmonic analysis of historical tide data from:
|
||||
- University of Hawaii Sea Level Center (UHSLC)
|
||||
- Research Quality Dataset
|
||||
- Global coverage with 600+ stations
|
||||
|
||||
## References
|
||||
|
||||
- [tidepredict GitHub](https://github.com/windcrusader/tidepredict)
|
||||
- [UHSLC Data](https://uhslc.soest.hawaii.edu/)
|
||||
- [pytides](https://github.com/sam-cox/pytides) - Underlying tide calculation library
|
||||
+14
-7
@@ -27,10 +27,14 @@ def get_nearest_station(lat, lon):
|
||||
# Read the station list
|
||||
try:
|
||||
stations = pd.read_csv(constants.STATIONFILE)
|
||||
except:
|
||||
# If station file doesn't exist, create it
|
||||
logger.info("xtide: Creating station database from online source")
|
||||
stations = process_station_list.create_station_dataframe()
|
||||
except FileNotFoundError:
|
||||
# If station file doesn't exist, create it (requires network)
|
||||
logger.info("xtide: Creating station database from online source (requires network)")
|
||||
try:
|
||||
stations = process_station_list.create_station_dataframe()
|
||||
except Exception as net_error:
|
||||
logger.error(f"xtide: Failed to download station database: {net_error}")
|
||||
return None
|
||||
|
||||
if stations.empty:
|
||||
logger.error("xtide: No stations found in database")
|
||||
@@ -119,7 +123,7 @@ def get_tide_predictions(lat=0, lon=0, days=1):
|
||||
# Find nearest station
|
||||
station_info = get_nearest_station(float(lat), float(lon))
|
||||
if not station_info:
|
||||
return "No tide station found nearby"
|
||||
return "No tide station found nearby. Network may be required to download station data."
|
||||
|
||||
station_code, station_name, station_country = station_info
|
||||
|
||||
@@ -128,8 +132,8 @@ def get_tide_predictions(lat=0, lon=0, days=1):
|
||||
|
||||
# Check if harmonic data exists for this station
|
||||
if station_code not in station_dict:
|
||||
logger.warning(f"xtide: No harmonic data for {station_name}. Generating from online data...")
|
||||
return f"Tide data not available for {station_name}. Use 'tide ?' for more info."
|
||||
logger.warning(f"xtide: No harmonic data for {station_name}.")
|
||||
return f"Tide data not available for {station_name}. Station database may need initialization."
|
||||
|
||||
# Reconstruct tide model
|
||||
tide = processdata.reconstruct_tide_model(station_dict, station_code)
|
||||
@@ -186,6 +190,9 @@ def get_tide_predictions(lat=0, lon=0, days=1):
|
||||
else:
|
||||
return predictions
|
||||
|
||||
except FileNotFoundError as e:
|
||||
logger.error(f"xtide: Station data file not found: {e}")
|
||||
return "Tide station database not initialized. Network access required for first-time setup."
|
||||
except Exception as e:
|
||||
logger.error(f"xtide: Error getting tide predictions: {e}")
|
||||
return f"Error getting tide data: {str(e)}"
|
||||
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for xtide module
|
||||
Tests both NOAA (disabled) and tidepredict (when available) tide predictions
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add parent directory to path
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
def test_xtide_import():
|
||||
"""Test that xtide module can be imported"""
|
||||
print("Testing xtide module import...")
|
||||
try:
|
||||
from modules import xtide
|
||||
print(f"✓ xtide module imported successfully")
|
||||
print(f" - tidepredict available: {xtide.TIDEPREDICT_AVAILABLE}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to import xtide: {e}")
|
||||
return False
|
||||
|
||||
def test_locationdata_import():
|
||||
"""Test that modified locationdata can be imported"""
|
||||
print("\nTesting locationdata module import...")
|
||||
try:
|
||||
from modules import locationdata
|
||||
print(f"✓ locationdata module imported successfully")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to import locationdata: {e}")
|
||||
return False
|
||||
|
||||
def test_settings():
|
||||
"""Test that settings has useTidePredict option"""
|
||||
print("\nTesting settings configuration...")
|
||||
try:
|
||||
from modules import settings as my_settings
|
||||
has_setting = hasattr(my_settings, 'useTidePredict')
|
||||
print(f"✓ settings module loaded")
|
||||
print(f" - useTidePredict setting available: {has_setting}")
|
||||
if has_setting:
|
||||
print(f" - useTidePredict value: {my_settings.useTidePredict}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to load settings: {e}")
|
||||
return False
|
||||
|
||||
def test_noaa_fallback():
|
||||
"""Test NOAA API fallback (without enabling tidepredict)"""
|
||||
print("\nTesting NOAA API (default mode)...")
|
||||
try:
|
||||
from modules import locationdata
|
||||
from modules import settings as my_settings
|
||||
|
||||
# Test with Seattle coordinates (should use NOAA)
|
||||
lat = 47.6062
|
||||
lon = -122.3321
|
||||
|
||||
print(f" Testing with Seattle coordinates: {lat}, {lon}")
|
||||
print(f" useTidePredict = {my_settings.useTidePredict}")
|
||||
|
||||
# Note: This will fail if we can't reach NOAA, but that's expected
|
||||
result = locationdata.get_NOAAtide(str(lat), str(lon))
|
||||
if result and "Error" not in result:
|
||||
print(f"✓ NOAA API returned data")
|
||||
print(f" First 100 chars: {result[:100]}")
|
||||
return True
|
||||
else:
|
||||
print(f"⚠ NOAA API returned: {result[:100]}")
|
||||
return True # Still pass as network might not be available
|
||||
except Exception as e:
|
||||
print(f"⚠ NOAA test encountered expected issue: {e}")
|
||||
return True # Expected in test environment
|
||||
|
||||
def test_parse_coords():
|
||||
"""Test coordinate parsing function"""
|
||||
print("\nTesting coordinate parsing...")
|
||||
try:
|
||||
from modules.xtide import parse_station_coords
|
||||
|
||||
test_cases = [
|
||||
(("43-36S", "172-43E"), (-43.6, 172.71666666666667)),
|
||||
(("02-45N", "072-21E"), (2.75, 72.35)),
|
||||
(("02-45S", "072-21W"), (-2.75, -72.35)),
|
||||
]
|
||||
|
||||
all_passed = True
|
||||
for (lat_str, lon_str), (expected_lat, expected_lon) in test_cases:
|
||||
result_lat, result_lon = parse_station_coords(lat_str, lon_str)
|
||||
if abs(result_lat - expected_lat) < 0.01 and abs(result_lon - expected_lon) < 0.01:
|
||||
print(f" ✓ {lat_str}, {lon_str} -> {result_lat:.2f}, {result_lon:.2f}")
|
||||
else:
|
||||
print(f" ✗ {lat_str}, {lon_str} -> expected {expected_lat}, {expected_lon}, got {result_lat}, {result_lon}")
|
||||
all_passed = False
|
||||
|
||||
return all_passed
|
||||
except Exception as e:
|
||||
print(f"✗ Coordinate parsing test failed: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""Run all tests"""
|
||||
print("=" * 60)
|
||||
print("xtide Module Test Suite")
|
||||
print("=" * 60)
|
||||
|
||||
results = []
|
||||
results.append(("Import xtide", test_xtide_import()))
|
||||
results.append(("Import locationdata", test_locationdata_import()))
|
||||
results.append(("Settings configuration", test_settings()))
|
||||
results.append(("Parse coordinates", test_parse_coords()))
|
||||
results.append(("NOAA fallback", test_noaa_fallback()))
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Test Results Summary")
|
||||
print("=" * 60)
|
||||
|
||||
passed = sum(1 for _, result in results if result)
|
||||
total = len(results)
|
||||
|
||||
for test_name, result in results:
|
||||
status = "✓ PASS" if result else "✗ FAIL"
|
||||
print(f"{status}: {test_name}")
|
||||
|
||||
print(f"\n{passed}/{total} tests passed")
|
||||
|
||||
return passed == total
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = main()
|
||||
sys.exit(0 if success else 1)
|
||||
Reference in New Issue
Block a user