| Conditions | 26 |
| Total Lines | 135 |
| Code Lines | 68 |
| Lines | 0 |
| Ratio | 0 % |
| Changes | 0 | ||
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
Complex classes like aacgmv2.wrapper.convert_latlon_arr() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
| 1 | # -*- coding: utf-8 -*- |
||
| 114 | def convert_latlon_arr(in_lat, in_lon, height, dtime, code="G2A"): |
||
| 115 | """Converts between geomagnetic coordinates and AACGM coordinates. At least |
||
| 116 | one of in_lat, in_lon, and height must be a list or array |
||
| 117 | |||
| 118 | Parameters |
||
| 119 | ------------ |
||
| 120 | in_lat : (np.ndarray or list or float) |
||
| 121 | Input latitude in degrees N (code specifies type of latitude) |
||
| 122 | in_lon : (np.ndarray or list or float) |
||
| 123 | Input longitude in degrees E (code specifies type of longitude) |
||
| 124 | height : (np.ndarray or list or float) |
||
| 125 | Altitude above the surface of the earth in km |
||
| 126 | dtime : (datetime) |
||
| 127 | Single datetime object for magnetic field |
||
| 128 | code : (int or str) |
||
| 129 | Bit code or string denoting which type(s) of conversion to perform |
||
| 130 | G2A - geographic (geodetic) to AACGM-v2 |
||
| 131 | A2G - AACGM-v2 to geographic (geodetic) |
||
| 132 | TRACE - use field-line tracing, not coefficients |
||
| 133 | ALLOWTRACE - use trace only above 2000 km |
||
| 134 | BADIDEA - use coefficients above 2000 km |
||
| 135 | GEOCENTRIC - assume inputs are geocentric w/ RE=6371.2 |
||
| 136 | (default = "G2A") |
||
| 137 | |||
| 138 | Returns |
||
| 139 | ------- |
||
| 140 | out_lat : (np.ndarray) |
||
| 141 | Output latitudes in degrees N |
||
| 142 | out_lon : (np.ndarray) |
||
| 143 | Output longitudes in degrees E |
||
| 144 | out_r : (np.ndarray) |
||
| 145 | Geocentric radial distance (R_Earth) or altitude above the surface of |
||
| 146 | the Earth (km) |
||
| 147 | """ |
||
| 148 | import aacgmv2._aacgmv2 as c_aacgmv2 |
||
| 149 | |||
| 150 | # If a list was entered instead of a numpy array, recast it here |
||
| 151 | if isinstance(in_lat, list): |
||
| 152 | in_lat = np.array(in_lat) |
||
| 153 | |||
| 154 | if isinstance(in_lon, list): |
||
| 155 | in_lon = np.array(in_lon) |
||
| 156 | |||
| 157 | if isinstance(height, list): |
||
| 158 | height = np.array(height) |
||
| 159 | |||
| 160 | # If one or two of these elements is a float or int, create an array |
||
| 161 | test_array = np.array([hasattr(in_lat, "shape"), hasattr(in_lon, "shape"), |
||
| 162 | hasattr(height, "shape")]) |
||
| 163 | if not test_array.all(): |
||
| 164 | if test_array.any(): |
||
| 165 | arr_shape = in_lat.shape if test_array.argmax() == 0 else \ |
||
| 166 | (in_lon.shape if test_array.argmax() == 1 else |
||
| 167 | height.shape) |
||
| 168 | if not test_array[0]: |
||
| 169 | in_lat = np.ones(shape=arr_shape, dtype=float) * in_lat |
||
| 170 | if not test_array[1]: |
||
| 171 | in_lon = np.ones(shape=arr_shape, dtype=float) * in_lon |
||
| 172 | if not test_array[2]: |
||
| 173 | height = np.ones(shape=arr_shape, dtype=float) * height |
||
| 174 | else: |
||
| 175 | logging.info("for a single location, consider using convert_latlon") |
||
| 176 | in_lat = np.array([in_lat]) |
||
| 177 | in_lon = np.array([in_lon]) |
||
| 178 | height = np.array([height]) |
||
| 179 | |||
| 180 | # Ensure that lat, lon, and height are the same length or if the lengths |
||
| 181 | # differ that the different ones contain only a single value |
||
| 182 | if not (in_lat.shape == in_lon.shape and in_lat.shape == height.shape): |
||
| 183 | ulen = np.unique([in_lat.shape, in_lon.shape, height.shape]) |
||
| 184 | if ulen.min() != (1,): |
||
| 185 | logging.error("mismatched input arrays") |
||
| 186 | return None, None, None |
||
| 187 | |||
| 188 | # Test time |
||
| 189 | if isinstance(dtime, dt.date): |
||
| 190 | dtime = dt.datetime.combine(dtime, dt.time(0)) |
||
| 191 | |||
| 192 | assert isinstance(dtime, dt.datetime), \ |
||
| 193 | logging.error('time must be specified as datetime object') |
||
| 194 | |||
| 195 | # Test height |
||
| 196 | if np.min(height) < 0: |
||
| 197 | logging.warn('conversion not intended for altitudes < 0 km') |
||
| 198 | |||
| 199 | # Initialise output |
||
| 200 | lat_out = np.empty(shape=in_lat.shape, dtype=float) * np.nan |
||
| 201 | lon_out = np.empty(shape=in_lon.shape, dtype=float) * np.nan |
||
| 202 | r_out = np.empty(shape=height.shape, dtype=float) * np.nan |
||
| 203 | |||
| 204 | # Test code |
||
| 205 | try: |
||
| 206 | code = code.upper() |
||
| 207 | |||
| 208 | if(np.nanmax(height) > 2000 and code.find("TRACE") < 0 and |
||
| 209 | code.find("ALLOWTRACE") < 0 and code.find("BADIDEA")): |
||
| 210 | estr = 'coefficients are not valid for altitudes above 2000 km. You' |
||
| 211 | estr += ' must either use field-line tracing (trace=True ' |
||
| 212 | estr += 'or allowtrace=True) or indicate you know this ' |
||
| 213 | estr += 'is a bad idea' |
||
| 214 | logging.error(estr) |
||
| 215 | return lat_out, lon_out, r_out |
||
| 216 | |||
| 217 | # make flag |
||
| 218 | bit_code = convert_str_to_bit(code) |
||
| 219 | except AttributeError: |
||
| 220 | bit_code = code |
||
| 221 | |||
| 222 | assert isinstance(bit_code, int), \ |
||
| 223 | logging.error("unknown code {:}".format(bit_code)) |
||
| 224 | |||
| 225 | # Test latitude range |
||
| 226 | if np.abs(in_lat).max() > 90.0: |
||
| 227 | assert np.abs(in_lat).max() <= 90.1, \ |
||
| 228 | logging.error('unrealistic latitude') |
||
| 229 | in_lat = np.clip(in_lat, -90.0, 90.0) |
||
| 230 | |||
| 231 | # Constrain longitudes between -180 and 180 |
||
| 232 | in_lon = ((in_lon + 180.0) % 360.0) - 180.0 |
||
| 233 | |||
| 234 | # Set current date and time |
||
| 235 | c_aacgmv2.set_datetime(dtime.year, dtime.month, dtime.day, dtime.hour, |
||
| 236 | dtime.minute, dtime.second) |
||
| 237 | |||
| 238 | # Vectorise the AACGM code |
||
| 239 | convert_vectorised = np.vectorize(c_aacgmv2.convert) |
||
| 240 | |||
| 241 | # convert |
||
| 242 | try: |
||
| 243 | lat_out, lon_out, r_out = convert_vectorised(in_lat, in_lon, height, |
||
| 244 | bit_code) |
||
| 245 | except: |
||
| 246 | pass |
||
| 247 | |||
| 248 | return lat_out, lon_out, r_out |
||
| 249 | |||
| 464 |