def convert_latlon_arr(in_lat, in_lon, height, dtime, method_code="G2A"): """Converts between geomagnetic coordinates and AACGM coordinates. Parameters ---------- in_lat : (np.ndarray or list or float) Input latitude in degrees N (method_code specifies type of latitude) in_lon : (np.ndarray or list or float) Input longitude in degrees E (method_code specifies type of longitude) height : (np.ndarray or list or float) Altitude above the surface of the earth in km dtime : (datetime) Single datetime object for magnetic field method_code : (int or str) Bit code or string denoting which type(s) of conversion to perform G2A - geographic (geodetic) to AACGM-v2 A2G - AACGM-v2 to geographic (geodetic) TRACE - use field-line tracing, not coefficients ALLOWTRACE - use trace only above 2000 km BADIDEA - use coefficients above 2000 km GEOCENTRIC - assume inputs are geocentric w/ RE=6371.2 (default = "G2A") Returns ------- out_lat : (np.ndarray) Output latitudes in degrees N out_lon : (np.ndarray) Output longitudes in degrees E out_r : (np.ndarray) Geocentric radial distance (R_Earth) or altitude above the surface of the Earth (km) Raises ------ ValueError if input is incorrect RuntimeError if unable to set AACGMV2 datetime Notes ----- At least one of in_lat, in_lon, and height must be a list or array. If errors are encountered, NaN or Inf will be included in the input so that all successful calculations are returned. To select only good values use a function like `np.isfinite`. Multi-dimensional arrays are not allowed. """ # Recast the data as numpy arrays in_lat = np.array(in_lat) in_lon = np.array(in_lon) height = np.array(height) # If one or two of these elements is a float, int, or single element array, # create an array equal to the length of the longest input test_array = np.array( [len(in_lat.shape), len(in_lon.shape), len(height.shape)]) if test_array.max() > 1: raise ValueError("unable to process multi-dimensional arrays") else: if test_array.max() == 0: aacgmv2.logger.info("".join([ "for a single location, consider ", "using convert_latlon or ", "get_aacgm_coord" ])) in_lat = np.array([in_lat]) in_lon = np.array([in_lon]) height = np.array([height]) else: max_len = max([ len(arr) for i, arr in enumerate([in_lat, in_lon, height]) if test_array[i] > 0 ]) if not test_array[0] or (len(in_lat) == 1 and max_len > 1): in_lat = np.full(shape=(max_len, ), fill_value=in_lat) if not test_array[1] or (len(in_lon) == 1 and max_len > 1): in_lon = np.full(shape=(max_len, ), fill_value=in_lon) if not test_array[2] or (len(height) == 1 and max_len > 1): height = np.full(shape=(max_len, ), fill_value=height) # Ensure that lat, lon, and height are the same length or if the lengths # differ that the different ones contain only a single value if not (in_lat.shape == in_lon.shape and in_lat.shape == height.shape): raise ValueError('lat, lon, and height arrays are mismatched') # Test time dtime = test_time(dtime) # Initialise output lat_out = np.full(shape=in_lat.shape, fill_value=np.nan) lon_out = np.full(shape=in_lon.shape, fill_value=np.nan) r_out = np.full(shape=height.shape, fill_value=np.nan) # Test and set the conversion method code try: bit_code = convert_str_to_bit(method_code.upper()) except AttributeError: bit_code = method_code if not isinstance(bit_code, int): raise ValueError("unknown method code {:}".format(method_code)) # Test height if not test_height(np.nanmax(height), bit_code): return lat_out, lon_out, r_out # Test latitude range if np.abs(in_lat).max() > 90.0: if np.abs(in_lat).max() > 90.1: raise ValueError('unrealistic latitude') in_lat = np.clip(in_lat, -90.0, 90.0) # Constrain longitudes between -180 and 180 in_lon = ((in_lon + 180.0) % 360.0) - 180.0 # Set current date and time try: c_aacgmv2.set_datetime(dtime.year, dtime.month, dtime.day, dtime.hour, dtime.minute, dtime.second) except (TypeError, RuntimeError) as err: raise RuntimeError("cannot set time for {:}: {:}".format(dtime, err)) try: lat_out, lon_out, r_out, bad_ind = c_aacgmv2.convert_arr( list(in_lat), list(in_lon), list(height), bit_code) # Cast the output as numpy arrays or masks lat_out = np.array(lat_out) lon_out = np.array(lon_out) r_out = np.array(r_out) bad_ind = np.array(bad_ind) >= 0 # Replace any bad indices with NaN, casting output as numpy arrays if np.any(bad_ind): lat_out[bad_ind] = np.nan lon_out[bad_ind] = np.nan r_out[bad_ind] = np.nan except SystemError as serr: aacgmv2.logger.warning('C Error encountered: {:}'.format(serr)) return lat_out, lon_out, r_out
def convert_latlon(in_lat, in_lon, height, dtime, method_code="G2A"): """Converts between geomagnetic coordinates and AACGM coordinates Parameters ---------- in_lat : (float) Input latitude in degrees N (code specifies type of latitude) in_lon : (float) Input longitude in degrees E (code specifies type of longitude) height : (float) Altitude above the surface of the earth in km dtime : (datetime) Datetime for magnetic field method_code : (str or int) Bit code or string denoting which type(s) of conversion to perform G2A - geographic (geodetic) to AACGM-v2 A2G - AACGM-v2 to geographic (geodetic) TRACE - use field-line tracing, not coefficients ALLOWTRACE - use trace only above 2000 km BADIDEA - use coefficients above 2000 km GEOCENTRIC - assume inputs are geocentric w/ RE=6371.2 (default is "G2A") Returns ------- out_lat : (float) Output latitude in degrees N out_lon : (float) Output longitude in degrees E out_r : (float) Geocentric radial distance (R_Earth) or altitude above the surface of the Earth (km) Raises ------ ValueError if input is incorrect RuntimeError if unable to set AACGMV2 datetime """ # Test time dtime = test_time(dtime) # Initialise output lat_out = np.nan lon_out = np.nan r_out = np.nan # Set the coordinate coversion method code in bits try: bit_code = convert_str_to_bit(method_code.upper()) except AttributeError: bit_code = method_code if not isinstance(bit_code, int): raise ValueError("unknown method code {:}".format(method_code)) # Test height that may or may not cause failure if not test_height(height, bit_code): return lat_out, lon_out, r_out # Test latitude range if abs(in_lat) > 90.0: # Allow latitudes with a small deviation from the maximum # (+/- 90 degrees) to be set to 90 if abs(in_lat) > 90.1: raise ValueError('unrealistic latitude') in_lat = np.sign(in_lat) * 90.0 # Constrain longitudes between -180 and 180 in_lon = ((in_lon + 180.0) % 360.0) - 180.0 # Set current date and time try: c_aacgmv2.set_datetime(dtime.year, dtime.month, dtime.day, dtime.hour, dtime.minute, dtime.second) except (TypeError, RuntimeError) as err: raise RuntimeError("cannot set time for {:}: {:}".format(dtime, err)) # convert location try: lat_out, lon_out, r_out = c_aacgmv2.convert(in_lat, in_lon, height, bit_code) except Exception: err = sys.exc_info()[0] estr = "unable to perform conversion at {:.1f},".format(in_lat) estr = "{:s}{:.1f} {:.1f} km, {:} ".format(estr, in_lon, height, dtime) estr = "{:s}using method {:}: {:}".format(estr, bit_code, err) aacgmv2.logger.warning(estr) pass return lat_out, lon_out, r_out
def convert_latlon(in_lat, in_lon, height, dtime, code="G2A"): """Converts between geomagnetic coordinates and AACGM coordinates Parameters ------------ in_lat : (float) Input latitude in degrees N (code specifies type of latitude) in_lon : (float) Input longitude in degrees E (code specifies type of longitude) height : (float) Altitude above the surface of the earth in km dtime : (datetime) Datetime for magnetic field code : (str or int) Bit code or string denoting which type(s) of conversion to perform G2A - geographic (geodetic) to AACGM-v2 A2G - AACGM-v2 to geographic (geodetic) TRACE - use field-line tracing, not coefficients ALLOWTRACE - use trace only above 2000 km BADIDEA - use coefficients above 2000 km GEOCENTRIC - assume inputs are geocentric w/ RE=6371.2 (default is "G2A") Returns ------- out_lat : (float) Output latitude in degrees N out_lon : (float) Output longitude in degrees E out_r : (float) Geocentric radial distance (R_Earth) or altitude above the surface of the Earth (km) """ import aacgmv2._aacgmv2 as c_aacgmv2 # Test time if isinstance(dtime, dt.date): dtime = dt.datetime.combine(dtime, dt.time(0)) assert isinstance(dtime, dt.datetime), \ logging.error('time must be specified as datetime object') # Test height if height < 0: logging.warn('conversion not intended for altitudes < 0 km') # Initialise output lat_out = np.nan lon_out = np.nan r_out = np.nan # Test code try: code = code.upper() if (height > 2000 and code.find("TRACE") < 0 and code.find("ALLOWTRACE") < 0 and code.find("BADIDEA") < 0): estr = 'coefficients are not valid for altitudes above 2000 km. You' estr += ' must either use field-line tracing (trace=True ' estr += 'or allowtrace=True) or indicate you know this ' estr += 'is a bad idea' logging.error(estr) return lat_out, lon_out, r_out # make flag bit_code = convert_str_to_bit(code) except AttributeError: bit_code = code assert isinstance(bit_code, int), \ logging.error("unknown code {:}".format(bit_code)) # Test latitude range if abs(in_lat) > 90.0: assert abs(in_lat) <= 90.1, logging.error('unrealistic latitude') in_lat = np.sign(in_lat) * 90.0 # Constrain longitudes between -180 and 180 in_lon = ((in_lon + 180.0) % 360.0) - 180.0 # Set current date and time try: c_aacgmv2.set_datetime(dtime.year, dtime.month, dtime.day, dtime.hour, dtime.minute, dtime.second) except: raise RuntimeError("unable to set time for {:}".format(dtime)) # convert location try: lat_out, lon_out, r_out = c_aacgmv2.convert(in_lat, in_lon, height, bit_code) except: pass return lat_out, lon_out, r_out
def convert_latlon_arr(in_lat, in_lon, height, dtime, method_code="G2A", **kwargs): """Converts between geomagnetic coordinates and AACGM coordinates. Parameters ------------ in_lat : (np.ndarray or list or float) Input latitude in degrees N (method_code specifies type of latitude) in_lon : (np.ndarray or list or float) Input longitude in degrees E (method_code specifies type of longitude) height : (np.ndarray or list or float) Altitude above the surface of the earth in km dtime : (datetime) Single datetime object for magnetic field method_code : (int or str) Bit code or string denoting which type(s) of conversion to perform G2A - geographic (geodetic) to AACGM-v2 A2G - AACGM-v2 to geographic (geodetic) TRACE - use field-line tracing, not coefficients ALLOWTRACE - use trace only above 2000 km BADIDEA - use coefficients above 2000 km GEOCENTRIC - assume inputs are geocentric w/ RE=6371.2 (default = "G2A") Returns ------- out_lat : (np.ndarray) Output latitudes in degrees N out_lon : (np.ndarray) Output longitudes in degrees E out_r : (np.ndarray) Geocentric radial distance (R_Earth) or altitude above the surface of the Earth (km) Raises ------ ValueError if input is incorrect TypeError or RuntimeError if unable to set AACGMV2 datetime Notes ------- At least one of in_lat, in_lon, and height must be a list or array. If errors are encountered, NaN or Inf will be included in the input so that all successful calculations are returned. To select only good values use a function like `np.isfinite`. Multi-dimensional arrays are not allowed. """ # Handle deprecated keyword arguments for kw in kwargs.keys(): if kw not in ['code']: raise TypeError('unexpected keyword argument [{:s}]'.format(kw)) else: method_code = kwargs[kw] warnings.warn("".join([ "Deprecated keyword argument 'code' will be", " removed in version 2.6.1, please update ", "your routine to use 'method_code'" ]), category=FutureWarning) # Recast the data as numpy arrays in_lat = np.array(in_lat) in_lon = np.array(in_lon) height = np.array(height) # If one or two of these elements is a float or int, create an array test_array = np.array( [len(in_lat.shape), len(in_lon.shape), len(height.shape)]) if test_array.max() > 1: raise ValueError("unable to process multi-dimensional arrays") if test_array.min() == 0: if test_array.max() == 0: aacgmv2.logger.info("".join([ "for a single location, consider ", "using convert_latlon or get_aacgm_coord" ])) in_lat = np.array([in_lat]) in_lon = np.array([in_lon]) height = np.array([height]) else: imax = test_array.argmax() max_shape = in_lat.shape if imax == 0 else (in_lon.shape \ if imax == 1 else height.shape) if not test_array[0]: in_lat = np.full(shape=max_shape, fill_value=in_lat) if not test_array[1]: in_lon = np.full(shape=max_shape, fill_value=in_lon) if not test_array[2]: height = np.full(shape=max_shape, fill_value=height) # Ensure that lat, lon, and height are the same length or if the lengths # differ that the different ones contain only a single value if not (in_lat.shape == in_lon.shape and in_lat.shape == height.shape): shape_dict = { 'lat': in_lat.shape, 'lon': in_lon.shape, 'height': height.shape } ulen = np.unique(shape_dict.values()) array_key = [ kk for i, kk in enumerate(shape_dict.keys()) if shape_dict[kk] != (1, ) ] if len(array_key) == 3: raise ValueError('lat, lon, and height arrays are mismatched') elif len(array_key) == 2: if shape_dict[array_key[0]] == shape_dict[array_dict[1]]: raise ValueError('{:s} and {:s} arrays are mismatched'.format(\ *array_key)) # Test time dtime = test_time(dtime) # Initialise output lat_out = np.full(shape=in_lat.shape, fill_value=np.nan) lon_out = np.full(shape=in_lon.shape, fill_value=np.nan) r_out = np.full(shape=height.shape, fill_value=np.nan) # Test and set the conversion method code try: bit_code = convert_str_to_bit(method_code.upper()) except AttributeError: bit_code = method_code if not isinstance(bit_code, int): raise ValueError("unknown method code {:}".format(method_code)) # Test height if not test_height(np.nanmax(height), bit_code): return lat_out, lon_out, r_out # Test latitude range if np.abs(in_lat).max() > 90.0: if np.abs(in_lat).max() > 90.1: raise ValueError('unrealistic latitude') in_lat = np.clip(in_lat, -90.0, 90.0) # Constrain longitudes between -180 and 180 in_lon = ((in_lon + 180.0) % 360.0) - 180.0 # Set current date and time try: c_aacgmv2.set_datetime(dtime.year, dtime.month, dtime.day, dtime.hour, dtime.minute, dtime.second) except TypeError as terr: raise TypeError("unable to set time for {:}: {:}".format(dtime, terr)) except RuntimeError as rerr: raise RuntimeError("unable to set time for {:}: {:}".format( dtime, rerr)) try: lat_out, lon_out, r_out, bad_ind = c_aacgmv2.convert_arr( list(in_lat), list(in_lon), list(height), bit_code) # Cast the output as numpy arrays or masks lat_out = np.array(lat_out) lon_out = np.array(lon_out) r_out = np.array(r_out) bad_ind = np.array(bad_ind) >= 0 # Replace any bad indices with NaN, casting output as numpy arrays if np.any(bad_ind): lat_out[bad_ind] = np.nan lon_out[bad_ind] = np.nan r_out[bad_ind] = np.nan except SystemError as serr: aacgmv2.logger.warning('C Error encountered: {:}'.format(serr)) return lat_out, lon_out, r_out
def convert_latlon_arr(in_lat, in_lon, height, dtime, code="G2A"): """Converts between geomagnetic coordinates and AACGM coordinates. Parameters ------------ in_lat : (np.ndarray or list or float) Input latitude in degrees N (code specifies type of latitude) in_lon : (np.ndarray or list or float) Input longitude in degrees E (code specifies type of longitude) height : (np.ndarray or list or float) Altitude above the surface of the earth in km dtime : (datetime) Single datetime object for magnetic field code : (int or str) Bit code or string denoting which type(s) of conversion to perform G2A - geographic (geodetic) to AACGM-v2 A2G - AACGM-v2 to geographic (geodetic) TRACE - use field-line tracing, not coefficients ALLOWTRACE - use trace only above 2000 km BADIDEA - use coefficients above 2000 km GEOCENTRIC - assume inputs are geocentric w/ RE=6371.2 (default = "G2A") Returns ------- out_lat : (np.ndarray) Output latitudes in degrees N out_lon : (np.ndarray) Output longitudes in degrees E out_r : (np.ndarray) Geocentric radial distance (R_Earth) or altitude above the surface of the Earth (km) Notes ------- At least one of in_lat, in_lon, and height must be a list or array. """ import aacgmv2._aacgmv2 as c_aacgmv2 # If a list was entered instead of a numpy array, recast it here if isinstance(in_lat, list): in_lat = np.array(in_lat) if isinstance(in_lon, list): in_lon = np.array(in_lon) if isinstance(height, list): height = np.array(height) # If one or two of these elements is a float or int, create an array test_array = np.array([ hasattr(in_lat, "shape"), hasattr(in_lon, "shape"), hasattr(height, "shape") ]) if not test_array.all(): if test_array.any(): arr_shape = in_lat.shape if test_array.argmax() == 0 else \ (in_lon.shape if test_array.argmax() == 1 else height.shape) if not test_array[0]: in_lat = np.ones(shape=arr_shape, dtype=float) * in_lat if not test_array[1]: in_lon = np.ones(shape=arr_shape, dtype=float) * in_lon if not test_array[2]: height = np.ones(shape=arr_shape, dtype=float) * height else: logging.info( "for a single location, consider using convert_latlon") in_lat = np.array([in_lat]) in_lon = np.array([in_lon]) height = np.array([height]) # Ensure that lat, lon, and height are the same length or if the lengths # differ that the different ones contain only a single value if not (in_lat.shape == in_lon.shape and in_lat.shape == height.shape): ulen = np.unique([in_lat.shape, in_lon.shape, height.shape]) if ulen.min() != (1, ): logging.error("mismatched input arrays") return None, None, None # Test time if isinstance(dtime, dt.date): dtime = dt.datetime.combine(dtime, dt.time(0)) assert isinstance(dtime, dt.datetime), \ logging.error('time must be specified as datetime object') # Test height if np.min(height) < 0: logging.warn('conversion not intended for altitudes < 0 km') # Initialise output lat_out = np.empty(shape=in_lat.shape, dtype=float) * np.nan lon_out = np.empty(shape=in_lon.shape, dtype=float) * np.nan r_out = np.empty(shape=height.shape, dtype=float) * np.nan # Test code try: code = code.upper() if (np.nanmax(height) > 2000 and code.find("TRACE") < 0 and code.find("ALLOWTRACE") < 0 and code.find("BADIDEA") < 0): estr = 'coefficients are not valid for altitudes above 2000 km. You' estr += ' must either use field-line tracing (trace=True ' estr += 'or allowtrace=True) or indicate you know this ' estr += 'is a bad idea' logging.error(estr) return lat_out, lon_out, r_out # make flag bit_code = convert_str_to_bit(code) except AttributeError: bit_code = code assert isinstance(bit_code, int), \ logging.error("unknown code {:}".format(bit_code)) # Test latitude range if np.abs(in_lat).max() > 90.0: assert np.abs(in_lat).max() <= 90.1, \ logging.error('unrealistic latitude') in_lat = np.clip(in_lat, -90.0, 90.0) # Constrain longitudes between -180 and 180 in_lon = ((in_lon + 180.0) % 360.0) - 180.0 # Set current date and time try: c_aacgmv2.set_datetime(dtime.year, dtime.month, dtime.day, dtime.hour, dtime.minute, dtime.second) except: raise RuntimeError("unable to set time for {:}".format(dtime)) # Vectorise the AACGM code convert_vectorised = np.vectorize(c_aacgmv2.convert) # convert try: lat_out, lon_out, r_out = convert_vectorised(in_lat, in_lon, height, bit_code) except: pass return lat_out, lon_out, r_out