5
5
from numpy import concatenate , int32 , empty , where , array , \
6
6
sin , deg2rad , pi , ones , cos , ma , int8 , histogram2d , arange , float_ , \
7
7
linspace , errstate , int_ , interp , meshgrid , nan , ceil , sinc , isnan , \
8
- percentile , zeros , arctan2 , arcsin , round_
8
+ percentile , zeros , arctan2 , arcsin , round_ , nanmean
9
9
from datetime import datetime
10
10
from scipy .special import j1
11
11
from netCDF4 import Dataset
@@ -71,10 +71,14 @@ def value_on_regular_contour(x_g, y_g, z_g, m_g, vertices, num_fac=2, fixed_size
71
71
72
72
73
73
@njit (cache = True )
74
- def mean_on_regular_contour (x_g , y_g , z_g , m_g , vertices , num_fac = 2 , fixed_size = None ):
74
+ def mean_on_regular_contour (x_g , y_g , z_g , m_g , vertices , num_fac = 2 , fixed_size = None , nan_remove = False ):
75
75
x_val , y_val = vertices [:, 0 ], vertices [:, 1 ]
76
76
x_new , y_new = uniform_resample (x_val , y_val , num_fac , fixed_size )
77
- return interp2d_geo (x_g , y_g , z_g , m_g , x_new [1 :], y_new [1 :]).mean ()
77
+ values = interp2d_geo (x_g , y_g , z_g , m_g , x_new [1 :], y_new [1 :])
78
+ if nan_remove :
79
+ return nanmean (values )
80
+ else :
81
+ return values .mean ()
78
82
79
83
80
84
def fit_circle_path (self ):
@@ -415,7 +419,7 @@ def bounds(self):
415
419
return self .x_bounds .min (), self .x_bounds .max (), self .y_bounds .min (), self .y_bounds .max ()
416
420
417
421
def eddy_identification (self , grid_height , uname , vname , date , step = 0.005 , shape_error = 55 ,
418
- array_sampling = 50 , pixel_limit = None , bbox_surface_min_degree = .125 ** 2 ):
422
+ array_sampling = 50 , pixel_limit = None ):
419
423
if not isinstance (date , datetime ):
420
424
raise Exception ('Date argument be a datetime object' )
421
425
# The inf limit must be in pixel and sup limit in surface
@@ -446,7 +450,7 @@ def eddy_identification(self, grid_height, uname, vname, date, step=0.005, shape
446
450
x , y = self .x_c , self .y_c
447
451
448
452
# Compute ssh contour
449
- self .contours = Contours (x , y , data , levels , bbox_surface_min_degree = bbox_surface_min_degree , wrap_x = self .is_circular ())
453
+ self .contours = Contours (x , y , data , levels , wrap_x = self .is_circular ())
450
454
451
455
track_extra_variables = ['height_max_speed_contour' , 'height_external_contour' , 'height_inner_contour' ]
452
456
array_variables = ['contour_lon_e' , 'contour_lat_e' , 'contour_lon_s' , 'contour_lat_s' , 'uavg_profile' ]
@@ -525,7 +529,7 @@ def eddy_identification(self, grid_height, uname, vname, date, step=0.005, shape
525
529
# centlat_e and centlon_e must be index of maximum, we will loose some inner contour, if it's not
526
530
max_average_speed , speed_contour , inner_contour , speed_array , i_max_speed , i_inner = \
527
531
self .get_uavg (self .contours , centlon_e , centlat_e , current_contour , anticyclonic_search ,
528
- corrected_coll_index )
532
+ corrected_coll_index , pixel_min = pixel_limit [ 0 ] )
529
533
530
534
# Use azimuth equal projection for radius
531
535
proj = Proj ('+proj=aeqd +ellps=WGS84 +lat_0={1} +lon_0={0}' .format (* inner_contour .mean_coordinates ))
@@ -606,7 +610,6 @@ def get_uavg(self, all_contours, centlon_e, centlat_e, original_contour, anticyc
606
610
"""
607
611
max_average_speed = self .speed_coef_mean (original_contour )
608
612
speed_array = [max_average_speed ]
609
- pixel_min = 1
610
613
611
614
eddy_contours = [original_contour ]
612
615
inner_contour = selected_contour = original_contour
@@ -619,22 +622,16 @@ def get_uavg(self, all_contours, centlon_e, centlat_e, original_contour, anticyc
619
622
# Leave loop if no contours at level
620
623
if level_contour is None :
621
624
break
622
- # 1. Ensure polygon_i contains point centlon_e, centlat_e (Maybe we loose some inner contour if eddy
623
- # core are not centered)
624
- # if winding_number_poly(centlon_e, centlat_e, level_contour.vertices) == 0:
625
- # break
626
- # 2. Ensure polygon_i is within polygon_e
625
+ # Ensure polygon_i is within polygon_e
627
626
if not poly_contain_poly (original_contour .vertices , level_contour .vertices ):
628
627
break
629
- # 3. Respect size range
628
+ # 3. Respect size range (for max speed)
630
629
# nb_pixel properties need call of pixels_in before with a grid of pixel
631
630
level_contour .pixels_in (self )
632
- if pixel_min > level_contour .nb_pixel :
633
- break
634
631
# Interpolate uspd to seglon, seglat, then get mean
635
632
level_average_speed = self .speed_coef_mean (level_contour )
636
633
speed_array .append (level_average_speed )
637
- if level_average_speed >= max_average_speed :
634
+ if pixel_min < level_contour . nb_pixel and level_average_speed >= max_average_speed :
638
635
max_average_speed = level_average_speed
639
636
i_max_speed = i
640
637
selected_contour = level_contour
@@ -1065,19 +1062,118 @@ def spectrum_lonlat(self, grid_name, area=None, ref=None, **kwargs):
1065
1062
return (lon_content [0 ], lon_content [1 ] / ref_lon_content [1 ]), \
1066
1063
(lat_content [0 ], lat_content [1 ] / ref_lat_content [1 ])
1067
1064
1068
- def add_uv (self , grid_height ):
1065
+ def compute_grad (self , data , stencil_halfwidth = 4 , mode = 'reflect' , vertical = False ):
1066
+ stencil_halfwidth = max (min (int (stencil_halfwidth ), 4 ), 1 )
1067
+ logging .debug ('Stencil half width apply : %d' , stencil_halfwidth )
1068
+ # output
1069
+ grad = None
1070
+
1071
+ weights = [
1072
+ array ((3 , - 32 , 168 , - 672 , 0 , 672 , - 168 , 32 , - 3 )) / 840. ,
1073
+ array ((- 1 , 9 , - 45 , 0 , 45 , - 9 , 1 )) / 60. ,
1074
+ array ((1 , - 8 , 0 , 8 , - 1 )) / 12. ,
1075
+ array ((- 1 , 0 , 1 )) / 2. ,
1076
+ # like array((0, -1, 1))
1077
+ array ((- 1 , 1 )),
1078
+ # like array((-1, 1, 0))
1079
+ (1 , array ((- 1 , 1 ))),
1080
+ ]
1081
+ # reduce to stencil selected
1082
+ weights = weights [4 - stencil_halfwidth :]
1083
+ if vertical :
1084
+ data = data .T
1085
+ # Iteration from larger stencil to smaller (to fill matrix)
1086
+ for weight in weights :
1087
+ if isinstance (weight , tuple ):
1088
+ # In the cas of unbalanced diff
1089
+ shift , weight = weight
1090
+ data_ = data .copy ()
1091
+ data_ [shift :] = data [:- shift ]
1092
+ if not vertical :
1093
+ data_ [:shift ] = data [- shift :]
1094
+ else :
1095
+ data_ = data
1096
+ # Delta h
1097
+ d_h = convolve (data_ , weights = weight .reshape ((- 1 , 1 )), mode = mode )
1098
+ mask = convolve (int8 (data_ .mask ), weights = ones (weight .shape ).reshape ((- 1 , 1 )), mode = mode )
1099
+ d_h = ma .array (d_h , mask = mask != 0 )
1100
+
1101
+ # Delta d
1102
+ if vertical :
1103
+ d_h = d_h .T
1104
+ d = self .EARTH_RADIUS * 2 * pi / 360 * convolve (self .y_c , weight )
1105
+ else :
1106
+ if mode == 'wrap' :
1107
+ # Along x axis, we need to close
1108
+ # we will compute in two part
1109
+ x = self .x_c % 360
1110
+ d_degrees = convolve (x , weight , mode = mode )
1111
+ d_degrees_180 = convolve ((x + 180 ) % 360 - 180 , weight , mode = mode )
1112
+ # Arbitrary, to be sure to be far far away of bound
1113
+ m = (x < 50 ) + (x > 310 )
1114
+ d_degrees [m ] = d_degrees_180 [m ]
1115
+ d_degrees = d_degrees .reshape ((- 1 , 1 ))
1116
+ else :
1117
+ d_degrees = convolve (self .x_c , weight , mode = mode ).reshape ((- 1 , 1 ))
1118
+ d = self .EARTH_RADIUS * 2 * pi / 360 * d_degrees * cos (deg2rad (self .y_c ))
1119
+ if grad is None :
1120
+ # First Gradient
1121
+ grad = d_h / d
1122
+ else :
1123
+ # Fill hole
1124
+ grad [grad .mask ] = (d_h / d )[grad .mask ]
1125
+ return grad
1126
+
1127
+ def add_uv_lagerloef (self , grid_height , uname = 'u' , vname = 'v' , latmax = 5 ):
1128
+ self .add_uv (grid_height , uname , vname )
1129
+ logging .info ('Modified u/v with lagerloef 99 method abov %f' , latmax )
1130
+ data = self .grid (grid_height )
1131
+ # Divide by sideral day
1132
+ gof = sin (deg2rad (self .y_c )) * ones ((self .x_c .shape [0 ], 1 )) * 4. * pi / (23 * 3600 + 56 * 60 + 4.1 )
1133
+ with errstate (divide = 'ignore' ):
1134
+ gof = self .GRAVITY / (gof * ones ((self .x_c .shape [0 ], 1 )))
1135
+
1136
+ def add_uv2 (self , grid_height , uname = 'u2' , vname = 'v2' ):
1069
1137
"""Compute a u and v grid
1070
- """
1138
+ """
1139
+ logging .info ('Add u/v variable with stencil method' )
1071
1140
data = self .grid (grid_height )
1072
1141
h_dict = self .variables_description [grid_height ]
1073
- for variable in ('u' , 'v' ):
1142
+ for variable in (uname , vname ):
1074
1143
self .variables_description [variable ] = dict (
1075
1144
infos = h_dict ['infos' ].copy (),
1076
1145
attrs = h_dict ['attrs' ].copy (),
1077
1146
args = tuple ((variable , * h_dict ['args' ][1 :])),
1078
1147
kwargs = h_dict ['kwargs' ].copy (),
1079
1148
)
1149
+ if 'units' in self .variables_description [variable ]['attrs' ]:
1150
+ self .variables_description [variable ]['attrs' ]['units' ] += '/s'
1151
+ if 'long_name' in self .variables_description [variable ]['attrs' ]:
1152
+ self .variables_description [variable ]['attrs' ]['long_name' ] += ' gradient'
1153
+ # Divide by sideral day
1154
+ gof = sin (deg2rad (self .y_c )) * ones ((self .x_c .shape [0 ], 1 )) * 4. * pi / (23 * 3600 + 56 * 60 + 4.1 )
1155
+ with errstate (divide = 'ignore' ):
1156
+ gof = self .GRAVITY / (gof * ones ((self .x_c .shape [0 ], 1 )))
1080
1157
1158
+ # Compute v
1159
+ mode = 'wrap' if self .is_circular () else 'reflect'
1160
+ self .vars [vname ] = self .compute_grad (data , mode = mode ) * gof
1161
+ # Compute u
1162
+ self .vars [uname ] = - self .compute_grad (data , vertical = True ) * gof
1163
+
1164
+ def add_uv (self , grid_height , uname = 'u' , vname = 'v' ):
1165
+ """Compute a u and v grid
1166
+ """
1167
+ logging .info ('Add u/v variable with stencil method' )
1168
+ data = self .grid (grid_height )
1169
+ h_dict = self .variables_description [grid_height ]
1170
+ for variable in (uname , vname ):
1171
+ self .variables_description [variable ] = dict (
1172
+ infos = h_dict ['infos' ].copy (),
1173
+ attrs = h_dict ['attrs' ].copy (),
1174
+ args = tuple ((variable , * h_dict ['args' ][1 :])),
1175
+ kwargs = h_dict ['kwargs' ].copy (),
1176
+ )
1081
1177
self .variables_description [variable ]['attrs' ]['units' ] += '/s'
1082
1178
# Divide by sideral day
1083
1179
gof = sin (deg2rad (self .y_c )) * ones ((self .x_c .shape [0 ], 1 )) * 4. * pi / (23 * 3600 + 56 * 60 + 4.1 )
@@ -1094,7 +1190,7 @@ def add_uv(self, grid_height):
1094
1190
]
1095
1191
1096
1192
# Compute v
1097
- self .vars ['v' ] = None
1193
+ self .vars [vname ] = None
1098
1194
mode = 'wrap' if self .is_circular () else 'reflect'
1099
1195
for m_x in w :
1100
1196
if isinstance (m_x , tuple ):
@@ -1112,13 +1208,13 @@ def add_uv(self, grid_height):
1112
1208
d_x_degrees = (d_x_degrees + 180 ) % 360 - 180
1113
1209
d_x = self .EARTH_RADIUS * 2 * pi / 360 * d_x_degrees * cos (deg2rad (self .y_c ))
1114
1210
v = d_hx / d_x * gof
1115
- if self .vars ['v' ] is None :
1116
- self .vars ['v' ] = v
1211
+ if self .vars [vname ] is None :
1212
+ self .vars [vname ] = v
1117
1213
else :
1118
- self .vars ['v' ][self .vars ['v' ].mask ] = v [self .vars ['v' ].mask ]
1214
+ self .vars [vname ][self .vars [vname ].mask ] = v [self .vars [vname ].mask ]
1119
1215
1120
1216
# Compute u
1121
- self .vars ['u' ] = None
1217
+ self .vars [uname ] = None
1122
1218
for m_y in w :
1123
1219
if isinstance (m_y , tuple ):
1124
1220
shift , m_y = m_y
@@ -1134,19 +1230,17 @@ def add_uv(self, grid_height):
1134
1230
d_y = self .EARTH_RADIUS * 2 * pi / 360 * convolve (self .y_c , m_y )
1135
1231
1136
1232
u = - d_hy / d_y * gof
1137
- if self .vars ['u' ] is None :
1138
- self .vars ['u' ] = u
1233
+ if self .vars [uname ] is None :
1234
+ self .vars [uname ] = u
1139
1235
else :
1140
- self .vars ['u' ][self .vars ['u' ].mask ] = u [self .vars ['u' ].mask ]
1236
+ self .vars [uname ][self .vars [uname ].mask ] = u [self .vars [uname ].mask ]
1141
1237
1142
1238
def speed_coef_mean (self , contour ):
1143
1239
"""some nan can be compute over contour if we are near border,
1144
1240
something to explore
1145
1241
"""
1146
1242
return mean_on_regular_contour (
1147
- self .x_c , self .y_c ,
1148
- self ._speed_ev , self ._speed_ev .mask ,
1149
- contour .vertices )
1243
+ self .x_c , self .y_c , self ._speed_ev , self ._speed_ev .mask , contour .vertices , nan_remove = True )
1150
1244
1151
1245
def init_speed_coef (self , uname = 'u' , vname = 'v' ):
1152
1246
"""Draft
0 commit comments