26
26
# -----------------------------------------------------------------------------.
27
27
"""This module contains utilities for orbit processing."""
28
28
29
- import numpy as np
30
- import pandas as pd
29
+ import numpy as np
30
+ import pandas as pd
31
31
import xarray as xr
32
32
33
33
34
34
def adjust_short_sequences (arr , min_size ):
35
35
"""
36
36
Replace value of short sequences of consecutive identical values.
37
-
37
+
38
38
The function examines contiguous sequences of identical elements in the input
39
39
array.
40
40
If a sequence is shorter than `min_size`, its values are replaced with
41
- the value of the adjacent longer sequence, working outward from the first
41
+ the value of the adjacent longer sequence, working outward from the first
42
42
valid sequence.
43
-
43
+
44
44
Parameters
45
45
----------
46
46
arr : array_like
47
47
The input array of values.
48
48
min_size : int
49
- The minimum number of consecutive identical elements to not be modified.
49
+ The minimum number of consecutive identical elements to not be modified.
50
50
Shorter sequences will be replaced with the previous sequence value.
51
51
52
52
Returns
@@ -59,33 +59,33 @@ def adjust_short_sequences(arr, min_size):
59
59
arr = np .asarray (arr )
60
60
if arr .ndim != 1 :
61
61
raise ValueError ("Input must be a 1D array." )
62
-
62
+
63
63
# If array is empty or has only one element, return as is
64
64
if len (arr ) <= 1 :
65
65
return arr
66
-
66
+
67
67
# Create a copy to modify
68
68
result = arr .copy ()
69
-
69
+
70
70
# Find boundaries of sequences (where values change)
71
71
change_indices = np .where (np .diff (result ) != 0 )[0 ]
72
72
sequence_starts = np .concatenate (([0 ], change_indices + 1 ))
73
73
sequence_ends = np .concatenate ((change_indices + 1 , [len (result )]))
74
-
74
+
75
75
# Find the first valid sequence (length >= min_size)
76
76
first_valid_idx = None
77
77
valid_value = None
78
-
79
- for i , (start , end ) in enumerate (zip (sequence_starts , sequence_ends )):
78
+
79
+ for i , (start , end ) in enumerate (zip (sequence_starts , sequence_ends , strict = False )):
80
80
if end - start >= min_size :
81
81
first_valid_idx = i
82
82
valid_value = result [start ]
83
83
break
84
-
84
+
85
85
if first_valid_idx is None :
86
86
# If no valid sequence found, return original array
87
87
return result
88
-
88
+
89
89
# Process sequences after the first valid sequence
90
90
for i in range (first_valid_idx + 1 , len (sequence_starts )):
91
91
start = sequence_starts [i ]
@@ -94,15 +94,15 @@ def adjust_short_sequences(arr, min_size):
94
94
result [start :end ] = valid_value
95
95
else :
96
96
valid_value = result [start ]
97
-
97
+
98
98
# Process sequences before the first valid sequence (in reverse)
99
99
valid_value = result [sequence_starts [first_valid_idx ]] # Reset to first valid sequence value
100
100
for i in range (first_valid_idx - 1 , - 1 , - 1 ):
101
101
start = sequence_starts [i ]
102
102
end = sequence_ends [i ]
103
103
if end - start < min_size :
104
104
result [start :end ] = valid_value
105
-
105
+
106
106
# Return array with replaced values
107
107
return result
108
108
@@ -111,31 +111,31 @@ def get_orbit_direction(lats, n_tol=1):
111
111
"""
112
112
Infer the satellite orbit direction from latitude values.
113
113
114
- This function determines the orbit direction by computing the sign
115
- of the differences between consecutive latitude values.
116
-
117
- A positive sign (+1) indicates an ascending orbit (increasing latitude),
118
- while a negative sign (-1) indicates a descending orbit (decreasing latitude).
119
-
114
+ This function determines the orbit direction by computing the sign
115
+ of the differences between consecutive latitude values.
116
+
117
+ A positive sign (+1) indicates an ascending orbit (increasing latitude),
118
+ while a negative sign (-1) indicates a descending orbit (decreasing latitude).
119
+
120
120
Any zero differences are replaced by the nearest nonzero direction.
121
- Additionally, short sequences of direction changes - those lasting fewer
122
- than `n_tol` consecutive data points - are adjusted to reduce
121
+ Additionally, short sequences of direction changes - those lasting fewer
122
+ than `n_tol` consecutive data points - are adjusted to reduce
123
123
the influence of geolocation errors.
124
124
125
125
Parameters
126
126
----------
127
127
lats : array_like
128
128
1-dimensional array of latitude values corresponding to the satellite's orbit.
129
129
n_tol : int, optional
130
- The minimum number of consecutive data points required to confirm a change
130
+ The minimum number of consecutive data points required to confirm a change
131
131
in direction.
132
132
Sequences shorter than this threshold will be smoothed. Default is 1.
133
133
134
134
Returns
135
135
-------
136
136
numpy.ndarray
137
- A 1-dimensional array of the same length as `lats` containing
138
- the inferred orbit direction.
137
+ A 1-dimensional array of the same length as `lats` containing
138
+ the inferred orbit direction.
139
139
A value of +1 denotes an ascending orbit and -1 denotes a descending orbit.
140
140
141
141
Examples
@@ -147,11 +147,11 @@ def get_orbit_direction(lats, n_tol=1):
147
147
# Get direction (1 for ascending, -1 for descending)
148
148
directions = np .sign (np .diff (lats ))
149
149
directions = np .append (directions [0 ], directions ) # Include starting point
150
- # Set 0 to NaN and infill values
150
+ # Set 0 to NaN and infill values
151
151
directions = directions .astype (float )
152
152
directions [directions == 0 ] = np .nan
153
153
if np .all (np .isnan (directions )):
154
- raise ValueError ("Invalid orbit." )
154
+ raise ValueError ("Invalid orbit." )
155
155
directions = pd .Series (directions ).ffill ().bfill ().to_numpy ()
156
156
# Remove short consecutive changes in direction to account for geolocation errors
157
157
directions = adjust_short_sequences (directions , min_size = n_tol )
@@ -162,19 +162,19 @@ def get_orbit_direction(lats, n_tol=1):
162
162
163
163
def get_orbit_mode (ds ):
164
164
# Retrieve latitude coordinates
165
- if "SClatitude" in ds :
165
+ if "SClatitude" in ds :
166
166
lats = ds ["SClatitude" ]
167
- elif "scLat" in ds :
167
+ elif "scLat" in ds :
168
168
lats = ds ["scLat" ]
169
- else :
169
+ else :
170
170
# Define cross_track idx defining the orbit coordinates
171
- # - TODO: conically scanning vs cross-track scanning
171
+ # - TODO: conically scanning vs cross-track scanning
172
172
# - TODO: Use SClatitude ?
173
- # Remove invalid outer cross-track_id if selecting 0 or -1
173
+ # Remove invalid outer cross-track_id if selecting 0 or -1
174
174
# from gpm.visualization.orbit import remove_invalid_outer_cross_track
175
175
# ds, _ = remove_invalid_outer_cross_track(ds)
176
176
idx = int (ds ["cross_track" ].shape [0 ] / 2 )
177
177
lats = ds ["lat" ].isel ({"cross_track" : idx }).to_numpy ()
178
178
directions = get_orbit_direction (lats , n_tol = 10 )
179
179
orbit_mode = xr .DataArray (directions , dims = ["along_track" ])
180
- return orbit_mode
180
+ return orbit_mode
0 commit comments