Skip to content

Commit df2f4dd

Browse files
committed
Fixed tangent-plane advection bug
1 parent 7ecaa3f commit df2f4dd

4 files changed

Lines changed: 19 additions & 10 deletions

File tree

src/EarthSHAB/Forecast.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,8 +358,8 @@ def _advect(self, lats, lons, u, v, dt):
358358
# same arithmetic as utils.transform.{latlon_to_meters,
359359
# meters_to_latlon}_spherical, minus their NaN guards).
360360
cosc = self._cos_central
361-
cx = (lons - self.central_lon) * 111000.0 * cosc
362-
cy = (lats - self.central_lat) * 111000.0
361+
cx = (lons - self.central_lon) * 111320.0 * cosc
362+
cy = (lats - self.central_lat) * 111320.0
363363
x_new = cx + u * dt
364364
y_new = cy + v * dt
365365
lat_new = np.clip(self.central_lat + y_new / 111320.0, -89.999999, 89.999999)

src/EarthSHAB/config.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
# for the evaluation suite. To run a current-day prediction instead, set a recent
2323
# forecast_start_time (cycle hour 00/06/12/18 UTC), set balloon_trajectory = None,
2424
# and download the forecast first with `python -m EarthSHAB.saveNETCDF`.
25-
forecast_start_time = "2026-06-15 12:00:00" # Forecast start time, should match a downloaded forecast in the forecasts directory
26-
start_time = datetime.fromisoformat("2026-06-15 14:36:00") # Simulation start time. The end time needs to be within the downloaded forecast
25+
forecast_start_time = "2026-06-16 06:00:00" # Forecast start time, should match a downloaded forecast in the forecasts directory
26+
start_time = datetime.fromisoformat("2026-06-16 14:36:00") # Simulation start time. The end time needs to be within the downloaded forecast
2727
balloon_trajectory = None # Only Accepting Files in the Standard APRS.fi format for now
2828

2929
# Single forecast file path. The reader (EarthSHAB.Forecast.Forecast) opens
@@ -57,7 +57,7 @@
5757
)
5858

5959
forecast = dict(
60-
file = "/home/schuler/HAB-COM/GFS_DATA/NETCDF/gfs_20260615_12z_1p00_3h.nc",
60+
file = "/home/schuler/HAB-COM/GFS_DATA/NETCDF/gfs_20260616_06z_1p00_3h.nc",
6161
forecast_start_time = forecast_start_time, # used to build the default file path above
6262
forecast_update_interval = 60, # (s) After how many iterated dt steps are new wind speeds are looked up
6363

@@ -80,7 +80,7 @@
8080
# re-anchored at the balloon each step.
8181
# 'tangent_plane' - spherical tangent-plane step about the fixed launch
8282
# anchor.
83-
advection = 'tangent_plane',
83+
advection = 'geodesic',
8484

8585
# Wind-lookup backend tier (same trajectory within float32-cache tolerance):
8686
# 'numpy' - (default) pure-numpy per-member sampling, float64. Matches

src/EarthSHAB/utils/transform.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ def latlon_to_meters_spherical(origin_lat, origin_lon, target_lat, target_lon):
4848
#target_lat_rad = math.radians(target_lat) # unused now
4949

5050
# Calculate distance for latitude change
51-
lat_distance = (target_lat - origin_lat) * 111000 # meters per degree of latitude
51+
lat_distance = (target_lat - origin_lat) * 111320 # meters per degree of latitude
5252

5353
# Calculate distance for longitude change
5454
#longitude_distance = (target_lon - origin_lon) * 111000 * math.cos((origin_lat_rad + target_lat_rad) / 2) #OLD OUTDATED TRANSFROM. THIS FAILED OUTSIDE of 150 km due to Transform errors
55-
longitude_distance = (target_lon - origin_lon) * 111000 * math.cos(origin_lat_rad)
55+
longitude_distance = (target_lon - origin_lon) * 111320 * math.cos(origin_lat_rad)
5656

5757
# If lat or lon is nan from the transform step being too close to the central coordinate, revert back to central coordinate distance
5858
if np.isnan(longitude_distance):

tests/test_savenetcdf_archive.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,16 @@ def test_select_ranges_level_subset():
8080
# ── URL construction ────────────────────────────────────────────────────────────
8181

8282
def test_cycle_file_url():
83-
assert arch.cycle_file_url("20220822", 12, 0) == (
83+
# cycle_file_url's res_token defaults to RES_TOKEN, which is frozen from
84+
# config at import time. Pass res_token explicitly so the test asserts the
85+
# URL format deterministically, independent of the res config declares.
86+
assert arch.cycle_file_url("20220822", 12, 0, res_token="0p25") == (
8487
"https://noaa-gfs-bdp-pds.s3.amazonaws.com/"
8588
"gfs.20220822/12/atmos/gfs.t12z.pgrb2.0p25.f000"
8689
)
87-
assert arch.cycle_file_url("20220822", 6, 27).endswith("gfs.t06z.pgrb2.0p25.f027")
90+
assert arch.cycle_file_url(
91+
"20220822", 6, 27, res_token="0p25"
92+
).endswith("gfs.t06z.pgrb2.0p25.f027")
8893

8994

9095
# ── Output filename ───────────────────────────────────────────────────────────
@@ -212,6 +217,10 @@ def test_live_step_hours_controls_forecast_hours(monkeypatch):
212217
nc_start = base.replace(hour=(base.hour // 6) * 6, minute=0, second=0, microsecond=0)
213218
monkeypatch.setitem(config_earth.netcdf_gfs, "nc_start", nc_start)
214219
monkeypatch.setitem(config_earth.netcdf_gfs, "download_days", 1)
220+
# Pin res: hourly steps exist only on the 0.25° grid, so the test must not
221+
# inherit whatever res config currently declares (a 0.5°/1° default would
222+
# make step_hours=1 invalid and raise before the URL-building path runs).
223+
monkeypatch.setitem(config_earth.netcdf_gfs, "res", 0.25)
215224
monkeypatch.setitem(config_earth.netcdf_gfs, "step_hours", 1)
216225

217226
seen = []

0 commit comments

Comments
 (0)