modverif 0.2.2__tar.gz → 0.2.4__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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modverif
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Project-URL: Documentation, https://mullenkamp.github.io/modverif/
5
5
  Project-URL: Source, https://github.com/mullenkamp/modverif
6
6
  Author-email: mullenkamp <mullenkamp1@gmail.com>
@@ -1,4 +1,4 @@
1
1
  from modverif.evaluator import Evaluator
2
2
  from modverif.station import StationEvaluator
3
3
 
4
- __version__ = '0.2.2'
4
+ __version__ = '0.2.4'
@@ -68,7 +68,9 @@ def _read_grid_from_ds(ds):
68
68
  Read 2D grid coordinate arrays and CRS info from a cfdb dataset.
69
69
 
70
70
  For datasets with ``latitude``/``longitude``: returns lat/lon arrays,
71
- data_crs=PlateCarree, is_projected=False.
71
+ data_crs=PlateCarree, is_projected=False. Longitudes > 180 are wrapped
72
+ to the -180..180 range and the longitude axis is reordered so values
73
+ are ascending (handles data that crosses the antimeridian).
72
74
 
73
75
  For projected datasets with ``y``/``x`` and a CRS: returns native
74
76
  projected coordinate arrays, the CRS, is_projected=True.
@@ -76,21 +78,35 @@ def _read_grid_from_ds(ds):
76
78
  Returns
77
79
  -------
78
80
  tuple
79
- (x2d, y2d, data_crs, is_projected) where x2d/y2d are 2D arrays
80
- in native coordinates and data_crs is a pyproj.CRS.
81
+ (x2d, y2d, data_crs, is_projected, lon_sort_idx) where x2d/y2d
82
+ are 2D arrays in native coordinates, data_crs is a pyproj.CRS,
83
+ and lon_sort_idx is an index array to reorder data along the
84
+ longitude axis (None if no reordering needed).
81
85
  """
82
86
  coord_names = set(ds.coord_names)
83
87
  var_names = set(ds.data_var_names)
84
88
 
85
89
  if 'latitude' in coord_names or 'latitude' in var_names:
86
90
  xlat, xlong = _read_latlon_2d(ds)
87
- return xlong, xlat, None, False
91
+
92
+ # Handle antimeridian: wrap > 180 and reorder so lons are ascending
93
+ lon_sort_idx = None
94
+ if np.any(xlong > 180):
95
+ xlong = np.where(xlong > 180, xlong - 360, xlong)
96
+ if xlong.ndim == 2:
97
+ # 1D coordinates meshed into 2D — reorder along lon axis
98
+ lon_1d = xlong[0, :]
99
+ lon_sort_idx = np.argsort(lon_1d)
100
+ xlong = xlong[:, lon_sort_idx]
101
+ xlat = xlat[:, lon_sort_idx]
102
+
103
+ return xlong, xlat, None, False, lon_sort_idx
88
104
 
89
105
  if 'y' in coord_names and 'x' in coord_names and ds.crs is not None:
90
106
  y_1d = ds['y'].data
91
107
  x_1d = ds['x'].data
92
108
  x2d, y2d = np.meshgrid(x_1d, y_1d)
93
- return x2d, y2d, ds.crs, True
109
+ return x2d, y2d, ds.crs, True, None
94
110
 
95
111
  raise ValueError(
96
112
  "Dataset must contain either 'latitude'/'longitude' coordinates "
@@ -99,6 +115,13 @@ def _read_grid_from_ds(ds):
99
115
  )
100
116
 
101
117
 
118
+ def _apply_lon_sort(data_2d, lon_sort_idx):
119
+ """Reorder a 2D array along the longitude axis if needed."""
120
+ if lon_sort_idx is not None:
121
+ return data_2d[:, lon_sort_idx]
122
+ return data_2d
123
+
124
+
102
125
  def _draw_composite_layers(
103
126
  ax,
104
127
  x2d: np.ndarray,
@@ -432,7 +455,7 @@ def plot_storm_composite_timestep(
432
455
  raise FileNotFoundError(f"Dataset not found: {cfdb_path}")
433
456
 
434
457
  with cfdb.open_dataset(cfdb_path) as ds:
435
- x2d, y2d, data_crs, is_projected = _read_grid_from_ds(ds)
458
+ x2d, y2d, data_crs, is_projected, lon_sort = _read_grid_from_ds(ds)
436
459
  time_values = ds['time'].data
437
460
 
438
461
  # Validate time index
@@ -440,15 +463,15 @@ def plot_storm_composite_timestep(
440
463
  raise IndexError(f"time_index {time_index} out of range [0, {len(time_values) - 1}]")
441
464
 
442
465
  # Read fields
443
- vimf_u = _read_var_2d(ds, vimf_u_var, time_index)
444
- vimf_v = _read_var_2d(ds, vimf_v_var, time_index)
445
- pwat = _read_var_2d(ds, pwat_var, time_index)
466
+ vimf_u = _apply_lon_sort(_read_var_2d(ds, vimf_u_var, time_index), lon_sort)
467
+ vimf_v = _apply_lon_sort(_read_var_2d(ds, vimf_v_var, time_index), lon_sort)
468
+ pwat = _apply_lon_sort(_read_var_2d(ds, pwat_var, time_index), lon_sort)
446
469
 
447
470
  # MSLP with compute fallback
448
471
  if mslp_var == 'mslp':
449
- mslp = _read_slp_from_cfdb(ds, time_index)
472
+ mslp = _apply_lon_sort(_read_slp_from_cfdb(ds, time_index), lon_sort)
450
473
  else:
451
- mslp = _read_var_2d(ds, mslp_var, time_index)
474
+ mslp = _apply_lon_sort(_read_var_2d(ds, mslp_var, time_index), lon_sort)
452
475
 
453
476
  time_str = str(time_values[time_index])
454
477
 
@@ -532,7 +555,7 @@ def plot_storm_composite(
532
555
  png_files = []
533
556
 
534
557
  with cfdb.open_dataset(cfdb_path) as ds:
535
- x2d, y2d, data_crs, is_projected = _read_grid_from_ds(ds)
558
+ x2d, y2d, data_crs, is_projected, lon_sort = _read_grid_from_ds(ds)
536
559
  time_values = ds['time'].data
537
560
  n_times = len(time_values)
538
561
 
@@ -571,14 +594,14 @@ def plot_storm_composite(
571
594
  continue
572
595
 
573
596
  # Read fields
574
- vimf_u = _read_var_2d(ds, vimf_u_var, t)
575
- vimf_v = _read_var_2d(ds, vimf_v_var, t)
576
- pwat = _read_var_2d(ds, pwat_var, t)
597
+ vimf_u = _apply_lon_sort(_read_var_2d(ds, vimf_u_var, t), lon_sort)
598
+ vimf_v = _apply_lon_sort(_read_var_2d(ds, vimf_v_var, t), lon_sort)
599
+ pwat = _apply_lon_sort(_read_var_2d(ds, pwat_var, t), lon_sort)
577
600
 
578
601
  if mslp_var == 'mslp':
579
- mslp = _read_slp_from_cfdb(ds, t)
602
+ mslp = _apply_lon_sort(_read_slp_from_cfdb(ds, t), lon_sort)
580
603
  else:
581
- mslp = _read_var_2d(ds, mslp_var, t)
604
+ mslp = _apply_lon_sort(_read_var_2d(ds, mslp_var, t), lon_sort)
582
605
 
583
606
  time_str = str(t_val)
584
607
  frame_path = output_dir / f'{filename_prefix}_t{t:03d}.png'
@@ -827,8 +850,8 @@ def plot_storm_composite_comparison_timestep(
827
850
  raise FileNotFoundError(f"Dataset not found: {cfdb_path_b}")
828
851
 
829
852
  with cfdb.open_dataset(cfdb_path_a) as ds_a, cfdb.open_dataset(cfdb_path_b) as ds_b:
830
- x2d_a, y2d_a, data_crs_a, is_projected_a = _read_grid_from_ds(ds_a)
831
- x2d_b, y2d_b, data_crs_b, is_projected_b = _read_grid_from_ds(ds_b)
853
+ x2d_a, y2d_a, data_crs_a, is_projected_a, lon_sort_a = _read_grid_from_ds(ds_a)
854
+ x2d_b, y2d_b, data_crs_b, is_projected_b, lon_sort_b = _read_grid_from_ds(ds_b)
832
855
 
833
856
  times_a = ds_a['time'].data
834
857
  times_b = ds_b['time'].data
@@ -842,22 +865,22 @@ def plot_storm_composite_comparison_timestep(
842
865
  t_val, idx_a, idx_b = matched[time_index]
843
866
 
844
867
  # Read fields from dataset A
845
- vimf_u_a = _read_var_2d(ds_a, vimf_u_var_a, idx_a)
846
- vimf_v_a = _read_var_2d(ds_a, vimf_v_var_a, idx_a)
847
- pwat_a = _read_var_2d(ds_a, pwat_var_a, idx_a)
868
+ vimf_u_a = _apply_lon_sort(_read_var_2d(ds_a, vimf_u_var_a, idx_a), lon_sort_a)
869
+ vimf_v_a = _apply_lon_sort(_read_var_2d(ds_a, vimf_v_var_a, idx_a), lon_sort_a)
870
+ pwat_a = _apply_lon_sort(_read_var_2d(ds_a, pwat_var_a, idx_a), lon_sort_a)
848
871
  if mslp_var_a == 'mslp':
849
- mslp_a = _read_slp_from_cfdb(ds_a, idx_a)
872
+ mslp_a = _apply_lon_sort(_read_slp_from_cfdb(ds_a, idx_a), lon_sort_a)
850
873
  else:
851
- mslp_a = _read_var_2d(ds_a, mslp_var_a, idx_a)
874
+ mslp_a = _apply_lon_sort(_read_var_2d(ds_a, mslp_var_a, idx_a), lon_sort_a)
852
875
 
853
876
  # Read fields from dataset B
854
- vimf_u_b = _read_var_2d(ds_b, vimf_u_var_b, idx_b)
855
- vimf_v_b = _read_var_2d(ds_b, vimf_v_var_b, idx_b)
856
- pwat_b = _read_var_2d(ds_b, pwat_var_b, idx_b)
877
+ vimf_u_b = _apply_lon_sort(_read_var_2d(ds_b, vimf_u_var_b, idx_b), lon_sort_b)
878
+ vimf_v_b = _apply_lon_sort(_read_var_2d(ds_b, vimf_v_var_b, idx_b), lon_sort_b)
879
+ pwat_b = _apply_lon_sort(_read_var_2d(ds_b, pwat_var_b, idx_b), lon_sort_b)
857
880
  if mslp_var_b == 'mslp':
858
- mslp_b = _read_slp_from_cfdb(ds_b, idx_b)
881
+ mslp_b = _apply_lon_sort(_read_slp_from_cfdb(ds_b, idx_b), lon_sort_b)
859
882
  else:
860
- mslp_b = _read_var_2d(ds_b, mslp_var_b, idx_b)
883
+ mslp_b = _apply_lon_sort(_read_var_2d(ds_b, mslp_var_b, idx_b), lon_sort_b)
861
884
 
862
885
  time_str = str(t_val)
863
886
 
@@ -960,8 +983,8 @@ def plot_storm_composite_comparison(
960
983
  png_files = []
961
984
 
962
985
  with cfdb.open_dataset(cfdb_path_a) as ds_a, cfdb.open_dataset(cfdb_path_b) as ds_b:
963
- x2d_a, y2d_a, data_crs_a, is_projected_a = _read_grid_from_ds(ds_a)
964
- x2d_b, y2d_b, data_crs_b, is_projected_b = _read_grid_from_ds(ds_b)
986
+ x2d_a, y2d_a, data_crs_a, is_projected_a, lon_sort_a = _read_grid_from_ds(ds_a)
987
+ x2d_b, y2d_b, data_crs_b, is_projected_b, lon_sort_b = _read_grid_from_ds(ds_b)
965
988
 
966
989
  times_a = ds_a['time'].data
967
990
  times_b = ds_b['time'].data
@@ -992,22 +1015,22 @@ def plot_storm_composite_comparison(
992
1015
 
993
1016
  for i, (t_val, idx_a, idx_b) in enumerate(matched):
994
1017
  # Read fields from dataset A
995
- vimf_u_a = _read_var_2d(ds_a, vimf_u_var_a, idx_a)
996
- vimf_v_a = _read_var_2d(ds_a, vimf_v_var_a, idx_a)
997
- pwat_a = _read_var_2d(ds_a, pwat_var_a, idx_a)
1018
+ vimf_u_a = _apply_lon_sort(_read_var_2d(ds_a, vimf_u_var_a, idx_a), lon_sort_a)
1019
+ vimf_v_a = _apply_lon_sort(_read_var_2d(ds_a, vimf_v_var_a, idx_a), lon_sort_a)
1020
+ pwat_a = _apply_lon_sort(_read_var_2d(ds_a, pwat_var_a, idx_a), lon_sort_a)
998
1021
  if mslp_var_a == 'mslp':
999
- mslp_a = _read_slp_from_cfdb(ds_a, idx_a)
1022
+ mslp_a = _apply_lon_sort(_read_slp_from_cfdb(ds_a, idx_a), lon_sort_a)
1000
1023
  else:
1001
- mslp_a = _read_var_2d(ds_a, mslp_var_a, idx_a)
1024
+ mslp_a = _apply_lon_sort(_read_var_2d(ds_a, mslp_var_a, idx_a), lon_sort_a)
1002
1025
 
1003
1026
  # Read fields from dataset B
1004
- vimf_u_b = _read_var_2d(ds_b, vimf_u_var_b, idx_b)
1005
- vimf_v_b = _read_var_2d(ds_b, vimf_v_var_b, idx_b)
1006
- pwat_b = _read_var_2d(ds_b, pwat_var_b, idx_b)
1027
+ vimf_u_b = _apply_lon_sort(_read_var_2d(ds_b, vimf_u_var_b, idx_b), lon_sort_b)
1028
+ vimf_v_b = _apply_lon_sort(_read_var_2d(ds_b, vimf_v_var_b, idx_b), lon_sort_b)
1029
+ pwat_b = _apply_lon_sort(_read_var_2d(ds_b, pwat_var_b, idx_b), lon_sort_b)
1007
1030
  if mslp_var_b == 'mslp':
1008
- mslp_b = _read_slp_from_cfdb(ds_b, idx_b)
1031
+ mslp_b = _apply_lon_sort(_read_slp_from_cfdb(ds_b, idx_b), lon_sort_b)
1009
1032
  else:
1010
- mslp_b = _read_var_2d(ds_b, mslp_var_b, idx_b)
1033
+ mslp_b = _apply_lon_sort(_read_var_2d(ds_b, mslp_var_b, idx_b), lon_sort_b)
1011
1034
 
1012
1035
  time_str = str(t_val)
1013
1036
  frame_path = output_dir / f'{filename_prefix}_t{i:03d}.png'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes