From 6f7f7dfc70276e391b3c045fd50073576ca04df6 Mon Sep 17 00:00:00 2001 From: Jeff Klenzing Date: Wed, 13 Feb 2019 08:09:37 -0500 Subject: [PATCH 01/50] utils restructure - #124 --- pysat/utils/coords.py | 351 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 351 insertions(+) create mode 100644 pysat/utils/coords.py diff --git a/pysat/utils/coords.py b/pysat/utils/coords.py new file mode 100644 index 0000000..a3d3f7f --- /dev/null +++ b/pysat/utils/coords.py @@ -0,0 +1,351 @@ +""" +pysat.coords - coordinate transformations for pysat +========================================= + +pysat.coords contains a number of coordinate-transformation +functions used throughout the pysat package. +""" + +import numpy as np + + +def geodetic_to_geocentric(lat_in, lon_in=None, inverse=False): + """Converts position from geodetic to geocentric or vice-versa. + + Parameters + ---------- + lat_in : float + latitude in degrees. + lon_in : float or NoneType + longitude in degrees. Remains unchanged, so does not need to be + included. (default=None) + inverse : bool + False for geodetic to geocentric, True for geocentric to geodetic. + (default=False) + + Returns + ------- + lat_out : float + latitude [degree] (geocentric/detic if inverse=False/True) + lon_out : float or NoneType + longitude [degree] (geocentric/detic if inverse=False/True) + rad_earth : float + Earth radius [km] (geocentric/detic if inverse=False/True) + + Notes + ----- + Uses WGS-84 values + + References + ---------- + Based on J.M. Ruohoniemi's geopack and R.J. Barnes radar.pro + + """ + rad_eq = 6378.1370 # WGS-84 semi-major axis + flat = 1.0 / 298.257223563 # WGS-84 flattening + rad_pol = rad_eq * (1.0 - flat) # WGS-84 semi-minor axis + + # The ratio between the semi-major and minor axis is used several times + rad_ratio_sq = (rad_eq / rad_pol)**2 + + # Calculate the square of the second eccentricity (e') + eprime_sq = rad_ratio_sq - 1.0 + + # Calculate the tangent of the input latitude + tan_in = np.tan(np.radians(lat_in)) + + # If converting from geodetic to geocentric, take the inverse of the + # radius ratio + if not inverse: + rad_ratio_sq = 1.0 / rad_ratio_sq + + # Calculate the output latitude + lat_out = np.degrees(np.arctan(rad_ratio_sq * tan_in)) + + # Calculate the Earth radius at this latitude + rad_earth = rad_eq / np.sqrt(1.0 + eprime_sq * + np.sin(np.radians(lat_out))**2) + + # longitude remains unchanged + lon_out = lon_in + + return lat_out, lon_out, rad_earth + + +def geodetic_to_geocentric_horizontal(lat_in, lon_in, az_in, el_in, + inverse=False): + """Converts from local horizontal coordinates in a geodetic system to local + horizontal coordinates in a geocentric system + + Parameters + ---------- + lat_in : float + latitude in degrees of the local horizontal coordinate system center + lon_in : float + longitude in degrees of the local horizontal coordinate system center + az_in : float + azimuth in degrees within the local horizontal coordinate system + el_in : float + elevation in degrees within the local horizontal coordinate system + inverse : bool + False for geodetic to geocentric, True for inverse (default=False) + + Returns + ------- + lat_out : float + latitude in degrees of the converted horizontal coordinate system + center + lon_out : float + longitude in degrees of the converted horizontal coordinate system + center + rad_earth : float + Earth radius in km at the geocentric/detic (False/True) location + az_out : float + azimuth in degrees of the converted horizontal coordinate system + el_out : float + elevation in degrees of the converted horizontal coordinate system + + References + ---------- + Based on J.M. Ruohoniemi's geopack and R.J. Barnes radar.pro + + """ + az = np.radians(az_in) + el = np.radians(el_in) + + # Transform the location of the local horizontal coordinate system center + lat_out, lon_out, rad_earth = geodetic_to_geocentric(lat_in, lon_in, + inverse=inverse) + + # Calcualte the deviation from vertical in radians + dev_vert = np.radians(lat_in - lat_out) + + # Calculate cartesian coordinated in local system + x_local = np.cos(el) * np.sin(az) + y_local = np.cos(el) * np.cos(az) + z_local = np.sin(el) + + # Now rotate system about the x axis to align local vertical vector + # with Earth radial vector + x_out = x_local + y_out = y_local * np.cos(dev_vert) + z_local * np.sin(dev_vert) + z_out = -y_local * np.sin(dev_vert) + z_local * np.cos(dev_vert) + + # Transform the azimuth and elevation angles + az_out = np.degrees(np.arctan2(x_out, y_out)) + el_out = np.degrees(np.arctan(z_out / np.sqrt(x_out**2 + y_out**2))) + + return lat_out, lon_out, rad_earth, az_out, el_out + + +def spherical_to_cartesian(az_in, el_in, r_in, inverse=False): + """Convert a position from spherical to cartesian, or vice-versa + + Parameters + ---------- + az_in : float + azimuth/longitude in degrees or cartesian x in km (inverse=False/True) + el_in : float + elevation/latitude in degrees or cartesian y in km (inverse=False/True) + r_in : float + distance from origin in km or cartesian z in km (inverse=False/True) + inverse : boolian + False to go from spherical to cartesian and True for the inverse + + Returns + ------- + x_out : float + cartesian x in km or azimuth/longitude in degrees (inverse=False/True) + y_out : float + cartesian y in km or elevation/latitude in degrees (inverse=False/True) + z_out : float + cartesian z in km or distance from origin in km (inverse=False/True) + + Notes + ------ + This transform is the same for local or global spherical/cartesian + transformations. + + Returns elevation angle (angle from the xy plane) rather than zenith angle + (angle from the z-axis) + + """ + + if inverse: + # Cartesian to Spherical + xy_sq = az_in**2 + el_in**2 + z_out = np.sqrt(xy_sq + r_in**2) # This is r + y_out = np.degrees(np.arctan2(np.sqrt(xy_sq), r_in)) # This is zenith + y_out = 90.0 - y_out # This is the elevation + x_out = np.degrees(np.arctan2(el_in, az_in)) # This is azimuth + else: + # Spherical coordinate system uses zenith angle (degrees from the + # z-axis) and not the elevation angle (degrees from the x-y plane) + zen_in = np.radians(90.0 - el_in) + + # Spherical to Cartesian + x_out = r_in * np.sin(zen_in) * np.cos(np.radians(az_in)) + y_out = r_in * np.sin(zen_in) * np.sin(np.radians(az_in)) + z_out = r_in * np.cos(zen_in) + + return x_out, y_out, z_out + + +def global_to_local_cartesian(x_in, y_in, z_in, lat_cent, lon_cent, rad_cent, + inverse=False): + """Converts a position from global to local cartesian or vice-versa + + Parameters + ---------- + x_in : float + global or local cartesian x in km (inverse=False/True) + y_in : float + global or local cartesian y in km (inverse=False/True) + z_in : float + global or local cartesian z in km (inverse=False/True) + lat_cent : float + geocentric latitude in degrees of local cartesian system origin + lon_cent : float + geocentric longitude in degrees of local cartesian system origin + rad_cent : float + distance from center of the Earth in km of local cartesian system + origin + inverse : bool + False to convert from global to local cartesian coodiantes, and True + for the inverse (default=False) + + Returns + ------- + x_out : float + local or global cartesian x in km (inverse=False/True) + y_out : float + local or global cartesian y in km (inverse=False/True) + z_out : float + local or global cartesian z in km (inverse=False/True) + + Notes + ------- + The global cartesian coordinate system has its origin at the center of the + Earth, while the local system has its origin specified by the input + latitude, longitude, and radius. The global system has x intersecting + the equatorial plane and the prime meridian, z pointing North along the + rotational axis, and y completing the right-handed coodinate system. + The local system has z pointing up, y pointing North, and x pointing East. + + """ + + # Get the global cartesian coordinates of local origin + x_cent, y_cent, z_cent = spherical_to_cartesian(lon_cent, lat_cent, + rad_cent) + + # Get the amount of rotation needed to align the x-axis with the + # Earth's rotational axis + ax_rot = np.radians(90.0 - lat_cent) + + # Get the amount of rotation needed to align the global x-axis with the + # prime meridian + mer_rot = np.radians(lon_cent - 90.0) + + if inverse: + # Rotate about the x-axis to align the z-axis with the Earth's + # rotational axis + xrot = x_in + yrot = y_in * np.cos(ax_rot) - z_in * np.sin(ax_rot) + zrot = y_in * np.sin(ax_rot) + z_in * np.cos(ax_rot) + + # Rotate about the global z-axis to get the global x-axis aligned + # with the prime meridian and translate the local center to the + # global origin + x_out = xrot * np.cos(mer_rot) - yrot * np.sin(mer_rot) + x_cent + y_out = xrot * np.sin(mer_rot) + yrot * np.cos(mer_rot) + y_cent + z_out = zrot + z_cent + else: + # Translate global origin to the local origin + xtrans = x_in - x_cent + ytrans = y_in - y_cent + ztrans = z_in - z_cent + + # Rotate about the global z-axis to get the local x-axis pointing East + xrot = xtrans * np.cos(-mer_rot) - ytrans * np.sin(-mer_rot) + yrot = xtrans * np.sin(-mer_rot) + ytrans * np.cos(-mer_rot) + zrot = ztrans + + # Rotate about the x-axis to get the z-axis pointing up + x_out = xrot + y_out = yrot * np.cos(-ax_rot) - zrot * np.sin(-ax_rot) + z_out = yrot * np.sin(-ax_rot) + zrot * np.cos(-ax_rot) + + return x_out, y_out, z_out + + +def local_horizontal_to_global_geo(az, el, dist, lat_orig, lon_orig, alt_orig, + geodetic=True): + """ Convert from local horizontal coordinates to geodetic or geocentric + coordinates + + Parameters + ---------- + az : float + Azimuth (angle from North) of point in degrees + el : float + Elevation (angle from ground) of point in degrees + dist : float + Distance from origin to point in km + lat_orig : float + Latitude of origin in degrees + lon_orig : float + Longitude of origin in degrees + alt_orig : float + Altitude of origin in km from the surface of the Earth + geodetic : bool + True if origin coordinates are geodetic, False if they are geocentric. + Will return coordinates in the same system as the origin input. + (default=True) + + Returns + ------- + lat_pnt : float + Latitude of point in degrees + lon_pnt : float + Longitude of point in degrees + rad_pnt : float + Distance to the point from the centre of the Earth in km + + References + ---------- + Based on J.M. Ruohoniemi's geopack and R.J. Barnes radar.pro + + """ + + # If the data are in geodetic coordiantes, convert to geocentric + if geodetic: + (glat, glon, rearth, gaz, gel) = \ + geodetic_to_geocentric_horizontal(lat_orig, lon_orig, az, el, + inverse=False) + grad = rearth + alt_orig + else: + glat = lat_orig + glon = lon_orig + grad = alt_orig + 6371.0 # Add the mean earth radius in km + gaz = az + gel = el + + # Convert from local horizontal to local cartesian coordiantes + x_loc, y_loc, z_loc = spherical_to_cartesian(gaz, gel, dist, inverse=False) + + # Convert from local to global cartesian coordiantes + x_glob, y_glob, z_glob = global_to_local_cartesian(x_loc, y_loc, z_loc, + glat, glon, grad, + inverse=True) + + # Convert from global cartesian to geocentric coordinates + lon_pnt, lat_pnt, rad_pnt = spherical_to_cartesian(x_glob, y_glob, z_glob, + inverse=True) + + # Convert from geocentric to geodetic, if desired + if geodetic: + lat_pnt, lon_pnt, rearth = geodetic_to_geocentric(lat_pnt, lon_pnt, + inverse=True) + rad_pnt = rearth + rad_pnt - 6371.0 + + return lat_pnt, lon_pnt, rad_pnt From c6870edb2e5a275f643a43dfb50a33d5e48c47f1 Mon Sep 17 00:00:00 2001 From: jklenzing Date: Wed, 13 Feb 2019 11:15:45 -0500 Subject: [PATCH 02/50] restructure --- pysat/utils/coords.py | 75 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/pysat/utils/coords.py b/pysat/utils/coords.py index a3d3f7f..d7a13ab 100644 --- a/pysat/utils/coords.py +++ b/pysat/utils/coords.py @@ -9,6 +9,81 @@ import numpy as np +def scale_units(out_unit, in_unit): + """ Determine the scaling factor between two units + + Parameters + ------------- + out_unit : str + Desired unit after scaling + in_unit : str + Unit to be scaled + + Returns + ----------- + unit_scale : float + Scaling factor that will convert from in_units to out_units + + Notes + ------- + Accepted units include degrees ('deg', 'degree', 'degrees'), + radians ('rad', 'radian', 'radians'), + hours ('h', 'hr', 'hrs', 'hour', 'hours'), and lengths ('m', 'km', 'cm'). + Can convert between degrees, radians, and hours or different lengths. + + Example + ----------- + :: + import numpy as np + two_pi = 2.0 * np.pi + scale = scale_units("deg", "RAD") + two_pi *= scale + two_pi # will show 360.0 + + + """ + + if out_unit == in_unit: + return 1.0 + + accepted_units = {'deg': ['deg', 'degree', 'degrees'], + 'rad': ['rad', 'radian', 'radians'], + 'h': ['h', 'hr', 'hrs', 'hours'], + 'm': ['m', 'km', 'cm'], + 'm/s': ['m/s', 'cm/s', 'km/s']} + + scales = {'deg': 180.0, 'rad': np.pi, 'h': 12.0, + 'm': 1.0, 'km': 0.001, 'cm': 100.0, + 'm/s': 1.0, 'cm/s': 100.0, 'km/s': 0.001} + + # Test input and determine transformation type + out_key = None + in_key = None + for kk in accepted_units.keys(): + if out_unit.lower() in accepted_units[kk]: + out_key = kk + if in_unit.lower() in accepted_units[kk]: + in_key = kk + + if out_key is None: + raise ValueError('Unknown output unit {:}'.format(out_unit)) + + if in_key is None: + raise ValueError('Unknown input unit {:}'.format(in_unit)) + + if out_key == 'm' or out_key == 'm/s' or in_key == 'm' or in_key == 'm/s': + if in_key != out_key: + raise ValueError('Cannot scale {:s} and {:s}'.format(out_unit, + in_unit)) + # Recast units as keys for the scales dictionary + out_key = out_unit + in_key = in_unit + + unit_scale = scales[out_key.lower()] / scales[in_key.lower()] + + return unit_scale + + def geodetic_to_geocentric(lat_in, lon_in=None, inverse=False): """Converts position from geodetic to geocentric or vice-versa. From 49a3894b9760922f5c7eb643ef2e065c0a740d61 Mon Sep 17 00:00:00 2001 From: jklenzing Date: Wed, 13 Feb 2019 12:26:14 -0500 Subject: [PATCH 03/50] longitude functions to coords --- pysat/utils/coords.py | 65 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/pysat/utils/coords.py b/pysat/utils/coords.py index d7a13ab..7f7122c 100644 --- a/pysat/utils/coords.py +++ b/pysat/utils/coords.py @@ -9,6 +9,71 @@ import numpy as np +def adjust_cyclic_data(samples, high=2.0*np.pi, low=0.0): + """Adjust cyclic values such as longitude to a different scale + + Parameters + ----------- + samples : array_like + Input array + high: float or int + Upper boundary for circular standard deviation range (default=2 pi) + low : float or int + Lower boundary for circular standard deviation range (default=0) + axis : int or NoneType + Axis along which standard deviations are computed. The default is to + compute the standard deviation of the flattened array + + Returns + -------- + out_samples : float + Circular standard deviation + + """ + + out_samples = np.asarray(samples) + sample_range = high - low + out_samples[out_samples >= high] -= sample_range + out_samples[out_samples < low] += sample_range + + return out_samples + + +def update_longitude(inst, lon_name=None, high=180.0, low=-180.0): + """ Update longitude to the desired range + + Parameters + ------------ + inst : pysat.Instrument instance + instrument object to be updated + lon_name : string + name of the longtiude data + high : float + Highest allowed longitude value (default=180.0) + low : float + Lowest allowed longitude value (default=-180.0) + + Returns + --------- + updates instrument data in column 'lon_name' + + """ + from pysat.utils.coords import adjust_cyclic_data + + if lon_name not in inst.data.keys(): + raise ValueError('uknown longitude variable name') + + new_lon = adjust_cyclic_data(inst[lon_name], high=high, low=low) + + # Update based on data type + if inst.pandas_format: + inst[lon_name] = new_lon + else: + inst[lon_name].data = new_lon + + return + + def scale_units(out_unit, in_unit): """ Determine the scaling factor between two units From 2e741822404527654026b25cce791ccd261c554b Mon Sep 17 00:00:00 2001 From: Jeff Klenzing Date: Wed, 20 Feb 2019 16:01:16 -0500 Subject: [PATCH 04/50] rename test files and tidy --- pysat/tests/test_utils_coords.py | 318 +++++++++++++++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 pysat/tests/test_utils_coords.py diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py new file mode 100644 index 0000000..b6cdff0 --- /dev/null +++ b/pysat/tests/test_utils_coords.py @@ -0,0 +1,318 @@ +""" +tests the pysat coords area +""" +import numpy as np +import pandas as pds +import nose.tools +from nose.tools import assert_raises, raises +import pysat +from pysat.utils import coords + + +class TestBasics(): + def setup(self): + """Runs before every method to create a clean testing setup.""" + self.test_angles = np.array([340.0, 348.0, 358.9, 0.5, 5.0, 9.87]) + + self.testInst = pysat.Instrument(platform='pysat', + name='testing', + clean_level='clean') + # Add longitude to the test instrument + ones = np.ones(shape=len(self.test_angles)) + time = pysat.utils.time.create_datetime_index(year=ones*2001, + month=ones, + uts=np.arange(0.0, + len(ones), + 1.0)) + + self.testInst.data = \ + pds.DataFrame(np.array([time, self.test_angles]).transpose(), + index=time, columns=["time", "longitude"]) + + self.deg_units = ["deg", "degree", "degrees", "rad", "radian", + "radians", "h", "hr", "hrs", "hours"] + self.dist_units = ["m", "km", "cm"] + self.vel_units = ["m/s", "cm/s", "km/s"] + + def teardown(self): + """Runs after every method to clean up previous testing.""" + del self.test_angles, self.testInst + del self.deg_units, self.dist_units, self.vel_units + + def test_adjust_cyclic_data_default(self): + """ Test adjust_cyclic_data with default range """ + + test_in = np.radians(self.test_angles) - np.pi + test_angles = coords.adjust_cyclic_data(test_in) + + assert test_angles.max() < 2.0 * np.pi + assert test_angles.min() >= 0.0 + + def test_adjust_cyclic_data_custom(self): + """ Test adjust_cyclic_data with a custom range """ + + test_angles = coords.adjust_cyclic_data(self.test_angles, + high=180.0, low=-180.0) + + assert test_angles.max() < 180.0 + assert test_angles.min() >= -180.0 + + def test_update_longitude(self): + """Test update_longitude """ + + coords.update_longitude(self.testInst, lon_name="longitude") + + assert np.all(self.testInst.data['longitude'] < 180.0) + assert np.all(self.testInst.data['longitude'] >= -180.0) + + def test_bad_lon_name_update_longitude(self): + """Test update_longitude with a bad longitude name""" + + assert_raises(ValueError, coords.update_longitude, + self.testInst) + + def test_scale_units_same(self): + """ Test scale_units when both units are the same """ + + scale = coords.scale_units("happy", "happy") + + assert scale == 1.0 + + def test_scale_units_angles(self): + """Test scale_units for angles """ + + for out_unit in self.deg_units: + scale = coords.scale_units(out_unit, "deg") + + if out_unit.find("deg") == 0: + assert scale == 1.0 + elif out_unit.find("rad") == 0: + assert scale == np.pi / 180.0 + else: + assert scale == 1.0 / 15.0 + + def test_scale_units_dist(self): + """Test scale_units for distances """ + + for out_unit in self.dist_units: + scale = coords.scale_units(out_unit, "m") + + if out_unit == "m": + assert scale == 1.0 + elif out_unit.find("km") == 0: + assert scale == 0.001 + else: + assert scale == 100.0 + + def test_scale_units_vel(self): + """Test scale_units for velocities """ + + for out_unit in self.vel_units: + scale = coords.scale_units(out_unit, "m/s") + + if out_unit == "m/s": + assert scale == 1.0 + elif out_unit.find("km/s") == 0: + assert scale == 0.001 + else: + assert scale == 100.0 + + def test_scale_units_bad(self): + """Test scale_units for mismatched input""" + + assert_raises(ValueError, coords.scale_units, "happy", "m") + assert_raises(ValueError, coords.scale_units, "m", "happy") + assert_raises(ValueError, coords.scale_units, "m", "m/s") + assert_raises(ValueError, coords.scale_units, "m", "deg") + assert_raises(ValueError, coords.scale_units, "h", "km/s") + + def test_geodetic_to_geocentric_single(self): + """Test conversion from geodetic to geocentric coordinates""" + + lat0 = 45.0 + lon0 = 8.0 + + latx, lonx, radx = coords.geodetic_to_geocentric(lat0, + lon_in=lon0) + + assert (abs(latx - 44.807576784018046) < 1.0e-6) + assert (abs(lonx - lon0) < 1.0e-6) + assert (abs(radx - 6367.489543863465) < 1.0e-6) + + def test_geocentric_to_geodetic_single(self): + """Test conversion from geocentric to geodetic coordinates""" + + lat0 = 45.0 + lon0 = 8.0 + + latx, lonx, radx = coords.geodetic_to_geocentric(lat0, + lon_in=lon0, + inverse=True) + + assert (abs(latx - 45.192423215981954) < 1.0e-6) + assert (abs(lonx - lon0) < 1.0e-6) + assert (abs(radx - 6367.345908499981) < 1.0e-6) + + def test_geodetic_to_geocentric_mult(self): + """Test array conversion from geodetic to geocentric coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + latx, lonx, radx = coords.geodetic_to_geocentric(45.0*arr, + lon_in=8.0*arr) + + assert latx.shape == arr.shape + assert lonx.shape == arr.shape + assert radx.shape == arr.shape + assert (abs(latx - 44.807576784018046).max() < 1.0e-6) + assert (abs(lonx - 8.0).max() < 1.0e-6) + assert (abs(radx - 6367.489543863465).max() < 1.0e-6) + + def test_geocentric_to_geodetic_mult(self): + """Test array conversion from geocentric to geodetic coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + latx, lonx, radx = coords.geodetic_to_geocentric(45.0*arr, + lon_in=8.0*arr, + inverse=True) + + assert latx.shape == arr.shape + assert lonx.shape == arr.shape + assert radx.shape == arr.shape + assert (abs(latx - 45.192423215981954).max() < 1.0e-6) + assert (abs(lonx - 8.0).max() < 1.0e-6) + assert (abs(radx - 6367.345908499981).max() < 1.0e-6) + + def test_spherical_to_cartesian_single(self): + """Test conversion from spherical to cartesian coordinates""" + + x, y, z = coords.spherical_to_cartesian(45.0, 30.0, 1.0) + + assert abs(x - y) < 1.0e-6 + assert abs(z - 0.5) < 1.0e-6 + + def test_cartesian_to_spherical_single(self): + """Test conversion from cartesian to spherical coordinates""" + + x = 0.6123724356957946 + az, el, r = coords.spherical_to_cartesian(x, x, 0.5, + inverse=True) + + assert abs(az - 45.0) < 1.0e-6 + assert abs(el - 30.0) < 1.0e-6 + assert abs(r - 1.0) < 1.0e-6 + + def test_spherical_to_cartesian_mult(self): + """Test array conversion from spherical to cartesian coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + x, y, z = coords.spherical_to_cartesian(45.0*arr, 30.0*arr, arr) + + assert x.shape == arr.shape + assert y.shape == arr.shape + assert z.shape == arr.shape + assert abs(x - y).max() < 1.0e-6 + assert abs(z - 0.5).max() < 1.0e-6 + + def test_cartesian_to_spherical_mult(self): + """Test array conversion from cartesian to spherical coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + x = 0.6123724356957946 + az, el, r = coords.spherical_to_cartesian(x*arr, x*arr, 0.5*arr, + inverse=True) + + assert az.shape == arr.shape + assert el.shape == arr.shape + assert r.shape == arr.shape + assert abs(az - 45.0).max() < 1.0e-6 + assert abs(el - 30.0).max() < 1.0e-6 + assert abs(r - 1.0).max() < 1.0e-6 + + def test_geodetic_to_geocentric_inverse(self): + """Tests the reversibility of geodetic to geocentric conversions""" + + lat1 = 37.5 + lon1 = 117.3 + lat2, lon2, rad_e = coords.geodetic_to_geocentric(lat1, + lon_in=lon1, + inverse=False) + lat3, lon3, rad_e = coords.geodetic_to_geocentric(lat2, + lon_in=lon2, + inverse=True) + assert (abs(lon1-lon3) < 1.0e-6) + assert (abs(lat1-lat3) < 1.0e-6) + + def test_geodetic_to_geocentric_horizontal_inverse(self): + """Tests the reversibility of geodetic to geocentric horiz conversions + + Note: inverse of az and el angles currently non-functional""" + + lat1 = -17.5 + lon1 = 187.3 + az1 = 52.0 + el1 = 63.0 + lat2, lon2, rad_e, az2, el2 = \ + coords.geodetic_to_geocentric_horizontal(lat1, lon1, + az1, el1, + inverse=False) + lat3, lon3, rad_e, az3, el3 = \ + coords.geodetic_to_geocentric_horizontal(lat2, lon2, + az2, el2, + inverse=True) + + assert (abs(lon1-lon3) < 1.0e-6) + assert (abs(lat1-lat3) < 1.0e-6) + assert (abs(az1-az3) < 1.0e-6) + assert (abs(el1-el3) < 1.0e-6) + + def test_spherical_to_cartesian_inverse(self): + """Tests the reversibility of spherical to cartesian conversions""" + + x1 = 3000.0 + y1 = 2000.0 + z1 = 2500.0 + az, el, r = coords.spherical_to_cartesian(x1, y1, z1, + inverse=True) + x2, y2, z2 = coords.spherical_to_cartesian(az, el, r, + inverse=False) + + assert (abs(x1-x2) < 1.0e-6) + assert (abs(y1-y2) < 1.0e-6) + assert (abs(z1-z2) < 1.0e-6) + + def test_global_to_local_cartesian_inverse(self): + """Tests the reversibility of the global to loc cartesian transform""" + + x1 = 7000.0 + y1 = 8000.0 + z1 = 9500.0 + lat = 37.5 + lon = 289.0 + rad = 6380.0 + x2, y2, z2 = coords.global_to_local_cartesian(x1, y1, z1, + lat, lon, rad, + inverse=False) + x3, y3, z3 = coords.global_to_local_cartesian(x2, y2, z2, + lat, lon, rad, + inverse=True) + assert (abs(x1-x3) < 1.0e-6) + assert (abs(y1-y3) < 1.0e-6) + assert (abs(z1-z3) < 1.0e-6) + + def test_local_horizontal_to_global_geo(self): + """Tests the conversion of the local horizontal to global geo""" + + az = 30.0 + el = 45.0 + dist = 1000.0 + lat0 = 45.0 + lon0 = 0.0 + alt0 = 400.0 + + latx, lonx, radx = \ + coords.local_horizontal_to_global_geo(az, el, dist, + lat0, lon0, alt0) + + assert (abs(latx - 50.419037572472625) < 1.0e-6) + assert (abs(lonx + 7.694008395350697) < 1.0e-6) + assert (abs(radx - 7172.15486518744) < 1.0e-6) From 804623196b62f70c43b7813dbeb6006908bb667d Mon Sep 17 00:00:00 2001 From: Jeff Klenzing Date: Wed, 20 Feb 2019 16:25:38 -0500 Subject: [PATCH 05/50] test_utils_coords structure --- pysat/tests/test_utils_coords.py | 98 ++++++++++++++++++++------------ 1 file changed, 61 insertions(+), 37 deletions(-) diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py index b6cdff0..fafeb60 100644 --- a/pysat/tests/test_utils_coords.py +++ b/pysat/tests/test_utils_coords.py @@ -39,6 +39,9 @@ def teardown(self): del self.test_angles, self.testInst del self.deg_units, self.dist_units, self.vel_units + ##################################### + # Cyclic data conversions + def test_adjust_cyclic_data_default(self): """ Test adjust_cyclic_data with default range """ @@ -57,6 +60,9 @@ def test_adjust_cyclic_data_custom(self): assert test_angles.max() < 180.0 assert test_angles.min() >= -180.0 + ##################################### + # Update Longitude + def test_update_longitude(self): """Test update_longitude """ @@ -71,6 +77,9 @@ def test_bad_lon_name_update_longitude(self): assert_raises(ValueError, coords.update_longitude, self.testInst) + ##################################### + # Scale units + def test_scale_units_same(self): """ Test scale_units when both units are the same """ @@ -126,6 +135,9 @@ def test_scale_units_bad(self): assert_raises(ValueError, coords.scale_units, "m", "deg") assert_raises(ValueError, coords.scale_units, "h", "km/s") + ##################################### + # Geodetic / Geocentric conversions + def test_geodetic_to_geocentric_single(self): """Test conversion from geodetic to geocentric coordinates""" @@ -182,6 +194,49 @@ def test_geocentric_to_geodetic_mult(self): assert (abs(lonx - 8.0).max() < 1.0e-6) assert (abs(radx - 6367.345908499981).max() < 1.0e-6) + def test_geodetic_to_geocentric_inverse(self): + """Tests the reversibility of geodetic to geocentric conversions""" + + lat1 = 37.5 + lon1 = 117.3 + lat2, lon2, rad_e = coords.geodetic_to_geocentric(lat1, + lon_in=lon1, + inverse=False) + lat3, lon3, rad_e = coords.geodetic_to_geocentric(lat2, + lon_in=lon2, + inverse=True) + assert (abs(lon1-lon3) < 1.0e-6) + assert (abs(lat1-lat3) < 1.0e-6) + + ############################################### + # Geodetic / Geocentric Horizontal conversions + + def test_geodetic_to_geocentric_horizontal_inverse(self): + """Tests the reversibility of geodetic to geocentric horiz conversions + + Note: inverse of az and el angles currently non-functional""" + + lat1 = -17.5 + lon1 = 187.3 + az1 = 52.0 + el1 = 63.0 + lat2, lon2, rad_e, az2, el2 = \ + coords.geodetic_to_geocentric_horizontal(lat1, lon1, + az1, el1, + inverse=False) + lat3, lon3, rad_e, az3, el3 = \ + coords.geodetic_to_geocentric_horizontal(lat2, lon2, + az2, el2, + inverse=True) + + assert (abs(lon1-lon3) < 1.0e-6) + assert (abs(lat1-lat3) < 1.0e-6) + assert (abs(az1-az3) < 1.0e-6) + assert (abs(el1-el3) < 1.0e-6) + + #################################### + # Spherical / Cartesian conversions + def test_spherical_to_cartesian_single(self): """Test conversion from spherical to cartesian coordinates""" @@ -228,43 +283,6 @@ def test_cartesian_to_spherical_mult(self): assert abs(el - 30.0).max() < 1.0e-6 assert abs(r - 1.0).max() < 1.0e-6 - def test_geodetic_to_geocentric_inverse(self): - """Tests the reversibility of geodetic to geocentric conversions""" - - lat1 = 37.5 - lon1 = 117.3 - lat2, lon2, rad_e = coords.geodetic_to_geocentric(lat1, - lon_in=lon1, - inverse=False) - lat3, lon3, rad_e = coords.geodetic_to_geocentric(lat2, - lon_in=lon2, - inverse=True) - assert (abs(lon1-lon3) < 1.0e-6) - assert (abs(lat1-lat3) < 1.0e-6) - - def test_geodetic_to_geocentric_horizontal_inverse(self): - """Tests the reversibility of geodetic to geocentric horiz conversions - - Note: inverse of az and el angles currently non-functional""" - - lat1 = -17.5 - lon1 = 187.3 - az1 = 52.0 - el1 = 63.0 - lat2, lon2, rad_e, az2, el2 = \ - coords.geodetic_to_geocentric_horizontal(lat1, lon1, - az1, el1, - inverse=False) - lat3, lon3, rad_e, az3, el3 = \ - coords.geodetic_to_geocentric_horizontal(lat2, lon2, - az2, el2, - inverse=True) - - assert (abs(lon1-lon3) < 1.0e-6) - assert (abs(lat1-lat3) < 1.0e-6) - assert (abs(az1-az3) < 1.0e-6) - assert (abs(el1-el3) < 1.0e-6) - def test_spherical_to_cartesian_inverse(self): """Tests the reversibility of spherical to cartesian conversions""" @@ -280,6 +298,9 @@ def test_spherical_to_cartesian_inverse(self): assert (abs(y1-y2) < 1.0e-6) assert (abs(z1-z2) < 1.0e-6) + ######################################## + # Global / Local Cartesian conversions + def test_global_to_local_cartesian_inverse(self): """Tests the reversibility of the global to loc cartesian transform""" @@ -299,6 +320,9 @@ def test_global_to_local_cartesian_inverse(self): assert (abs(y1-y3) < 1.0e-6) assert (abs(z1-z3) < 1.0e-6) + ######################################## + # Local Horizontal / Global conversions + def test_local_horizontal_to_global_geo(self): """Tests the conversion of the local horizontal to global geo""" From 9129d13cdba25e8893defd3e7a54c42164365449 Mon Sep 17 00:00:00 2001 From: Jeff Klenzing Date: Wed, 20 Feb 2019 17:47:10 -0500 Subject: [PATCH 06/50] move local time to coords --- pysat/tests/test_utils_coords.py | 9 +++++ pysat/utils/coords.py | 60 ++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py index fafeb60..75f991c 100644 --- a/pysat/tests/test_utils_coords.py +++ b/pysat/tests/test_utils_coords.py @@ -340,3 +340,12 @@ def test_local_horizontal_to_global_geo(self): assert (abs(latx - 50.419037572472625) < 1.0e-6) assert (abs(lonx + 7.694008395350697) < 1.0e-6) assert (abs(radx - 7172.15486518744) < 1.0e-6) + + ######################### + # calc_solar_local_time + + def test_calc_solar_local_time(self): + + pytime.calc_solar_local_time(inst, lon_name=None, slt_name='slt') + + assert True diff --git a/pysat/utils/coords.py b/pysat/utils/coords.py index 7f7122c..6cb44f0 100644 --- a/pysat/utils/coords.py +++ b/pysat/utils/coords.py @@ -489,3 +489,63 @@ def local_horizontal_to_global_geo(az, el, dist, lat_orig, lon_orig, alt_orig, rad_pnt = rearth + rad_pnt - 6371.0 return lat_pnt, lon_pnt, rad_pnt + + +def calc_solar_local_time(inst, lon_name=None, slt_name='slt'): + """ Append solar local time to an instrument object + + Parameters + ------------ + inst : pysat.Instrument instance + instrument object to be updated + lon_name : string + name of the longtiude data key (assumes data are in degrees) + slt_name : string + name of the output solar local time data key (default='slt') + + Returns + --------- + updates instrument data in column specified by slt_name + + """ + import datetime as dt + + if lon_name not in inst.data.keys(): + raise ValueError('uknown longitude variable name') + + # Convert from numpy epoch nanoseconds to UT seconds of day + utsec = list() + for nptime in inst.index.values.astype(int): + # Numpy times come out in nanoseconds and timestamp converts + # from seconds + dtime = dt.datetime.fromtimestamp(nptime * 1.0e-9) + utsec.append((dtime.hour * 3600.0 + dtime.minute * 60.0 + + dtime.second + dtime.microsecond * 1.0e-6) / 3600.0) + + # Calculate solar local time + slt = np.array([t + inst[lon_name][i] / 15.0 for i, t in enumerate(utsec)]) + + # Ensure that solar local time falls between 0 and 24 hours + slt[slt >= 24.0] -= 24.0 + slt[slt < 0.0] += 24.0 + + # Add the solar local time to the instrument + if inst.pandas_format: + inst[slt_name] = pds.Series(slt, index=inst.data.index) + else: + data = inst.data.assign(pysat_slt=(inst.data.coords.keys(), slt)) + data.rename({"pysat_slt": slt_name}, inplace=True) + inst.data = data + + # Add units to the metadata + inst.meta.__setitem__(slt_name, {inst.meta.units_label: 'h', + inst.meta.name_label: "Solar Local Time", + inst.meta.desc_label: "Solar local time", + inst.meta.plot_label: "SLT", + inst.meta.axis_label: "SLT", + inst.meta.scale_label: "linear", + inst.meta.min_label: 0.0, + inst.meta.max_label: 24.0, + inst.meta.fill_label: np.nan}) + + return From c7da44ee39bef699c6b64060e6c656a73ea9c2c5 Mon Sep 17 00:00:00 2001 From: Jeff Klenzing Date: Wed, 20 Feb 2019 18:02:42 -0500 Subject: [PATCH 07/50] unit tests for solar local time --- pysat/tests/test_utils_coords.py | 32 +++++++++++++++++++++----------- pysat/utils/coords.py | 1 + 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py index 75f991c..20da4ef 100644 --- a/pysat/tests/test_utils_coords.py +++ b/pysat/tests/test_utils_coords.py @@ -71,11 +71,30 @@ def test_update_longitude(self): assert np.all(self.testInst.data['longitude'] < 180.0) assert np.all(self.testInst.data['longitude'] >= -180.0) + @raises(ValueError) def test_bad_lon_name_update_longitude(self): """Test update_longitude with a bad longitude name""" - assert_raises(ValueError, coords.update_longitude, - self.testInst) + coords.update_longitude(self.testInst, lon_name="not longitude") + + ######################### + # calc_solar_local_time + + def test_calc_solar_local_time(self): + + coords.calc_solar_local_time(self.testInst, lon_name="longitude", + slt_name='slt') + target = [17.666667, 18.200278, 18.927222, + 19.034167, 19.334444, 19.659389] + + assert (np.abs(self.testInst['slt'] - target)).max() < 1.0e-6 + + @raises(ValueError) + def test_bad_lon_name_calc_solar_local_time(self): + """Test calc_solar_local_time with a bad longitude name""" + + coords.calc_solar_local_time(self.testInst, lon_name="not longitude", + slt_name='slt') ##################################### # Scale units @@ -340,12 +359,3 @@ def test_local_horizontal_to_global_geo(self): assert (abs(latx - 50.419037572472625) < 1.0e-6) assert (abs(lonx + 7.694008395350697) < 1.0e-6) assert (abs(radx - 7172.15486518744) < 1.0e-6) - - ######################### - # calc_solar_local_time - - def test_calc_solar_local_time(self): - - pytime.calc_solar_local_time(inst, lon_name=None, slt_name='slt') - - assert True diff --git a/pysat/utils/coords.py b/pysat/utils/coords.py index 6cb44f0..610d8d4 100644 --- a/pysat/utils/coords.py +++ b/pysat/utils/coords.py @@ -7,6 +7,7 @@ """ import numpy as np +import pandas as pds def adjust_cyclic_data(samples, high=2.0*np.pi, low=0.0): From e292e42cd0fb2f635fa8f6ba15e251d8ed5e37aa Mon Sep 17 00:00:00 2001 From: Jeff Klenzing Date: Wed, 20 Feb 2019 18:39:09 -0500 Subject: [PATCH 08/50] more coord tests + style --- pysat/tests/test_utils_coords.py | 234 ++++++++++++++++++++++--------- pysat/utils/coords.py | 120 ++++++++-------- 2 files changed, 227 insertions(+), 127 deletions(-) diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py index 20da4ef..5b40ba7 100644 --- a/pysat/tests/test_utils_coords.py +++ b/pysat/tests/test_utils_coords.py @@ -6,7 +6,7 @@ import nose.tools from nose.tools import assert_raises, raises import pysat -from pysat.utils import coords +from pysat.utils import coords, time class TestBasics(): @@ -19,15 +19,13 @@ def setup(self): clean_level='clean') # Add longitude to the test instrument ones = np.ones(shape=len(self.test_angles)) - time = pysat.utils.time.create_datetime_index(year=ones*2001, - month=ones, - uts=np.arange(0.0, - len(ones), - 1.0)) + tind = time.create_datetime_index(year=ones*2001, + month=ones, + uts=np.arange(0.0, len(ones), 1.0)) self.testInst.data = \ - pds.DataFrame(np.array([time, self.test_angles]).transpose(), - index=time, columns=["time", "longitude"]) + pds.DataFrame(np.array([tind, self.test_angles]).transpose(), + index=tind, columns=["time", "longitude"]) self.deg_units = ["deg", "degree", "degrees", "rad", "radian", "radians", "h", "hr", "hrs", "hours"] @@ -87,7 +85,7 @@ def test_calc_solar_local_time(self): target = [17.666667, 18.200278, 18.927222, 19.034167, 19.334444, 19.659389] - assert (np.abs(self.testInst['slt'] - target)).max() < 1.0e-6 + assert (abs(self.testInst['slt'] - target)).max() < 1.0e-6 @raises(ValueError) def test_bad_lon_name_calc_solar_local_time(self): @@ -160,76 +158,128 @@ def test_scale_units_bad(self): def test_geodetic_to_geocentric_single(self): """Test conversion from geodetic to geocentric coordinates""" - lat0 = 45.0 - lon0 = 8.0 - - latx, lonx, radx = coords.geodetic_to_geocentric(lat0, - lon_in=lon0) + lat, lon, rad = coords.geodetic_to_geocentric(45.0, lon_in=8.0) - assert (abs(latx - 44.807576784018046) < 1.0e-6) - assert (abs(lonx - lon0) < 1.0e-6) - assert (abs(radx - 6367.489543863465) < 1.0e-6) + assert abs(lat - 44.807576784018046) < 1.0e-6 + assert abs(lon - 8.0) < 1.0e-6 + assert abs(rad - 6367.489543863465) < 1.0e-6 def test_geocentric_to_geodetic_single(self): """Test conversion from geocentric to geodetic coordinates""" - lat0 = 45.0 - lon0 = 8.0 - - latx, lonx, radx = coords.geodetic_to_geocentric(lat0, - lon_in=lon0, - inverse=True) + lat, lon, rad = coords.geodetic_to_geocentric(45.0, lon_in=8.0, + inverse=True) - assert (abs(latx - 45.192423215981954) < 1.0e-6) - assert (abs(lonx - lon0) < 1.0e-6) - assert (abs(radx - 6367.345908499981) < 1.0e-6) + assert abs(lat - 45.192423215981954) < 1.0e-6 + assert abs(lon - 8.0) < 1.0e-6 + assert abs(rad - 6367.345908499981) < 1.0e-6 def test_geodetic_to_geocentric_mult(self): """Test array conversion from geodetic to geocentric coordinates""" arr = np.ones(shape=(10,), dtype=float) - latx, lonx, radx = coords.geodetic_to_geocentric(45.0*arr, - lon_in=8.0*arr) + lat, lon, rad = coords.geodetic_to_geocentric(45.0*arr, lon_in=8.0*arr) - assert latx.shape == arr.shape - assert lonx.shape == arr.shape - assert radx.shape == arr.shape - assert (abs(latx - 44.807576784018046).max() < 1.0e-6) - assert (abs(lonx - 8.0).max() < 1.0e-6) - assert (abs(radx - 6367.489543863465).max() < 1.0e-6) + assert lat.shape == arr.shape + assert lon.shape == arr.shape + assert rad.shape == arr.shape + assert abs(lat - 44.807576784018046).max() < 1.0e-6 + assert abs(lon - 8.0).max() < 1.0e-6 + assert abs(rad - 6367.489543863465).max() < 1.0e-6 def test_geocentric_to_geodetic_mult(self): """Test array conversion from geocentric to geodetic coordinates""" arr = np.ones(shape=(10,), dtype=float) - latx, lonx, radx = coords.geodetic_to_geocentric(45.0*arr, - lon_in=8.0*arr, - inverse=True) + lat, lon, rad = coords.geodetic_to_geocentric(45.0*arr, lon_in=8.0*arr, + inverse=True) - assert latx.shape == arr.shape - assert lonx.shape == arr.shape - assert radx.shape == arr.shape - assert (abs(latx - 45.192423215981954).max() < 1.0e-6) - assert (abs(lonx - 8.0).max() < 1.0e-6) - assert (abs(radx - 6367.345908499981).max() < 1.0e-6) + assert lat.shape == arr.shape + assert lon.shape == arr.shape + assert rad.shape == arr.shape + assert abs(lat - 45.192423215981954).max() < 1.0e-6 + assert abs(lon - 8.0).max() < 1.0e-6 + assert abs(rad - 6367.345908499981).max() < 1.0e-6 def test_geodetic_to_geocentric_inverse(self): """Tests the reversibility of geodetic to geocentric conversions""" lat1 = 37.5 lon1 = 117.3 - lat2, lon2, rad_e = coords.geodetic_to_geocentric(lat1, - lon_in=lon1, + lat2, lon2, rad_e = coords.geodetic_to_geocentric(lat1, lon_in=lon1, inverse=False) - lat3, lon3, rad_e = coords.geodetic_to_geocentric(lat2, - lon_in=lon2, + lat3, lon3, rad_e = coords.geodetic_to_geocentric(lat2, lon_in=lon2, inverse=True) - assert (abs(lon1-lon3) < 1.0e-6) - assert (abs(lat1-lat3) < 1.0e-6) + assert abs(lon1-lon3) < 1.0e-6 + assert abs(lat1-lat3) < 1.0e-6 ############################################### # Geodetic / Geocentric Horizontal conversions + def test_geodetic_to_geocentric_horz_single(self): + """Test conversion from geodetic to geocentric coordinates""" + + lat, lon, rad, az, el = \ + coords.geodetic_to_geocentric_horizontal(45.0, 8.0, 52.0, 63.0) + + assert abs(lat - 44.807576784018046) < 1.0e-6 + assert abs(lon - 8.0) < 1.0e-6 + assert abs(rad - 6367.489543863465) < 1.0e-6 + assert abs(az - 51.70376774257361) < 1.0e-6 + assert abs(el - 62.8811403841008) < 1.0e-6 + + def test_geocentric_to_geodetic_horz_single(self): + """Test conversion from geocentric to geodetic coordinates""" + + lat, lon, rad, az, el = \ + coords.geodetic_to_geocentric_horizontal(45.0, 8.0, 52.0, 63.0, + inverse=True) + + assert abs(lat - 45.192423215981954) < 1.0e-6 + assert abs(lon - 8.0) < 1.0e-6 + assert abs(rad - 6367.345908499981) < 1.0e-6 + assert abs(az - 52.29896101551479) < 1.0e-6 + assert abs(el - 63.118072033649916) < 1.0e-6 + + def test_geodetic_to_geocentric_horz_mult(self): + """Test array conversion from geodetic to geocentric coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + lat, lon, rad, az, el = \ + coords.geodetic_to_geocentric_horizontal(45.0*arr, 8.0*arr, + 52.0*arr, 63.0*arr) + + assert lat.shape == arr.shape + assert lon.shape == arr.shape + assert rad.shape == arr.shape + assert az.shape == arr.shape + assert el.shape == arr.shape + assert abs(lat - 44.807576784018046).max() < 1.0e-6 + assert abs(lon - 8.0).max() < 1.0e-6 + assert abs(rad - 6367.489543863465).max() < 1.0e-6 + assert abs(az - 51.70376774257361).max() < 1.0e-6 + assert abs(el - 62.8811403841008).max() < 1.0e-6 + + def test_geocentric_to_geodetic_horz_mult(self): + """Test array conversion from geocentric to geodetic coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + lat, lon, rad, az, el = \ + coords.geodetic_to_geocentric_horizontal(45.0*arr, 8.0*arr, + 52.0*arr, 63.0*arr, + inverse=True) + + assert lat.shape == arr.shape + assert lon.shape == arr.shape + assert rad.shape == arr.shape + assert az.shape == arr.shape + assert el.shape == arr.shape + assert abs(lat - 45.192423215981954).max() < 1.0e-6 + assert abs(lon - 8.0).max() < 1.0e-6 + assert abs(rad - 6367.345908499981).max() < 1.0e-6 + assert abs(az - 52.29896101551479).max() < 1.0e-6 + assert abs(el - 63.118072033649916).max() < 1.0e-6 + def test_geodetic_to_geocentric_horizontal_inverse(self): """Tests the reversibility of geodetic to geocentric horiz conversions @@ -240,18 +290,16 @@ def test_geodetic_to_geocentric_horizontal_inverse(self): az1 = 52.0 el1 = 63.0 lat2, lon2, rad_e, az2, el2 = \ - coords.geodetic_to_geocentric_horizontal(lat1, lon1, - az1, el1, + coords.geodetic_to_geocentric_horizontal(lat1, lon1, az1, el1, inverse=False) lat3, lon3, rad_e, az3, el3 = \ - coords.geodetic_to_geocentric_horizontal(lat2, lon2, - az2, el2, + coords.geodetic_to_geocentric_horizontal(lat2, lon2, az2, el2, inverse=True) - assert (abs(lon1-lon3) < 1.0e-6) - assert (abs(lat1-lat3) < 1.0e-6) - assert (abs(az1-az3) < 1.0e-6) - assert (abs(el1-el3) < 1.0e-6) + assert abs(lon1-lon3) < 1.0e-6 + assert abs(lat1-lat3) < 1.0e-6 + assert abs(az1-az3) < 1.0e-6 + assert abs(el1-el3) < 1.0e-6 #################################### # Spherical / Cartesian conversions @@ -313,13 +361,65 @@ def test_spherical_to_cartesian_inverse(self): x2, y2, z2 = coords.spherical_to_cartesian(az, el, r, inverse=False) - assert (abs(x1-x2) < 1.0e-6) - assert (abs(y1-y2) < 1.0e-6) - assert (abs(z1-z2) < 1.0e-6) + assert abs(x1-x2) < 1.0e-6 + assert abs(y1-y2) < 1.0e-6 + assert abs(z1-z2) < 1.0e-6 ######################################## # Global / Local Cartesian conversions + def test_global_to_local_cartesian_single(self): + """Test conversion from global to local cartesian coordinates""" + + x, y, z = coords.global_to_local_cartesian(7000.0, 8000.0, 9000.0, + 37.5, 289.0, 6380.0) + + assert abs(x + 9223.175264852474) < 1.0e-6 + assert abs(y + 2239.835278362686) < 1.0e-6 + assert abs(z - 11323.126851088331) < 1.0e-6 + + def test_local_cartesian_to_global_single(self): + """Test conversion from local cartesian to global coordinates""" + + x, y, z = coords.global_to_local_cartesian(7000.0, 8000.0, 9000.0, + 37.5, 289.0, 6380.0, + inverse=True) + + assert abs(x + 5709.804676635975) < 1.0e-6 + assert abs(y + 4918.397556010223) < 1.0e-6 + assert abs(z - 15709.577500484005) < 1.0e-6 + + def test_global_to_local_cartesian_mult(self): + """Test array conversion from global to local cartesian coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + x, y, z = coords.global_to_local_cartesian(7000.0*arr, 8000.0*arr, + 9000.0*arr, 37.5*arr, + 289.0*arr, 6380.0*arr) + + assert x.shape == arr.shape + assert y.shape == arr.shape + assert z.shape == arr.shape + assert abs(x + 9223.175264852474).max() < 1.0e-6 + assert abs(y + 2239.835278362686).max() < 1.0e-6 + assert abs(z - 11323.126851088331).max() < 1.0e-6 + + def test_local_cartesian_to_global_mult(self): + """Test array conversion from local cartesian to global coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + x, y, z = coords.global_to_local_cartesian(7000.0*arr, 8000.0*arr, + 9000.0*arr, 37.5*arr, + 289.0*arr, 6380.0*arr, + inverse=True) + + assert x.shape == arr.shape + assert y.shape == arr.shape + assert z.shape == arr.shape + assert abs(x + 5709.804676635975).max() < 1.0e-6 + assert abs(y + 4918.397556010223).max() < 1.0e-6 + assert abs(z - 15709.577500484005).max() < 1.0e-6 + def test_global_to_local_cartesian_inverse(self): """Tests the reversibility of the global to loc cartesian transform""" @@ -335,9 +435,9 @@ def test_global_to_local_cartesian_inverse(self): x3, y3, z3 = coords.global_to_local_cartesian(x2, y2, z2, lat, lon, rad, inverse=True) - assert (abs(x1-x3) < 1.0e-6) - assert (abs(y1-y3) < 1.0e-6) - assert (abs(z1-z3) < 1.0e-6) + assert abs(x1-x3) < 1.0e-6 + assert abs(y1-y3) < 1.0e-6 + assert abs(z1-z3) < 1.0e-6 ######################################## # Local Horizontal / Global conversions @@ -352,10 +452,10 @@ def test_local_horizontal_to_global_geo(self): lon0 = 0.0 alt0 = 400.0 - latx, lonx, radx = \ + lat, lon, rad = \ coords.local_horizontal_to_global_geo(az, el, dist, lat0, lon0, alt0) - assert (abs(latx - 50.419037572472625) < 1.0e-6) - assert (abs(lonx + 7.694008395350697) < 1.0e-6) - assert (abs(radx - 7172.15486518744) < 1.0e-6) + assert abs(lat - 50.419037572472625) < 1.0e-6 + assert abs(lon + 7.694008395350697) < 1.0e-6 + assert abs(rad - 7172.15486518744) < 1.0e-6 diff --git a/pysat/utils/coords.py b/pysat/utils/coords.py index 610d8d4..59106c0 100644 --- a/pysat/utils/coords.py +++ b/pysat/utils/coords.py @@ -75,6 +75,66 @@ def update_longitude(inst, lon_name=None, high=180.0, low=-180.0): return +def calc_solar_local_time(inst, lon_name=None, slt_name='slt'): + """ Append solar local time to an instrument object + + Parameters + ------------ + inst : pysat.Instrument instance + instrument object to be updated + lon_name : string + name of the longtiude data key (assumes data are in degrees) + slt_name : string + name of the output solar local time data key (default='slt') + + Returns + --------- + updates instrument data in column specified by slt_name + + """ + import datetime as dt + + if lon_name not in inst.data.keys(): + raise ValueError('uknown longitude variable name') + + # Convert from numpy epoch nanoseconds to UT seconds of day + utsec = list() + for nptime in inst.index.values.astype(int): + # Numpy times come out in nanoseconds and timestamp converts + # from seconds + dtime = dt.datetime.fromtimestamp(nptime * 1.0e-9) + utsec.append((dtime.hour * 3600.0 + dtime.minute * 60.0 + + dtime.second + dtime.microsecond * 1.0e-6) / 3600.0) + + # Calculate solar local time + slt = np.array([t + inst[lon_name][i] / 15.0 for i, t in enumerate(utsec)]) + + # Ensure that solar local time falls between 0 and 24 hours + slt[slt >= 24.0] -= 24.0 + slt[slt < 0.0] += 24.0 + + # Add the solar local time to the instrument + if inst.pandas_format: + inst[slt_name] = pds.Series(slt, index=inst.data.index) + else: + data = inst.data.assign(pysat_slt=(inst.data.coords.keys(), slt)) + data.rename({"pysat_slt": slt_name}, inplace=True) + inst.data = data + + # Add units to the metadata + inst.meta.__setitem__(slt_name, {inst.meta.units_label: 'h', + inst.meta.name_label: "Solar Local Time", + inst.meta.desc_label: "Solar local time", + inst.meta.plot_label: "SLT", + inst.meta.axis_label: "SLT", + inst.meta.scale_label: "linear", + inst.meta.min_label: 0.0, + inst.meta.max_label: 24.0, + inst.meta.fill_label: np.nan}) + + return + + def scale_units(out_unit, in_unit): """ Determine the scaling factor between two units @@ -490,63 +550,3 @@ def local_horizontal_to_global_geo(az, el, dist, lat_orig, lon_orig, alt_orig, rad_pnt = rearth + rad_pnt - 6371.0 return lat_pnt, lon_pnt, rad_pnt - - -def calc_solar_local_time(inst, lon_name=None, slt_name='slt'): - """ Append solar local time to an instrument object - - Parameters - ------------ - inst : pysat.Instrument instance - instrument object to be updated - lon_name : string - name of the longtiude data key (assumes data are in degrees) - slt_name : string - name of the output solar local time data key (default='slt') - - Returns - --------- - updates instrument data in column specified by slt_name - - """ - import datetime as dt - - if lon_name not in inst.data.keys(): - raise ValueError('uknown longitude variable name') - - # Convert from numpy epoch nanoseconds to UT seconds of day - utsec = list() - for nptime in inst.index.values.astype(int): - # Numpy times come out in nanoseconds and timestamp converts - # from seconds - dtime = dt.datetime.fromtimestamp(nptime * 1.0e-9) - utsec.append((dtime.hour * 3600.0 + dtime.minute * 60.0 + - dtime.second + dtime.microsecond * 1.0e-6) / 3600.0) - - # Calculate solar local time - slt = np.array([t + inst[lon_name][i] / 15.0 for i, t in enumerate(utsec)]) - - # Ensure that solar local time falls between 0 and 24 hours - slt[slt >= 24.0] -= 24.0 - slt[slt < 0.0] += 24.0 - - # Add the solar local time to the instrument - if inst.pandas_format: - inst[slt_name] = pds.Series(slt, index=inst.data.index) - else: - data = inst.data.assign(pysat_slt=(inst.data.coords.keys(), slt)) - data.rename({"pysat_slt": slt_name}, inplace=True) - inst.data = data - - # Add units to the metadata - inst.meta.__setitem__(slt_name, {inst.meta.units_label: 'h', - inst.meta.name_label: "Solar Local Time", - inst.meta.desc_label: "Solar local time", - inst.meta.plot_label: "SLT", - inst.meta.axis_label: "SLT", - inst.meta.scale_label: "linear", - inst.meta.min_label: 0.0, - inst.meta.max_label: 24.0, - inst.meta.fill_label: np.nan}) - - return From 99e196f92c54fb805c33b24707fa27d68b28dc93 Mon Sep 17 00:00:00 2001 From: Jeff Klenzing Date: Thu, 21 Feb 2019 06:06:49 -0500 Subject: [PATCH 09/50] test_calc_solar_local_time --- pysat/tests/test_utils_coords.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py index 5b40ba7..eff67fe 100644 --- a/pysat/tests/test_utils_coords.py +++ b/pysat/tests/test_utils_coords.py @@ -79,11 +79,12 @@ def test_bad_lon_name_update_longitude(self): # calc_solar_local_time def test_calc_solar_local_time(self): + """Test calc_solar_local_time""" coords.calc_solar_local_time(self.testInst, lon_name="longitude", slt_name='slt') - target = [17.666667, 18.200278, 18.927222, - 19.034167, 19.334444, 19.659389] + target = np.array([17.666667, 18.200278, 18.927222, + 19.034167, 19.334444, 19.659389]) assert (abs(self.testInst['slt'] - target)).max() < 1.0e-6 From 1ec067c8e194c3ce7e3c9db1989e1b69a6023bef Mon Sep 17 00:00:00 2001 From: jklenzing Date: Thu, 21 Feb 2019 13:21:21 -0500 Subject: [PATCH 10/50] loosen tolerance on test_calc_solar_local_time --- pysat/tests/test_utils_coords.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py index eff67fe..771550b 100644 --- a/pysat/tests/test_utils_coords.py +++ b/pysat/tests/test_utils_coords.py @@ -86,7 +86,7 @@ def test_calc_solar_local_time(self): target = np.array([17.666667, 18.200278, 18.927222, 19.034167, 19.334444, 19.659389]) - assert (abs(self.testInst['slt'] - target)).max() < 1.0e-6 + assert (abs(self.testInst['slt'] - target)).max() < 1.0e-5 @raises(ValueError) def test_bad_lon_name_calc_solar_local_time(self): From d27d4a70eeb0fdfbad8858038cd92bb8a0ae0833 Mon Sep 17 00:00:00 2001 From: Jeff Klenzing Date: Thu, 21 Feb 2019 20:25:37 -0500 Subject: [PATCH 11/50] updated targets --- pysat/tests/test_utils_coords.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py index 771550b..2704949 100644 --- a/pysat/tests/test_utils_coords.py +++ b/pysat/tests/test_utils_coords.py @@ -83,10 +83,10 @@ def test_calc_solar_local_time(self): coords.calc_solar_local_time(self.testInst, lon_name="longitude", slt_name='slt') - target = np.array([17.666667, 18.200278, 18.927222, - 19.034167, 19.334444, 19.659389]) + target = np.array([17.66666667, 18.20027778, 18.92722222, + 19.03416667, 19.33444444, 19.65938889]) - assert (abs(self.testInst['slt'] - target)).max() < 1.0e-5 + assert (abs(self.testInst['slt'] - target)).max() < 1.0e-6 @raises(ValueError) def test_bad_lon_name_calc_solar_local_time(self): From a6f27eafdf38c371712267bd64eb229a0a93b78e Mon Sep 17 00:00:00 2001 From: Jeff Klenzing Date: Fri, 22 Feb 2019 02:10:56 -0500 Subject: [PATCH 12/50] test --- pysat/tests/test_utils_coords.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py index 2704949..78668f4 100644 --- a/pysat/tests/test_utils_coords.py +++ b/pysat/tests/test_utils_coords.py @@ -86,7 +86,7 @@ def test_calc_solar_local_time(self): target = np.array([17.66666667, 18.20027778, 18.92722222, 19.03416667, 19.33444444, 19.65938889]) - assert (abs(self.testInst['slt'] - target)).max() < 1.0e-6 + assert (abs(self.testInst['slt'] - target)).max() < 1.0 @raises(ValueError) def test_bad_lon_name_calc_solar_local_time(self): From 62ddd17c0c1894dd1f1c3dd939c115a4659ba69f Mon Sep 17 00:00:00 2001 From: Jeff Klenzing Date: Fri, 22 Feb 2019 08:32:53 -0500 Subject: [PATCH 13/50] opposite test --- pysat/tests/test_utils_coords.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py index 78668f4..2d7d739 100644 --- a/pysat/tests/test_utils_coords.py +++ b/pysat/tests/test_utils_coords.py @@ -86,7 +86,7 @@ def test_calc_solar_local_time(self): target = np.array([17.66666667, 18.20027778, 18.92722222, 19.03416667, 19.33444444, 19.65938889]) - assert (abs(self.testInst['slt'] - target)).max() < 1.0 + assert (abs(self.testInst['slt'] - target)).max() > 1.0 @raises(ValueError) def test_bad_lon_name_calc_solar_local_time(self): From 76a68d8a689b992ff666ec640978a86e7304df25 Mon Sep 17 00:00:00 2001 From: Jeff Klenzing Date: Fri, 22 Feb 2019 09:40:24 -0500 Subject: [PATCH 14/50] review round 1 --- pysat/tests/test_utils_coords.py | 1 - pysat/utils/coords.py | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py index 2d7d739..7d8381c 100644 --- a/pysat/tests/test_utils_coords.py +++ b/pysat/tests/test_utils_coords.py @@ -3,7 +3,6 @@ """ import numpy as np import pandas as pds -import nose.tools from nose.tools import assert_raises, raises import pysat from pysat.utils import coords, time diff --git a/pysat/utils/coords.py b/pysat/utils/coords.py index 59106c0..7b946e4 100644 --- a/pysat/utils/coords.py +++ b/pysat/utils/coords.py @@ -59,6 +59,7 @@ def update_longitude(inst, lon_name=None, high=180.0, low=-180.0): updates instrument data in column 'lon_name' """ + from pysat.utils.coords import adjust_cyclic_data if lon_name not in inst.data.keys(): @@ -92,6 +93,7 @@ def calc_solar_local_time(inst, lon_name=None, slt_name='slt'): updates instrument data in column specified by slt_name """ + import datetime as dt if lon_name not in inst.data.keys(): @@ -311,6 +313,7 @@ def geodetic_to_geocentric_horizontal(lat_in, lon_in, az_in, el_in, Based on J.M. Ruohoniemi's geopack and R.J. Barnes radar.pro """ + az = np.radians(az_in) el = np.radians(el_in) From 086a2007f48fd2b6ae31333d6c2af0794a182d5b Mon Sep 17 00:00:00 2001 From: jklenzing Date: Fri, 22 Feb 2019 10:38:50 -0500 Subject: [PATCH 15/50] setitem fix --- pysat/tests/test_utils_coords.py | 2 +- pysat/utils/coords.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py index 7d8381c..cc427e0 100644 --- a/pysat/tests/test_utils_coords.py +++ b/pysat/tests/test_utils_coords.py @@ -85,7 +85,7 @@ def test_calc_solar_local_time(self): target = np.array([17.66666667, 18.20027778, 18.92722222, 19.03416667, 19.33444444, 19.65938889]) - assert (abs(self.testInst['slt'] - target)).max() > 1.0 + assert (abs(self.testInst['slt'][0] - target[0])).max() < 1.0e-6 @raises(ValueError) def test_bad_lon_name_calc_solar_local_time(self): diff --git a/pysat/utils/coords.py b/pysat/utils/coords.py index 7b946e4..6ff01c4 100644 --- a/pysat/utils/coords.py +++ b/pysat/utils/coords.py @@ -124,15 +124,15 @@ def calc_solar_local_time(inst, lon_name=None, slt_name='slt'): inst.data = data # Add units to the metadata - inst.meta.__setitem__(slt_name, {inst.meta.units_label: 'h', - inst.meta.name_label: "Solar Local Time", - inst.meta.desc_label: "Solar local time", - inst.meta.plot_label: "SLT", - inst.meta.axis_label: "SLT", - inst.meta.scale_label: "linear", - inst.meta.min_label: 0.0, - inst.meta.max_label: 24.0, - inst.meta.fill_label: np.nan}) + inst.meta[slt_name] = {inst.meta.units_label: 'h', + inst.meta.name_label: "Solar Local Time", + inst.meta.desc_label: "Solar local time", + inst.meta.plot_label: "SLT", + inst.meta.axis_label: "SLT", + inst.meta.scale_label: "linear", + inst.meta.min_label: 0.0, + inst.meta.max_label: 24.0, + inst.meta.fill_label: np.nan}) return @@ -313,7 +313,7 @@ def geodetic_to_geocentric_horizontal(lat_in, lon_in, az_in, el_in, Based on J.M. Ruohoniemi's geopack and R.J. Barnes radar.pro """ - + az = np.radians(az_in) el = np.radians(el_in) From 0e33e06c69407906913427bde042d276ca3a2b2e Mon Sep 17 00:00:00 2001 From: jklenzing Date: Fri, 22 Feb 2019 10:51:27 -0500 Subject: [PATCH 16/50] test --- pysat/tests/test_utils_coords.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py index cc427e0..929dc42 100644 --- a/pysat/tests/test_utils_coords.py +++ b/pysat/tests/test_utils_coords.py @@ -85,7 +85,7 @@ def test_calc_solar_local_time(self): target = np.array([17.66666667, 18.20027778, 18.92722222, 19.03416667, 19.33444444, 19.65938889]) - assert (abs(self.testInst['slt'][0] - target[0])).max() < 1.0e-6 + assert (abs(self.testInst['slt'][-1] - target[-1])).max() < 1.0e-6 @raises(ValueError) def test_bad_lon_name_calc_solar_local_time(self): From dadd14a6212e07c5dab768c74f2b64792c58491f Mon Sep 17 00:00:00 2001 From: jklenzing Date: Fri, 22 Feb 2019 10:58:35 -0500 Subject: [PATCH 17/50] typo --- pysat/utils/coords.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysat/utils/coords.py b/pysat/utils/coords.py index 6ff01c4..f127498 100644 --- a/pysat/utils/coords.py +++ b/pysat/utils/coords.py @@ -132,7 +132,7 @@ def calc_solar_local_time(inst, lon_name=None, slt_name='slt'): inst.meta.scale_label: "linear", inst.meta.min_label: 0.0, inst.meta.max_label: 24.0, - inst.meta.fill_label: np.nan}) + inst.meta.fill_label: np.nan} return From 5bf8a8b1c755c9716f6361a97832a19d4490da65 Mon Sep 17 00:00:00 2001 From: jklenzing Date: Fri, 22 Feb 2019 12:37:00 -0500 Subject: [PATCH 18/50] switch to mod --- pysat/utils/coords.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pysat/utils/coords.py b/pysat/utils/coords.py index f127498..dc60720 100644 --- a/pysat/utils/coords.py +++ b/pysat/utils/coords.py @@ -112,8 +112,9 @@ def calc_solar_local_time(inst, lon_name=None, slt_name='slt'): slt = np.array([t + inst[lon_name][i] / 15.0 for i, t in enumerate(utsec)]) # Ensure that solar local time falls between 0 and 24 hours - slt[slt >= 24.0] -= 24.0 - slt[slt < 0.0] += 24.0 + # slt[slt >= 24.0] -= 24.0 + # slt[slt < 0.0] += 24.0 + slt = np.mod(slt, 24.0) # Add the solar local time to the instrument if inst.pandas_format: From 862af2f59c787ff38e7ec8bf3058df09b0bf36ca Mon Sep 17 00:00:00 2001 From: jklenzing Date: Fri, 22 Feb 2019 12:45:50 -0500 Subject: [PATCH 19/50] restore travis tests --- pysat/tests/test_utils_coords.py | 20 +++++++++++--------- pysat/utils/coords.py | 2 -- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py index 929dc42..3b61f5f 100644 --- a/pysat/tests/test_utils_coords.py +++ b/pysat/tests/test_utils_coords.py @@ -77,15 +77,17 @@ def test_bad_lon_name_update_longitude(self): ######################### # calc_solar_local_time - def test_calc_solar_local_time(self): - """Test calc_solar_local_time""" - - coords.calc_solar_local_time(self.testInst, lon_name="longitude", - slt_name='slt') - target = np.array([17.66666667, 18.20027778, 18.92722222, - 19.03416667, 19.33444444, 19.65938889]) - - assert (abs(self.testInst['slt'][-1] - target[-1])).max() < 1.0e-6 + # NOTE: TURNING OFF THIS TEST FOR NOW WHILE WE DEBUG TRAVIS IMPLEMENTATION + + # def test_calc_solar_local_time(self): + # """Test calc_solar_local_time""" + # + # coords.calc_solar_local_time(self.testInst, lon_name="longitude", + # slt_name='slt') + # target = np.array([17.66666667, 18.20027778, 18.92722222, + # 19.03416667, 19.33444444, 19.65938889]) + # + # assert (abs(self.testInst['slt'] - target)).max() < 1.0e-6 @raises(ValueError) def test_bad_lon_name_calc_solar_local_time(self): diff --git a/pysat/utils/coords.py b/pysat/utils/coords.py index dc60720..0b33e00 100644 --- a/pysat/utils/coords.py +++ b/pysat/utils/coords.py @@ -112,8 +112,6 @@ def calc_solar_local_time(inst, lon_name=None, slt_name='slt'): slt = np.array([t + inst[lon_name][i] / 15.0 for i, t in enumerate(utsec)]) # Ensure that solar local time falls between 0 and 24 hours - # slt[slt >= 24.0] -= 24.0 - # slt[slt < 0.0] += 24.0 slt = np.mod(slt, 24.0) # Add the solar local time to the instrument From 44f068c07891f944fee54920f3c53ccdd0129c4a Mon Sep 17 00:00:00 2001 From: jklenzing Date: Fri, 22 Feb 2019 14:51:44 -0500 Subject: [PATCH 20/50] added unit test --- pysat/tests/test_utils_coords.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py index 3b61f5f..510cd20 100644 --- a/pysat/tests/test_utils_coords.py +++ b/pysat/tests/test_utils_coords.py @@ -444,7 +444,7 @@ def test_global_to_local_cartesian_inverse(self): ######################################## # Local Horizontal / Global conversions - def test_local_horizontal_to_global_geo(self): + def test_local_horizontal_to_global_geo_geodetic(self): """Tests the conversion of the local horizontal to global geo""" az = 30.0 @@ -461,3 +461,22 @@ def test_local_horizontal_to_global_geo(self): assert abs(lat - 50.419037572472625) < 1.0e-6 assert abs(lon + 7.694008395350697) < 1.0e-6 assert abs(rad - 7172.15486518744) < 1.0e-6 + + def test_local_horizontal_to_global_geo(self): + """Tests the conversion of the local horizontal to global geo""" + + az = 30.0 + el = 45.0 + dist = 1000.0 + lat0 = 45.0 + lon0 = 0.0 + alt0 = 400.0 + + lat, lon, rad = \ + coords.local_horizontal_to_global_geo(az, el, dist, + lat0, lon0, alt0, + geodetic=False) + + assert abs(lat - 50.414315865044202) < 1.0e-6 + assert abs(lon + 7.6855551809119502) < 1.0e-6 + assert abs(rad - 7185.6983665760772) < 1.0e-6 From c4bbba7a8b3d9e4da5064d5f123489617993241a Mon Sep 17 00:00:00 2001 From: jklenzing Date: Fri, 22 Feb 2019 15:00:56 -0500 Subject: [PATCH 21/50] add unit testing --- pysat/tests/test_utils_coords.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py index 510cd20..a2bff74 100644 --- a/pysat/tests/test_utils_coords.py +++ b/pysat/tests/test_utils_coords.py @@ -89,6 +89,18 @@ def test_bad_lon_name_update_longitude(self): # # assert (abs(self.testInst['slt'] - target)).max() < 1.0e-6 + def test_calc_solar_local_time_w_update_longitude(self): + """Test calc_solar_local_time with update_longitude""" + + coords.calc_solar_local_time(self.testInst, lon_name="longitude", + slt_name='slt') + coords.update_longitude(self.testInst, lon_name="longitude") + coords.calc_solar_local_time(self.testInst, lon_name="longitude", + slt_name='slt2') + + assert (abs(self.testInst['slt'] + - self.testInst['slt2'])).max() < 1.0e-6 + @raises(ValueError) def test_bad_lon_name_calc_solar_local_time(self): """Test calc_solar_local_time with a bad longitude name""" From 9573c5fc1c47b4d92ef6631ea0d29a34314883f6 Mon Sep 17 00:00:00 2001 From: jklenzing Date: Fri, 22 Feb 2019 15:21:09 -0500 Subject: [PATCH 22/50] fix for #171 --- pysat/tests/test_utils_coords.py | 20 +++++++++----------- pysat/utils/coords.py | 9 ++++----- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py index a2bff74..4c5a4f0 100644 --- a/pysat/tests/test_utils_coords.py +++ b/pysat/tests/test_utils_coords.py @@ -77,17 +77,15 @@ def test_bad_lon_name_update_longitude(self): ######################### # calc_solar_local_time - # NOTE: TURNING OFF THIS TEST FOR NOW WHILE WE DEBUG TRAVIS IMPLEMENTATION - - # def test_calc_solar_local_time(self): - # """Test calc_solar_local_time""" - # - # coords.calc_solar_local_time(self.testInst, lon_name="longitude", - # slt_name='slt') - # target = np.array([17.66666667, 18.20027778, 18.92722222, - # 19.03416667, 19.33444444, 19.65938889]) - # - # assert (abs(self.testInst['slt'] - target)).max() < 1.0e-6 + def test_calc_solar_local_time(self): + """Test calc_solar_local_time""" + + coords.calc_solar_local_time(self.testInst, lon_name="longitude", + slt_name='slt') + target = np.array([22.66666667, 23.20027778, 23.92722222, + 0.03416667, 0.33444444, 0.65938889]) + + assert (abs(self.testInst['slt'] - target)).max() < 1.0e-6 def test_calc_solar_local_time_w_update_longitude(self): """Test calc_solar_local_time with update_longitude""" diff --git a/pysat/utils/coords.py b/pysat/utils/coords.py index 0b33e00..8e60fad 100644 --- a/pysat/utils/coords.py +++ b/pysat/utils/coords.py @@ -100,16 +100,15 @@ def calc_solar_local_time(inst, lon_name=None, slt_name='slt'): raise ValueError('uknown longitude variable name') # Convert from numpy epoch nanoseconds to UT seconds of day - utsec = list() + ut_hr = list() for nptime in inst.index.values.astype(int): # Numpy times come out in nanoseconds and timestamp converts # from seconds - dtime = dt.datetime.fromtimestamp(nptime * 1.0e-9) - utsec.append((dtime.hour * 3600.0 + dtime.minute * 60.0 + + dtime = dt.datetime.utcfromtimestamp(nptime * 1.0e-9) + ut_hr.append((dtime.hour * 3600.0 + dtime.minute * 60.0 + dtime.second + dtime.microsecond * 1.0e-6) / 3600.0) - # Calculate solar local time - slt = np.array([t + inst[lon_name][i] / 15.0 for i, t in enumerate(utsec)]) + slt = np.array([t + inst[lon_name][i] / 15.0 for i, t in enumerate(ut_hr)]) # Ensure that solar local time falls between 0 and 24 hours slt = np.mod(slt, 24.0) From 173fa321ec1f29be0d5eee127f0b6d29941e1046 Mon Sep 17 00:00:00 2001 From: Jeff Klenzing Date: Fri, 1 Mar 2019 17:21:45 -0500 Subject: [PATCH 23/50] Merge branch 'develop' into documentation --- pysat/tests/test_utils_coords.py | 492 +++++++++++++++++++++++++++ pysat/utils/coords.py | 553 +++++++++++++++++++++++++++++++ 2 files changed, 1045 insertions(+) create mode 100644 pysat/tests/test_utils_coords.py create mode 100644 pysat/utils/coords.py diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py new file mode 100644 index 0000000..4c5a4f0 --- /dev/null +++ b/pysat/tests/test_utils_coords.py @@ -0,0 +1,492 @@ +""" +tests the pysat coords area +""" +import numpy as np +import pandas as pds +from nose.tools import assert_raises, raises +import pysat +from pysat.utils import coords, time + + +class TestBasics(): + def setup(self): + """Runs before every method to create a clean testing setup.""" + self.test_angles = np.array([340.0, 348.0, 358.9, 0.5, 5.0, 9.87]) + + self.testInst = pysat.Instrument(platform='pysat', + name='testing', + clean_level='clean') + # Add longitude to the test instrument + ones = np.ones(shape=len(self.test_angles)) + tind = time.create_datetime_index(year=ones*2001, + month=ones, + uts=np.arange(0.0, len(ones), 1.0)) + + self.testInst.data = \ + pds.DataFrame(np.array([tind, self.test_angles]).transpose(), + index=tind, columns=["time", "longitude"]) + + self.deg_units = ["deg", "degree", "degrees", "rad", "radian", + "radians", "h", "hr", "hrs", "hours"] + self.dist_units = ["m", "km", "cm"] + self.vel_units = ["m/s", "cm/s", "km/s"] + + def teardown(self): + """Runs after every method to clean up previous testing.""" + del self.test_angles, self.testInst + del self.deg_units, self.dist_units, self.vel_units + + ##################################### + # Cyclic data conversions + + def test_adjust_cyclic_data_default(self): + """ Test adjust_cyclic_data with default range """ + + test_in = np.radians(self.test_angles) - np.pi + test_angles = coords.adjust_cyclic_data(test_in) + + assert test_angles.max() < 2.0 * np.pi + assert test_angles.min() >= 0.0 + + def test_adjust_cyclic_data_custom(self): + """ Test adjust_cyclic_data with a custom range """ + + test_angles = coords.adjust_cyclic_data(self.test_angles, + high=180.0, low=-180.0) + + assert test_angles.max() < 180.0 + assert test_angles.min() >= -180.0 + + ##################################### + # Update Longitude + + def test_update_longitude(self): + """Test update_longitude """ + + coords.update_longitude(self.testInst, lon_name="longitude") + + assert np.all(self.testInst.data['longitude'] < 180.0) + assert np.all(self.testInst.data['longitude'] >= -180.0) + + @raises(ValueError) + def test_bad_lon_name_update_longitude(self): + """Test update_longitude with a bad longitude name""" + + coords.update_longitude(self.testInst, lon_name="not longitude") + + ######################### + # calc_solar_local_time + + def test_calc_solar_local_time(self): + """Test calc_solar_local_time""" + + coords.calc_solar_local_time(self.testInst, lon_name="longitude", + slt_name='slt') + target = np.array([22.66666667, 23.20027778, 23.92722222, + 0.03416667, 0.33444444, 0.65938889]) + + assert (abs(self.testInst['slt'] - target)).max() < 1.0e-6 + + def test_calc_solar_local_time_w_update_longitude(self): + """Test calc_solar_local_time with update_longitude""" + + coords.calc_solar_local_time(self.testInst, lon_name="longitude", + slt_name='slt') + coords.update_longitude(self.testInst, lon_name="longitude") + coords.calc_solar_local_time(self.testInst, lon_name="longitude", + slt_name='slt2') + + assert (abs(self.testInst['slt'] + - self.testInst['slt2'])).max() < 1.0e-6 + + @raises(ValueError) + def test_bad_lon_name_calc_solar_local_time(self): + """Test calc_solar_local_time with a bad longitude name""" + + coords.calc_solar_local_time(self.testInst, lon_name="not longitude", + slt_name='slt') + + ##################################### + # Scale units + + def test_scale_units_same(self): + """ Test scale_units when both units are the same """ + + scale = coords.scale_units("happy", "happy") + + assert scale == 1.0 + + def test_scale_units_angles(self): + """Test scale_units for angles """ + + for out_unit in self.deg_units: + scale = coords.scale_units(out_unit, "deg") + + if out_unit.find("deg") == 0: + assert scale == 1.0 + elif out_unit.find("rad") == 0: + assert scale == np.pi / 180.0 + else: + assert scale == 1.0 / 15.0 + + def test_scale_units_dist(self): + """Test scale_units for distances """ + + for out_unit in self.dist_units: + scale = coords.scale_units(out_unit, "m") + + if out_unit == "m": + assert scale == 1.0 + elif out_unit.find("km") == 0: + assert scale == 0.001 + else: + assert scale == 100.0 + + def test_scale_units_vel(self): + """Test scale_units for velocities """ + + for out_unit in self.vel_units: + scale = coords.scale_units(out_unit, "m/s") + + if out_unit == "m/s": + assert scale == 1.0 + elif out_unit.find("km/s") == 0: + assert scale == 0.001 + else: + assert scale == 100.0 + + def test_scale_units_bad(self): + """Test scale_units for mismatched input""" + + assert_raises(ValueError, coords.scale_units, "happy", "m") + assert_raises(ValueError, coords.scale_units, "m", "happy") + assert_raises(ValueError, coords.scale_units, "m", "m/s") + assert_raises(ValueError, coords.scale_units, "m", "deg") + assert_raises(ValueError, coords.scale_units, "h", "km/s") + + ##################################### + # Geodetic / Geocentric conversions + + def test_geodetic_to_geocentric_single(self): + """Test conversion from geodetic to geocentric coordinates""" + + lat, lon, rad = coords.geodetic_to_geocentric(45.0, lon_in=8.0) + + assert abs(lat - 44.807576784018046) < 1.0e-6 + assert abs(lon - 8.0) < 1.0e-6 + assert abs(rad - 6367.489543863465) < 1.0e-6 + + def test_geocentric_to_geodetic_single(self): + """Test conversion from geocentric to geodetic coordinates""" + + lat, lon, rad = coords.geodetic_to_geocentric(45.0, lon_in=8.0, + inverse=True) + + assert abs(lat - 45.192423215981954) < 1.0e-6 + assert abs(lon - 8.0) < 1.0e-6 + assert abs(rad - 6367.345908499981) < 1.0e-6 + + def test_geodetic_to_geocentric_mult(self): + """Test array conversion from geodetic to geocentric coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + lat, lon, rad = coords.geodetic_to_geocentric(45.0*arr, lon_in=8.0*arr) + + assert lat.shape == arr.shape + assert lon.shape == arr.shape + assert rad.shape == arr.shape + assert abs(lat - 44.807576784018046).max() < 1.0e-6 + assert abs(lon - 8.0).max() < 1.0e-6 + assert abs(rad - 6367.489543863465).max() < 1.0e-6 + + def test_geocentric_to_geodetic_mult(self): + """Test array conversion from geocentric to geodetic coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + lat, lon, rad = coords.geodetic_to_geocentric(45.0*arr, lon_in=8.0*arr, + inverse=True) + + assert lat.shape == arr.shape + assert lon.shape == arr.shape + assert rad.shape == arr.shape + assert abs(lat - 45.192423215981954).max() < 1.0e-6 + assert abs(lon - 8.0).max() < 1.0e-6 + assert abs(rad - 6367.345908499981).max() < 1.0e-6 + + def test_geodetic_to_geocentric_inverse(self): + """Tests the reversibility of geodetic to geocentric conversions""" + + lat1 = 37.5 + lon1 = 117.3 + lat2, lon2, rad_e = coords.geodetic_to_geocentric(lat1, lon_in=lon1, + inverse=False) + lat3, lon3, rad_e = coords.geodetic_to_geocentric(lat2, lon_in=lon2, + inverse=True) + assert abs(lon1-lon3) < 1.0e-6 + assert abs(lat1-lat3) < 1.0e-6 + + ############################################### + # Geodetic / Geocentric Horizontal conversions + + def test_geodetic_to_geocentric_horz_single(self): + """Test conversion from geodetic to geocentric coordinates""" + + lat, lon, rad, az, el = \ + coords.geodetic_to_geocentric_horizontal(45.0, 8.0, 52.0, 63.0) + + assert abs(lat - 44.807576784018046) < 1.0e-6 + assert abs(lon - 8.0) < 1.0e-6 + assert abs(rad - 6367.489543863465) < 1.0e-6 + assert abs(az - 51.70376774257361) < 1.0e-6 + assert abs(el - 62.8811403841008) < 1.0e-6 + + def test_geocentric_to_geodetic_horz_single(self): + """Test conversion from geocentric to geodetic coordinates""" + + lat, lon, rad, az, el = \ + coords.geodetic_to_geocentric_horizontal(45.0, 8.0, 52.0, 63.0, + inverse=True) + + assert abs(lat - 45.192423215981954) < 1.0e-6 + assert abs(lon - 8.0) < 1.0e-6 + assert abs(rad - 6367.345908499981) < 1.0e-6 + assert abs(az - 52.29896101551479) < 1.0e-6 + assert abs(el - 63.118072033649916) < 1.0e-6 + + def test_geodetic_to_geocentric_horz_mult(self): + """Test array conversion from geodetic to geocentric coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + lat, lon, rad, az, el = \ + coords.geodetic_to_geocentric_horizontal(45.0*arr, 8.0*arr, + 52.0*arr, 63.0*arr) + + assert lat.shape == arr.shape + assert lon.shape == arr.shape + assert rad.shape == arr.shape + assert az.shape == arr.shape + assert el.shape == arr.shape + assert abs(lat - 44.807576784018046).max() < 1.0e-6 + assert abs(lon - 8.0).max() < 1.0e-6 + assert abs(rad - 6367.489543863465).max() < 1.0e-6 + assert abs(az - 51.70376774257361).max() < 1.0e-6 + assert abs(el - 62.8811403841008).max() < 1.0e-6 + + def test_geocentric_to_geodetic_horz_mult(self): + """Test array conversion from geocentric to geodetic coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + lat, lon, rad, az, el = \ + coords.geodetic_to_geocentric_horizontal(45.0*arr, 8.0*arr, + 52.0*arr, 63.0*arr, + inverse=True) + + assert lat.shape == arr.shape + assert lon.shape == arr.shape + assert rad.shape == arr.shape + assert az.shape == arr.shape + assert el.shape == arr.shape + assert abs(lat - 45.192423215981954).max() < 1.0e-6 + assert abs(lon - 8.0).max() < 1.0e-6 + assert abs(rad - 6367.345908499981).max() < 1.0e-6 + assert abs(az - 52.29896101551479).max() < 1.0e-6 + assert abs(el - 63.118072033649916).max() < 1.0e-6 + + def test_geodetic_to_geocentric_horizontal_inverse(self): + """Tests the reversibility of geodetic to geocentric horiz conversions + + Note: inverse of az and el angles currently non-functional""" + + lat1 = -17.5 + lon1 = 187.3 + az1 = 52.0 + el1 = 63.0 + lat2, lon2, rad_e, az2, el2 = \ + coords.geodetic_to_geocentric_horizontal(lat1, lon1, az1, el1, + inverse=False) + lat3, lon3, rad_e, az3, el3 = \ + coords.geodetic_to_geocentric_horizontal(lat2, lon2, az2, el2, + inverse=True) + + assert abs(lon1-lon3) < 1.0e-6 + assert abs(lat1-lat3) < 1.0e-6 + assert abs(az1-az3) < 1.0e-6 + assert abs(el1-el3) < 1.0e-6 + + #################################### + # Spherical / Cartesian conversions + + def test_spherical_to_cartesian_single(self): + """Test conversion from spherical to cartesian coordinates""" + + x, y, z = coords.spherical_to_cartesian(45.0, 30.0, 1.0) + + assert abs(x - y) < 1.0e-6 + assert abs(z - 0.5) < 1.0e-6 + + def test_cartesian_to_spherical_single(self): + """Test conversion from cartesian to spherical coordinates""" + + x = 0.6123724356957946 + az, el, r = coords.spherical_to_cartesian(x, x, 0.5, + inverse=True) + + assert abs(az - 45.0) < 1.0e-6 + assert abs(el - 30.0) < 1.0e-6 + assert abs(r - 1.0) < 1.0e-6 + + def test_spherical_to_cartesian_mult(self): + """Test array conversion from spherical to cartesian coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + x, y, z = coords.spherical_to_cartesian(45.0*arr, 30.0*arr, arr) + + assert x.shape == arr.shape + assert y.shape == arr.shape + assert z.shape == arr.shape + assert abs(x - y).max() < 1.0e-6 + assert abs(z - 0.5).max() < 1.0e-6 + + def test_cartesian_to_spherical_mult(self): + """Test array conversion from cartesian to spherical coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + x = 0.6123724356957946 + az, el, r = coords.spherical_to_cartesian(x*arr, x*arr, 0.5*arr, + inverse=True) + + assert az.shape == arr.shape + assert el.shape == arr.shape + assert r.shape == arr.shape + assert abs(az - 45.0).max() < 1.0e-6 + assert abs(el - 30.0).max() < 1.0e-6 + assert abs(r - 1.0).max() < 1.0e-6 + + def test_spherical_to_cartesian_inverse(self): + """Tests the reversibility of spherical to cartesian conversions""" + + x1 = 3000.0 + y1 = 2000.0 + z1 = 2500.0 + az, el, r = coords.spherical_to_cartesian(x1, y1, z1, + inverse=True) + x2, y2, z2 = coords.spherical_to_cartesian(az, el, r, + inverse=False) + + assert abs(x1-x2) < 1.0e-6 + assert abs(y1-y2) < 1.0e-6 + assert abs(z1-z2) < 1.0e-6 + + ######################################## + # Global / Local Cartesian conversions + + def test_global_to_local_cartesian_single(self): + """Test conversion from global to local cartesian coordinates""" + + x, y, z = coords.global_to_local_cartesian(7000.0, 8000.0, 9000.0, + 37.5, 289.0, 6380.0) + + assert abs(x + 9223.175264852474) < 1.0e-6 + assert abs(y + 2239.835278362686) < 1.0e-6 + assert abs(z - 11323.126851088331) < 1.0e-6 + + def test_local_cartesian_to_global_single(self): + """Test conversion from local cartesian to global coordinates""" + + x, y, z = coords.global_to_local_cartesian(7000.0, 8000.0, 9000.0, + 37.5, 289.0, 6380.0, + inverse=True) + + assert abs(x + 5709.804676635975) < 1.0e-6 + assert abs(y + 4918.397556010223) < 1.0e-6 + assert abs(z - 15709.577500484005) < 1.0e-6 + + def test_global_to_local_cartesian_mult(self): + """Test array conversion from global to local cartesian coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + x, y, z = coords.global_to_local_cartesian(7000.0*arr, 8000.0*arr, + 9000.0*arr, 37.5*arr, + 289.0*arr, 6380.0*arr) + + assert x.shape == arr.shape + assert y.shape == arr.shape + assert z.shape == arr.shape + assert abs(x + 9223.175264852474).max() < 1.0e-6 + assert abs(y + 2239.835278362686).max() < 1.0e-6 + assert abs(z - 11323.126851088331).max() < 1.0e-6 + + def test_local_cartesian_to_global_mult(self): + """Test array conversion from local cartesian to global coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + x, y, z = coords.global_to_local_cartesian(7000.0*arr, 8000.0*arr, + 9000.0*arr, 37.5*arr, + 289.0*arr, 6380.0*arr, + inverse=True) + + assert x.shape == arr.shape + assert y.shape == arr.shape + assert z.shape == arr.shape + assert abs(x + 5709.804676635975).max() < 1.0e-6 + assert abs(y + 4918.397556010223).max() < 1.0e-6 + assert abs(z - 15709.577500484005).max() < 1.0e-6 + + def test_global_to_local_cartesian_inverse(self): + """Tests the reversibility of the global to loc cartesian transform""" + + x1 = 7000.0 + y1 = 8000.0 + z1 = 9500.0 + lat = 37.5 + lon = 289.0 + rad = 6380.0 + x2, y2, z2 = coords.global_to_local_cartesian(x1, y1, z1, + lat, lon, rad, + inverse=False) + x3, y3, z3 = coords.global_to_local_cartesian(x2, y2, z2, + lat, lon, rad, + inverse=True) + assert abs(x1-x3) < 1.0e-6 + assert abs(y1-y3) < 1.0e-6 + assert abs(z1-z3) < 1.0e-6 + + ######################################## + # Local Horizontal / Global conversions + + def test_local_horizontal_to_global_geo_geodetic(self): + """Tests the conversion of the local horizontal to global geo""" + + az = 30.0 + el = 45.0 + dist = 1000.0 + lat0 = 45.0 + lon0 = 0.0 + alt0 = 400.0 + + lat, lon, rad = \ + coords.local_horizontal_to_global_geo(az, el, dist, + lat0, lon0, alt0) + + assert abs(lat - 50.419037572472625) < 1.0e-6 + assert abs(lon + 7.694008395350697) < 1.0e-6 + assert abs(rad - 7172.15486518744) < 1.0e-6 + + def test_local_horizontal_to_global_geo(self): + """Tests the conversion of the local horizontal to global geo""" + + az = 30.0 + el = 45.0 + dist = 1000.0 + lat0 = 45.0 + lon0 = 0.0 + alt0 = 400.0 + + lat, lon, rad = \ + coords.local_horizontal_to_global_geo(az, el, dist, + lat0, lon0, alt0, + geodetic=False) + + assert abs(lat - 50.414315865044202) < 1.0e-6 + assert abs(lon + 7.6855551809119502) < 1.0e-6 + assert abs(rad - 7185.6983665760772) < 1.0e-6 diff --git a/pysat/utils/coords.py b/pysat/utils/coords.py new file mode 100644 index 0000000..8e60fad --- /dev/null +++ b/pysat/utils/coords.py @@ -0,0 +1,553 @@ +""" +pysat.coords - coordinate transformations for pysat +========================================= + +pysat.coords contains a number of coordinate-transformation +functions used throughout the pysat package. +""" + +import numpy as np +import pandas as pds + + +def adjust_cyclic_data(samples, high=2.0*np.pi, low=0.0): + """Adjust cyclic values such as longitude to a different scale + + Parameters + ----------- + samples : array_like + Input array + high: float or int + Upper boundary for circular standard deviation range (default=2 pi) + low : float or int + Lower boundary for circular standard deviation range (default=0) + axis : int or NoneType + Axis along which standard deviations are computed. The default is to + compute the standard deviation of the flattened array + + Returns + -------- + out_samples : float + Circular standard deviation + + """ + + out_samples = np.asarray(samples) + sample_range = high - low + out_samples[out_samples >= high] -= sample_range + out_samples[out_samples < low] += sample_range + + return out_samples + + +def update_longitude(inst, lon_name=None, high=180.0, low=-180.0): + """ Update longitude to the desired range + + Parameters + ------------ + inst : pysat.Instrument instance + instrument object to be updated + lon_name : string + name of the longtiude data + high : float + Highest allowed longitude value (default=180.0) + low : float + Lowest allowed longitude value (default=-180.0) + + Returns + --------- + updates instrument data in column 'lon_name' + + """ + + from pysat.utils.coords import adjust_cyclic_data + + if lon_name not in inst.data.keys(): + raise ValueError('uknown longitude variable name') + + new_lon = adjust_cyclic_data(inst[lon_name], high=high, low=low) + + # Update based on data type + if inst.pandas_format: + inst[lon_name] = new_lon + else: + inst[lon_name].data = new_lon + + return + + +def calc_solar_local_time(inst, lon_name=None, slt_name='slt'): + """ Append solar local time to an instrument object + + Parameters + ------------ + inst : pysat.Instrument instance + instrument object to be updated + lon_name : string + name of the longtiude data key (assumes data are in degrees) + slt_name : string + name of the output solar local time data key (default='slt') + + Returns + --------- + updates instrument data in column specified by slt_name + + """ + + import datetime as dt + + if lon_name not in inst.data.keys(): + raise ValueError('uknown longitude variable name') + + # Convert from numpy epoch nanoseconds to UT seconds of day + ut_hr = list() + for nptime in inst.index.values.astype(int): + # Numpy times come out in nanoseconds and timestamp converts + # from seconds + dtime = dt.datetime.utcfromtimestamp(nptime * 1.0e-9) + ut_hr.append((dtime.hour * 3600.0 + dtime.minute * 60.0 + + dtime.second + dtime.microsecond * 1.0e-6) / 3600.0) + # Calculate solar local time + slt = np.array([t + inst[lon_name][i] / 15.0 for i, t in enumerate(ut_hr)]) + + # Ensure that solar local time falls between 0 and 24 hours + slt = np.mod(slt, 24.0) + + # Add the solar local time to the instrument + if inst.pandas_format: + inst[slt_name] = pds.Series(slt, index=inst.data.index) + else: + data = inst.data.assign(pysat_slt=(inst.data.coords.keys(), slt)) + data.rename({"pysat_slt": slt_name}, inplace=True) + inst.data = data + + # Add units to the metadata + inst.meta[slt_name] = {inst.meta.units_label: 'h', + inst.meta.name_label: "Solar Local Time", + inst.meta.desc_label: "Solar local time", + inst.meta.plot_label: "SLT", + inst.meta.axis_label: "SLT", + inst.meta.scale_label: "linear", + inst.meta.min_label: 0.0, + inst.meta.max_label: 24.0, + inst.meta.fill_label: np.nan} + + return + + +def scale_units(out_unit, in_unit): + """ Determine the scaling factor between two units + + Parameters + ------------- + out_unit : str + Desired unit after scaling + in_unit : str + Unit to be scaled + + Returns + ----------- + unit_scale : float + Scaling factor that will convert from in_units to out_units + + Notes + ------- + Accepted units include degrees ('deg', 'degree', 'degrees'), + radians ('rad', 'radian', 'radians'), + hours ('h', 'hr', 'hrs', 'hour', 'hours'), and lengths ('m', 'km', 'cm'). + Can convert between degrees, radians, and hours or different lengths. + + Example + ----------- + :: + import numpy as np + two_pi = 2.0 * np.pi + scale = scale_units("deg", "RAD") + two_pi *= scale + two_pi # will show 360.0 + + + """ + + if out_unit == in_unit: + return 1.0 + + accepted_units = {'deg': ['deg', 'degree', 'degrees'], + 'rad': ['rad', 'radian', 'radians'], + 'h': ['h', 'hr', 'hrs', 'hours'], + 'm': ['m', 'km', 'cm'], + 'm/s': ['m/s', 'cm/s', 'km/s']} + + scales = {'deg': 180.0, 'rad': np.pi, 'h': 12.0, + 'm': 1.0, 'km': 0.001, 'cm': 100.0, + 'm/s': 1.0, 'cm/s': 100.0, 'km/s': 0.001} + + # Test input and determine transformation type + out_key = None + in_key = None + for kk in accepted_units.keys(): + if out_unit.lower() in accepted_units[kk]: + out_key = kk + if in_unit.lower() in accepted_units[kk]: + in_key = kk + + if out_key is None: + raise ValueError('Unknown output unit {:}'.format(out_unit)) + + if in_key is None: + raise ValueError('Unknown input unit {:}'.format(in_unit)) + + if out_key == 'm' or out_key == 'm/s' or in_key == 'm' or in_key == 'm/s': + if in_key != out_key: + raise ValueError('Cannot scale {:s} and {:s}'.format(out_unit, + in_unit)) + # Recast units as keys for the scales dictionary + out_key = out_unit + in_key = in_unit + + unit_scale = scales[out_key.lower()] / scales[in_key.lower()] + + return unit_scale + + +def geodetic_to_geocentric(lat_in, lon_in=None, inverse=False): + """Converts position from geodetic to geocentric or vice-versa. + + Parameters + ---------- + lat_in : float + latitude in degrees. + lon_in : float or NoneType + longitude in degrees. Remains unchanged, so does not need to be + included. (default=None) + inverse : bool + False for geodetic to geocentric, True for geocentric to geodetic. + (default=False) + + Returns + ------- + lat_out : float + latitude [degree] (geocentric/detic if inverse=False/True) + lon_out : float or NoneType + longitude [degree] (geocentric/detic if inverse=False/True) + rad_earth : float + Earth radius [km] (geocentric/detic if inverse=False/True) + + Notes + ----- + Uses WGS-84 values + + References + ---------- + Based on J.M. Ruohoniemi's geopack and R.J. Barnes radar.pro + + """ + rad_eq = 6378.1370 # WGS-84 semi-major axis + flat = 1.0 / 298.257223563 # WGS-84 flattening + rad_pol = rad_eq * (1.0 - flat) # WGS-84 semi-minor axis + + # The ratio between the semi-major and minor axis is used several times + rad_ratio_sq = (rad_eq / rad_pol)**2 + + # Calculate the square of the second eccentricity (e') + eprime_sq = rad_ratio_sq - 1.0 + + # Calculate the tangent of the input latitude + tan_in = np.tan(np.radians(lat_in)) + + # If converting from geodetic to geocentric, take the inverse of the + # radius ratio + if not inverse: + rad_ratio_sq = 1.0 / rad_ratio_sq + + # Calculate the output latitude + lat_out = np.degrees(np.arctan(rad_ratio_sq * tan_in)) + + # Calculate the Earth radius at this latitude + rad_earth = rad_eq / np.sqrt(1.0 + eprime_sq * + np.sin(np.radians(lat_out))**2) + + # longitude remains unchanged + lon_out = lon_in + + return lat_out, lon_out, rad_earth + + +def geodetic_to_geocentric_horizontal(lat_in, lon_in, az_in, el_in, + inverse=False): + """Converts from local horizontal coordinates in a geodetic system to local + horizontal coordinates in a geocentric system + + Parameters + ---------- + lat_in : float + latitude in degrees of the local horizontal coordinate system center + lon_in : float + longitude in degrees of the local horizontal coordinate system center + az_in : float + azimuth in degrees within the local horizontal coordinate system + el_in : float + elevation in degrees within the local horizontal coordinate system + inverse : bool + False for geodetic to geocentric, True for inverse (default=False) + + Returns + ------- + lat_out : float + latitude in degrees of the converted horizontal coordinate system + center + lon_out : float + longitude in degrees of the converted horizontal coordinate system + center + rad_earth : float + Earth radius in km at the geocentric/detic (False/True) location + az_out : float + azimuth in degrees of the converted horizontal coordinate system + el_out : float + elevation in degrees of the converted horizontal coordinate system + + References + ---------- + Based on J.M. Ruohoniemi's geopack and R.J. Barnes radar.pro + + """ + + az = np.radians(az_in) + el = np.radians(el_in) + + # Transform the location of the local horizontal coordinate system center + lat_out, lon_out, rad_earth = geodetic_to_geocentric(lat_in, lon_in, + inverse=inverse) + + # Calcualte the deviation from vertical in radians + dev_vert = np.radians(lat_in - lat_out) + + # Calculate cartesian coordinated in local system + x_local = np.cos(el) * np.sin(az) + y_local = np.cos(el) * np.cos(az) + z_local = np.sin(el) + + # Now rotate system about the x axis to align local vertical vector + # with Earth radial vector + x_out = x_local + y_out = y_local * np.cos(dev_vert) + z_local * np.sin(dev_vert) + z_out = -y_local * np.sin(dev_vert) + z_local * np.cos(dev_vert) + + # Transform the azimuth and elevation angles + az_out = np.degrees(np.arctan2(x_out, y_out)) + el_out = np.degrees(np.arctan(z_out / np.sqrt(x_out**2 + y_out**2))) + + return lat_out, lon_out, rad_earth, az_out, el_out + + +def spherical_to_cartesian(az_in, el_in, r_in, inverse=False): + """Convert a position from spherical to cartesian, or vice-versa + + Parameters + ---------- + az_in : float + azimuth/longitude in degrees or cartesian x in km (inverse=False/True) + el_in : float + elevation/latitude in degrees or cartesian y in km (inverse=False/True) + r_in : float + distance from origin in km or cartesian z in km (inverse=False/True) + inverse : boolian + False to go from spherical to cartesian and True for the inverse + + Returns + ------- + x_out : float + cartesian x in km or azimuth/longitude in degrees (inverse=False/True) + y_out : float + cartesian y in km or elevation/latitude in degrees (inverse=False/True) + z_out : float + cartesian z in km or distance from origin in km (inverse=False/True) + + Notes + ------ + This transform is the same for local or global spherical/cartesian + transformations. + + Returns elevation angle (angle from the xy plane) rather than zenith angle + (angle from the z-axis) + + """ + + if inverse: + # Cartesian to Spherical + xy_sq = az_in**2 + el_in**2 + z_out = np.sqrt(xy_sq + r_in**2) # This is r + y_out = np.degrees(np.arctan2(np.sqrt(xy_sq), r_in)) # This is zenith + y_out = 90.0 - y_out # This is the elevation + x_out = np.degrees(np.arctan2(el_in, az_in)) # This is azimuth + else: + # Spherical coordinate system uses zenith angle (degrees from the + # z-axis) and not the elevation angle (degrees from the x-y plane) + zen_in = np.radians(90.0 - el_in) + + # Spherical to Cartesian + x_out = r_in * np.sin(zen_in) * np.cos(np.radians(az_in)) + y_out = r_in * np.sin(zen_in) * np.sin(np.radians(az_in)) + z_out = r_in * np.cos(zen_in) + + return x_out, y_out, z_out + + +def global_to_local_cartesian(x_in, y_in, z_in, lat_cent, lon_cent, rad_cent, + inverse=False): + """Converts a position from global to local cartesian or vice-versa + + Parameters + ---------- + x_in : float + global or local cartesian x in km (inverse=False/True) + y_in : float + global or local cartesian y in km (inverse=False/True) + z_in : float + global or local cartesian z in km (inverse=False/True) + lat_cent : float + geocentric latitude in degrees of local cartesian system origin + lon_cent : float + geocentric longitude in degrees of local cartesian system origin + rad_cent : float + distance from center of the Earth in km of local cartesian system + origin + inverse : bool + False to convert from global to local cartesian coodiantes, and True + for the inverse (default=False) + + Returns + ------- + x_out : float + local or global cartesian x in km (inverse=False/True) + y_out : float + local or global cartesian y in km (inverse=False/True) + z_out : float + local or global cartesian z in km (inverse=False/True) + + Notes + ------- + The global cartesian coordinate system has its origin at the center of the + Earth, while the local system has its origin specified by the input + latitude, longitude, and radius. The global system has x intersecting + the equatorial plane and the prime meridian, z pointing North along the + rotational axis, and y completing the right-handed coodinate system. + The local system has z pointing up, y pointing North, and x pointing East. + + """ + + # Get the global cartesian coordinates of local origin + x_cent, y_cent, z_cent = spherical_to_cartesian(lon_cent, lat_cent, + rad_cent) + + # Get the amount of rotation needed to align the x-axis with the + # Earth's rotational axis + ax_rot = np.radians(90.0 - lat_cent) + + # Get the amount of rotation needed to align the global x-axis with the + # prime meridian + mer_rot = np.radians(lon_cent - 90.0) + + if inverse: + # Rotate about the x-axis to align the z-axis with the Earth's + # rotational axis + xrot = x_in + yrot = y_in * np.cos(ax_rot) - z_in * np.sin(ax_rot) + zrot = y_in * np.sin(ax_rot) + z_in * np.cos(ax_rot) + + # Rotate about the global z-axis to get the global x-axis aligned + # with the prime meridian and translate the local center to the + # global origin + x_out = xrot * np.cos(mer_rot) - yrot * np.sin(mer_rot) + x_cent + y_out = xrot * np.sin(mer_rot) + yrot * np.cos(mer_rot) + y_cent + z_out = zrot + z_cent + else: + # Translate global origin to the local origin + xtrans = x_in - x_cent + ytrans = y_in - y_cent + ztrans = z_in - z_cent + + # Rotate about the global z-axis to get the local x-axis pointing East + xrot = xtrans * np.cos(-mer_rot) - ytrans * np.sin(-mer_rot) + yrot = xtrans * np.sin(-mer_rot) + ytrans * np.cos(-mer_rot) + zrot = ztrans + + # Rotate about the x-axis to get the z-axis pointing up + x_out = xrot + y_out = yrot * np.cos(-ax_rot) - zrot * np.sin(-ax_rot) + z_out = yrot * np.sin(-ax_rot) + zrot * np.cos(-ax_rot) + + return x_out, y_out, z_out + + +def local_horizontal_to_global_geo(az, el, dist, lat_orig, lon_orig, alt_orig, + geodetic=True): + """ Convert from local horizontal coordinates to geodetic or geocentric + coordinates + + Parameters + ---------- + az : float + Azimuth (angle from North) of point in degrees + el : float + Elevation (angle from ground) of point in degrees + dist : float + Distance from origin to point in km + lat_orig : float + Latitude of origin in degrees + lon_orig : float + Longitude of origin in degrees + alt_orig : float + Altitude of origin in km from the surface of the Earth + geodetic : bool + True if origin coordinates are geodetic, False if they are geocentric. + Will return coordinates in the same system as the origin input. + (default=True) + + Returns + ------- + lat_pnt : float + Latitude of point in degrees + lon_pnt : float + Longitude of point in degrees + rad_pnt : float + Distance to the point from the centre of the Earth in km + + References + ---------- + Based on J.M. Ruohoniemi's geopack and R.J. Barnes radar.pro + + """ + + # If the data are in geodetic coordiantes, convert to geocentric + if geodetic: + (glat, glon, rearth, gaz, gel) = \ + geodetic_to_geocentric_horizontal(lat_orig, lon_orig, az, el, + inverse=False) + grad = rearth + alt_orig + else: + glat = lat_orig + glon = lon_orig + grad = alt_orig + 6371.0 # Add the mean earth radius in km + gaz = az + gel = el + + # Convert from local horizontal to local cartesian coordiantes + x_loc, y_loc, z_loc = spherical_to_cartesian(gaz, gel, dist, inverse=False) + + # Convert from local to global cartesian coordiantes + x_glob, y_glob, z_glob = global_to_local_cartesian(x_loc, y_loc, z_loc, + glat, glon, grad, + inverse=True) + + # Convert from global cartesian to geocentric coordinates + lon_pnt, lat_pnt, rad_pnt = spherical_to_cartesian(x_glob, y_glob, z_glob, + inverse=True) + + # Convert from geocentric to geodetic, if desired + if geodetic: + lat_pnt, lon_pnt, rearth = geodetic_to_geocentric(lat_pnt, lon_pnt, + inverse=True) + rad_pnt = rearth + rad_pnt - 6371.0 + + return lat_pnt, lon_pnt, rad_pnt From fad618a7cf4500329b4d6576b762cab6972fcb0f Mon Sep 17 00:00:00 2001 From: Jeff Klenzing Date: Wed, 6 Mar 2019 09:46:06 -0500 Subject: [PATCH 24/50] tidy up imports --- pysat/tests/test_utils_coords.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py index 4c5a4f0..2e21faf 100644 --- a/pysat/tests/test_utils_coords.py +++ b/pysat/tests/test_utils_coords.py @@ -2,8 +2,10 @@ tests the pysat coords area """ import numpy as np -import pandas as pds + from nose.tools import assert_raises, raises +import pandas as pds + import pysat from pysat.utils import coords, time From 706241e8a744950af7f251e13d86c1869fdfc438 Mon Sep 17 00:00:00 2001 From: "Angeline G. Burrell" Date: Wed, 17 Jul 2019 11:52:55 -0400 Subject: [PATCH 25/50] Update coords.py Updated failures to be more informative, allowing more intellegent error catches. --- pysat/utils/coords.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pysat/utils/coords.py b/pysat/utils/coords.py index 8e60fad..add56c7 100644 --- a/pysat/utils/coords.py +++ b/pysat/utils/coords.py @@ -191,6 +191,10 @@ def scale_units(out_unit, in_unit): if in_unit.lower() in accepted_units[kk]: in_key = kk + if out_key is None and in_key is None: + raise ValueError(''.join(['Cannot scale {:s} and '.format(in_unit) + '{:s}, unknown units'.format(out_unit)])) + if out_key is None: raise ValueError('Unknown output unit {:}'.format(out_unit)) From bf17f580c39c02b80bf7f89dd438bf590e6b3b8b Mon Sep 17 00:00:00 2001 From: "Angeline G. Burrell" Date: Wed, 17 Jul 2019 12:42:25 -0400 Subject: [PATCH 26/50] Update coords.py - Added new unit possibilities for velocity - Reduced the amount of time spent in initial for-loop --- pysat/utils/coords.py | 48 ++++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/pysat/utils/coords.py b/pysat/utils/coords.py index add56c7..4f3f4d6 100644 --- a/pysat/utils/coords.py +++ b/pysat/utils/coords.py @@ -176,40 +176,60 @@ def scale_units(out_unit, in_unit): 'rad': ['rad', 'radian', 'radians'], 'h': ['h', 'hr', 'hrs', 'hours'], 'm': ['m', 'km', 'cm'], - 'm/s': ['m/s', 'cm/s', 'km/s']} + 'm/s': ['m/s', 'cm/s', 'km/s', 'm s$^{-1}$', + 'cm s$^{-1}$', 'km s$^{-1}$', 'm s-1', 'cm s-1', + 'km s-1']} + replace_str = {'/s': [' s$^{-1}$', ' s-1']} scales = {'deg': 180.0, 'rad': np.pi, 'h': 12.0, 'm': 1.0, 'km': 0.001, 'cm': 100.0, 'm/s': 1.0, 'cm/s': 100.0, 'km/s': 0.001} # Test input and determine transformation type - out_key = None - in_key = None + out_key = out_unit.lower() + in_key = in_unit.lower() for kk in accepted_units.keys(): - if out_unit.lower() in accepted_units[kk]: + if out_key in accepted_units.keys() and in_key in accepted_units.keys(): + break + + if(out_key not in accepted_units.keys() and + out_unit.lower() in accepted_units[kk]): out_key = kk - if in_unit.lower() in accepted_units[kk]: + if(in_key not in accepted_units.keys() and + in_unit.lower() in accepted_units[kk]): in_key = kk - if out_key is None and in_key is None: - raise ValueError(''.join(['Cannot scale {:s} and '.format(in_unit) + if(out_key not in accepted_units.keys() and + in_key not in accepted_units.keys()): + raise ValueError(''.join(['Cannot scale {:s} and '.format(in_unit), '{:s}, unknown units'.format(out_unit)])) - if out_key is None: + if out_key not in accepted_units.keys(): raise ValueError('Unknown output unit {:}'.format(out_unit)) - if in_key is None: + if in_key not in accepted_units.keys(): raise ValueError('Unknown input unit {:}'.format(in_unit)) if out_key == 'm' or out_key == 'm/s' or in_key == 'm' or in_key == 'm/s': if in_key != out_key: raise ValueError('Cannot scale {:s} and {:s}'.format(out_unit, in_unit)) - # Recast units as keys for the scales dictionary - out_key = out_unit - in_key = in_unit - - unit_scale = scales[out_key.lower()] / scales[in_key.lower()] + # Recast units as keys for the scales dictionary and ensure that + # the format is consistent + rkey = '' + for rr in replace_str.keys(): + if out_key.find(rr): + rkey = rr + + out_key = out_unit.lower() + in_key = in_unit.lower() + + if rkey in replace_str.keys(): + for rval in replace_str[rkey]: + out_key = out_key.replace(rval, rkey) + in_key = in_key.replace(rval, rkey) + + unit_scale = scales[out_key] / scales[in_key] return unit_scale From 5bfded52cabb3345fb9c7807c07ed8bf66e722e2 Mon Sep 17 00:00:00 2001 From: "Angeline G. Burrell" Date: Wed, 17 Jul 2019 12:42:50 -0400 Subject: [PATCH 27/50] Update test_utils_coords.py - Added tests for new functionality --- pysat/tests/test_utils_coords.py | 51 ++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py index 2e21faf..3c4c4ed 100644 --- a/pysat/tests/test_utils_coords.py +++ b/pysat/tests/test_utils_coords.py @@ -31,7 +31,8 @@ def setup(self): self.deg_units = ["deg", "degree", "degrees", "rad", "radian", "radians", "h", "hr", "hrs", "hours"] self.dist_units = ["m", "km", "cm"] - self.vel_units = ["m/s", "cm/s", "km/s"] + self.vel_units = ["m/s", "cm/s", "km/s", 'm s$^{-1}$', 'cm s$^{-1}$', + 'km s$^{-1}$', 'm s-1', 'cm s-1', 'km s-1'] def teardown(self): """Runs after every method to clean up previous testing.""" @@ -150,22 +151,62 @@ def test_scale_units_vel(self): for out_unit in self.vel_units: scale = coords.scale_units(out_unit, "m/s") - if out_unit == "m/s": + if out_unit.find("m") == 0: assert scale == 1.0 - elif out_unit.find("km/s") == 0: + elif out_unit.find("km") == 0: assert scale == 0.001 else: assert scale == 100.0 - def test_scale_units_bad(self): - """Test scale_units for mismatched input""" + def test_scale_units_bad_output(self): + """Test scale_units for unknown output unit""" assert_raises(ValueError, coords.scale_units, "happy", "m") + try: + coords.scale_units('happy', 'm') + verr = None + except ValueError as verr: + assert str(verr).find('output unit') > 0 + + def test_scale_units_bad_input(self): + """Test scale_units for unknown input unit""" + assert_raises(ValueError, coords.scale_units, "m", "happy") + try: + coords.scale_units('m', 'happy') + verr = None + except ValueError as verr: + assert str(verr).find('input unit') > 0 + + def test_scale_units_bad_match_pairs(self): + """Test scale_units for mismatched input for all pairings""" + assert_raises(ValueError, coords.scale_units, "m", "m/s") assert_raises(ValueError, coords.scale_units, "m", "deg") assert_raises(ValueError, coords.scale_units, "h", "km/s") + def test_scale_units_bad_match_message(self): + """Test scale_units error message for mismatched input""" + + assert_raises(ValueError, coords.scale_units, "m", "m/s") + try: + coords.scale_units('m', 'm/s') + verr = None + except ValueError as verr: + assert str(verr).find('Cannot scale') >= 0 + assert str(verr).find('unknown units') < 0 + + def test_scale_units_both_bad(self): + """Test scale_units for bad input and output""" + + assert_raises(ValueError, coords.scale_units, "happy", "sad") + try: + coords.scale_units('happy', 'sad') + verr = None + except ValueError as verr: + assert str(verr).find('unknown units') > 0 + + ##################################### # Geodetic / Geocentric conversions From 14e449529eabca67e741c44a12f94827950c2831 Mon Sep 17 00:00:00 2001 From: "Angeline G. Burrell" Date: Thu, 25 Jul 2019 12:41:50 -0400 Subject: [PATCH 28/50] Update test_utils_coords.py Updated tests for Jeff --- pysat/tests/test_utils_coords.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py index 3c4c4ed..8720cb6 100644 --- a/pysat/tests/test_utils_coords.py +++ b/pysat/tests/test_utils_coords.py @@ -164,7 +164,6 @@ def test_scale_units_bad_output(self): assert_raises(ValueError, coords.scale_units, "happy", "m") try: coords.scale_units('happy', 'm') - verr = None except ValueError as verr: assert str(verr).find('output unit') > 0 @@ -174,7 +173,6 @@ def test_scale_units_bad_input(self): assert_raises(ValueError, coords.scale_units, "m", "happy") try: coords.scale_units('m', 'happy') - verr = None except ValueError as verr: assert str(verr).find('input unit') > 0 @@ -191,7 +189,6 @@ def test_scale_units_bad_match_message(self): assert_raises(ValueError, coords.scale_units, "m", "m/s") try: coords.scale_units('m', 'm/s') - verr = None except ValueError as verr: assert str(verr).find('Cannot scale') >= 0 assert str(verr).find('unknown units') < 0 @@ -202,7 +199,6 @@ def test_scale_units_both_bad(self): assert_raises(ValueError, coords.scale_units, "happy", "sad") try: coords.scale_units('happy', 'sad') - verr = None except ValueError as verr: assert str(verr).find('unknown units') > 0 From b73f2d22139bab1c43277d04f617718a31a2dfca Mon Sep 17 00:00:00 2001 From: jklenzing Date: Wed, 28 Aug 2019 13:47:29 -0400 Subject: [PATCH 29/50] move scale_units to utils._core --- pysat/tests/test_utils_coords.py | 97 +------------------------------ pysat/utils/coords.py | 99 -------------------------------- 2 files changed, 1 insertion(+), 195 deletions(-) diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py index 8720cb6..dd828cf 100644 --- a/pysat/tests/test_utils_coords.py +++ b/pysat/tests/test_utils_coords.py @@ -3,7 +3,6 @@ """ import numpy as np -from nose.tools import assert_raises, raises import pandas as pds import pysat @@ -109,101 +108,7 @@ def test_bad_lon_name_calc_solar_local_time(self): coords.calc_solar_local_time(self.testInst, lon_name="not longitude", slt_name='slt') - ##################################### - # Scale units - - def test_scale_units_same(self): - """ Test scale_units when both units are the same """ - - scale = coords.scale_units("happy", "happy") - - assert scale == 1.0 - - def test_scale_units_angles(self): - """Test scale_units for angles """ - - for out_unit in self.deg_units: - scale = coords.scale_units(out_unit, "deg") - - if out_unit.find("deg") == 0: - assert scale == 1.0 - elif out_unit.find("rad") == 0: - assert scale == np.pi / 180.0 - else: - assert scale == 1.0 / 15.0 - - def test_scale_units_dist(self): - """Test scale_units for distances """ - - for out_unit in self.dist_units: - scale = coords.scale_units(out_unit, "m") - - if out_unit == "m": - assert scale == 1.0 - elif out_unit.find("km") == 0: - assert scale == 0.001 - else: - assert scale == 100.0 - - def test_scale_units_vel(self): - """Test scale_units for velocities """ - - for out_unit in self.vel_units: - scale = coords.scale_units(out_unit, "m/s") - - if out_unit.find("m") == 0: - assert scale == 1.0 - elif out_unit.find("km") == 0: - assert scale == 0.001 - else: - assert scale == 100.0 - - def test_scale_units_bad_output(self): - """Test scale_units for unknown output unit""" - - assert_raises(ValueError, coords.scale_units, "happy", "m") - try: - coords.scale_units('happy', 'm') - except ValueError as verr: - assert str(verr).find('output unit') > 0 - - def test_scale_units_bad_input(self): - """Test scale_units for unknown input unit""" - - assert_raises(ValueError, coords.scale_units, "m", "happy") - try: - coords.scale_units('m', 'happy') - except ValueError as verr: - assert str(verr).find('input unit') > 0 - - def test_scale_units_bad_match_pairs(self): - """Test scale_units for mismatched input for all pairings""" - - assert_raises(ValueError, coords.scale_units, "m", "m/s") - assert_raises(ValueError, coords.scale_units, "m", "deg") - assert_raises(ValueError, coords.scale_units, "h", "km/s") - - def test_scale_units_bad_match_message(self): - """Test scale_units error message for mismatched input""" - - assert_raises(ValueError, coords.scale_units, "m", "m/s") - try: - coords.scale_units('m', 'm/s') - except ValueError as verr: - assert str(verr).find('Cannot scale') >= 0 - assert str(verr).find('unknown units') < 0 - - def test_scale_units_both_bad(self): - """Test scale_units for bad input and output""" - - assert_raises(ValueError, coords.scale_units, "happy", "sad") - try: - coords.scale_units('happy', 'sad') - except ValueError as verr: - assert str(verr).find('unknown units') > 0 - - - ##################################### + ################################### # Geodetic / Geocentric conversions def test_geodetic_to_geocentric_single(self): diff --git a/pysat/utils/coords.py b/pysat/utils/coords.py index 4f3f4d6..6c77045 100644 --- a/pysat/utils/coords.py +++ b/pysat/utils/coords.py @@ -135,105 +135,6 @@ def calc_solar_local_time(inst, lon_name=None, slt_name='slt'): return -def scale_units(out_unit, in_unit): - """ Determine the scaling factor between two units - - Parameters - ------------- - out_unit : str - Desired unit after scaling - in_unit : str - Unit to be scaled - - Returns - ----------- - unit_scale : float - Scaling factor that will convert from in_units to out_units - - Notes - ------- - Accepted units include degrees ('deg', 'degree', 'degrees'), - radians ('rad', 'radian', 'radians'), - hours ('h', 'hr', 'hrs', 'hour', 'hours'), and lengths ('m', 'km', 'cm'). - Can convert between degrees, radians, and hours or different lengths. - - Example - ----------- - :: - import numpy as np - two_pi = 2.0 * np.pi - scale = scale_units("deg", "RAD") - two_pi *= scale - two_pi # will show 360.0 - - - """ - - if out_unit == in_unit: - return 1.0 - - accepted_units = {'deg': ['deg', 'degree', 'degrees'], - 'rad': ['rad', 'radian', 'radians'], - 'h': ['h', 'hr', 'hrs', 'hours'], - 'm': ['m', 'km', 'cm'], - 'm/s': ['m/s', 'cm/s', 'km/s', 'm s$^{-1}$', - 'cm s$^{-1}$', 'km s$^{-1}$', 'm s-1', 'cm s-1', - 'km s-1']} - replace_str = {'/s': [' s$^{-1}$', ' s-1']} - - scales = {'deg': 180.0, 'rad': np.pi, 'h': 12.0, - 'm': 1.0, 'km': 0.001, 'cm': 100.0, - 'm/s': 1.0, 'cm/s': 100.0, 'km/s': 0.001} - - # Test input and determine transformation type - out_key = out_unit.lower() - in_key = in_unit.lower() - for kk in accepted_units.keys(): - if out_key in accepted_units.keys() and in_key in accepted_units.keys(): - break - - if(out_key not in accepted_units.keys() and - out_unit.lower() in accepted_units[kk]): - out_key = kk - if(in_key not in accepted_units.keys() and - in_unit.lower() in accepted_units[kk]): - in_key = kk - - if(out_key not in accepted_units.keys() and - in_key not in accepted_units.keys()): - raise ValueError(''.join(['Cannot scale {:s} and '.format(in_unit), - '{:s}, unknown units'.format(out_unit)])) - - if out_key not in accepted_units.keys(): - raise ValueError('Unknown output unit {:}'.format(out_unit)) - - if in_key not in accepted_units.keys(): - raise ValueError('Unknown input unit {:}'.format(in_unit)) - - if out_key == 'm' or out_key == 'm/s' or in_key == 'm' or in_key == 'm/s': - if in_key != out_key: - raise ValueError('Cannot scale {:s} and {:s}'.format(out_unit, - in_unit)) - # Recast units as keys for the scales dictionary and ensure that - # the format is consistent - rkey = '' - for rr in replace_str.keys(): - if out_key.find(rr): - rkey = rr - - out_key = out_unit.lower() - in_key = in_unit.lower() - - if rkey in replace_str.keys(): - for rval in replace_str[rkey]: - out_key = out_key.replace(rval, rkey) - in_key = in_key.replace(rval, rkey) - - unit_scale = scales[out_key] / scales[in_key] - - return unit_scale - - def geodetic_to_geocentric(lat_in, lon_in=None, inverse=False): """Converts position from geodetic to geocentric or vice-versa. From fa4e18e8146aab179d2b6cbeaa7d37d8cdd2ff63 Mon Sep 17 00:00:00 2001 From: jklenzing Date: Wed, 28 Aug 2019 14:26:38 -0400 Subject: [PATCH 30/50] bugfix --- pysat/tests/test_utils_coords.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py index dd828cf..492f9a9 100644 --- a/pysat/tests/test_utils_coords.py +++ b/pysat/tests/test_utils_coords.py @@ -5,6 +5,7 @@ import pandas as pds +from nose.tools import raises import pysat from pysat.utils import coords, time From 6dcfd1f2515a97b5b1a453de1c9ad690385e3b5e Mon Sep 17 00:00:00 2001 From: Jeff Klenzing Date: Sat, 28 Sep 2019 16:18:10 -0400 Subject: [PATCH 31/50] BUG: restore functions and add deprecationwarnings --- pysat/utils/coords.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pysat/utils/coords.py b/pysat/utils/coords.py index 6c77045..c3fc778 100644 --- a/pysat/utils/coords.py +++ b/pysat/utils/coords.py @@ -135,6 +135,20 @@ def calc_solar_local_time(inst, lon_name=None, slt_name='slt'): return +def scale_units(out_unit, in_unit): + """Deprecated function, moved to pysat.utils._core""" + + import warnings + from pysat import utils + + warnings.warn(' '.join(["utils.computational_form is deprecated, use", + "pysat.ssnl.computational_form instead"]), + DeprecationWarning) + unit_scale = utils.scale_units(out_unit, in_unit) + + return unit_scale + + def geodetic_to_geocentric(lat_in, lon_in=None, inverse=False): """Converts position from geodetic to geocentric or vice-versa. From 306abc3d06a5347d6fae069f38992adfd84a212c Mon Sep 17 00:00:00 2001 From: Jeff Klenzing Date: Sat, 28 Sep 2019 16:28:50 -0400 Subject: [PATCH 32/50] TST: add test for deprecation --- pysat/tests/test_utils_coords.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py index 492f9a9..99b7a95 100644 --- a/pysat/tests/test_utils_coords.py +++ b/pysat/tests/test_utils_coords.py @@ -109,6 +109,20 @@ def test_bad_lon_name_calc_solar_local_time(self): coords.calc_solar_local_time(self.testInst, lon_name="not longitude", slt_name='slt') + def test_deprecation_warning_scale_units(self): + """Test deprecation warning for this function""" + + import warnings + + warnings.simplefilter("always") + scale1 = pysat.utils.scale_units("happy", "happy") + with warnings.catch_warnings(record=True) as w: + scale2 = coords.scale_units("happy", "happy") + + assert scale1 == scale2 + assert len(w) == 1 + assert w[0].category == DeprecationWarning + ################################### # Geodetic / Geocentric conversions From 67fab4e031646e67cbb1299601b4628ec48de07d Mon Sep 17 00:00:00 2001 From: Jeff Klenzing Date: Mon, 21 Oct 2019 19:26:49 -0400 Subject: [PATCH 33/50] BUG: stacklevel for all DeprecationWarnings --- pysat/utils/coords.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysat/utils/coords.py b/pysat/utils/coords.py index c3fc778..39e90a5 100644 --- a/pysat/utils/coords.py +++ b/pysat/utils/coords.py @@ -143,7 +143,7 @@ def scale_units(out_unit, in_unit): warnings.warn(' '.join(["utils.computational_form is deprecated, use", "pysat.ssnl.computational_form instead"]), - DeprecationWarning) + DeprecationWarning, stacklevel=2) unit_scale = utils.scale_units(out_unit, in_unit) return unit_scale From a0290d6884d5f0740231d6075e7de444c28c96e6 Mon Sep 17 00:00:00 2001 From: rstoneback Date: Sun, 27 Oct 2019 17:28:11 -0500 Subject: [PATCH 34/50] Merged --- pysat/utils/coords.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysat/utils/coords.py b/pysat/utils/coords.py index c3fc778..39e90a5 100644 --- a/pysat/utils/coords.py +++ b/pysat/utils/coords.py @@ -143,7 +143,7 @@ def scale_units(out_unit, in_unit): warnings.warn(' '.join(["utils.computational_form is deprecated, use", "pysat.ssnl.computational_form instead"]), - DeprecationWarning) + DeprecationWarning, stacklevel=2) unit_scale = utils.scale_units(out_unit, in_unit) return unit_scale From 2ba02afe45a0bbdcfbe22b9277f1f01023094955 Mon Sep 17 00:00:00 2001 From: rstoneback Date: Thu, 7 Nov 2019 18:24:54 -0600 Subject: [PATCH 35/50] STY: Updated variable name and imports. --- pysat/tests/test_utils_coords.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py index 99b7a95..8682a7a 100644 --- a/pysat/tests/test_utils_coords.py +++ b/pysat/tests/test_utils_coords.py @@ -116,12 +116,12 @@ def test_deprecation_warning_scale_units(self): warnings.simplefilter("always") scale1 = pysat.utils.scale_units("happy", "happy") - with warnings.catch_warnings(record=True) as w: + with warnings.catch_warnings(record=True) as war: scale2 = coords.scale_units("happy", "happy") assert scale1 == scale2 - assert len(w) == 1 - assert w[0].category == DeprecationWarning + assert len(war) == 1 + assert war[0].category == DeprecationWarning ################################### # Geodetic / Geocentric conversions From 3b7684fce0a9244e655c66c7abd0ffe25ace0544 Mon Sep 17 00:00:00 2001 From: jklenzing Date: Fri, 3 Jul 2020 13:09:14 -0400 Subject: [PATCH 36/50] ENH: Add coord transformations for JRO --- pysat/tests/test_utils_coords.py | 451 ----------------------- pysatMadrigal/__init__.py | 1 + {pysat/utils => pysatMadrigal}/coords.py | 142 +------ pysatMadrigal/tests/test_coords.py | 352 ++++++++++++++++++ 4 files changed, 354 insertions(+), 592 deletions(-) delete mode 100644 pysat/tests/test_utils_coords.py rename {pysat/utils => pysatMadrigal}/coords.py (73%) create mode 100644 pysatMadrigal/tests/test_coords.py diff --git a/pysat/tests/test_utils_coords.py b/pysat/tests/test_utils_coords.py deleted file mode 100644 index 8682a7a..0000000 --- a/pysat/tests/test_utils_coords.py +++ /dev/null @@ -1,451 +0,0 @@ -""" -tests the pysat coords area -""" -import numpy as np - -import pandas as pds - -from nose.tools import raises -import pysat -from pysat.utils import coords, time - - -class TestBasics(): - def setup(self): - """Runs before every method to create a clean testing setup.""" - self.test_angles = np.array([340.0, 348.0, 358.9, 0.5, 5.0, 9.87]) - - self.testInst = pysat.Instrument(platform='pysat', - name='testing', - clean_level='clean') - # Add longitude to the test instrument - ones = np.ones(shape=len(self.test_angles)) - tind = time.create_datetime_index(year=ones*2001, - month=ones, - uts=np.arange(0.0, len(ones), 1.0)) - - self.testInst.data = \ - pds.DataFrame(np.array([tind, self.test_angles]).transpose(), - index=tind, columns=["time", "longitude"]) - - self.deg_units = ["deg", "degree", "degrees", "rad", "radian", - "radians", "h", "hr", "hrs", "hours"] - self.dist_units = ["m", "km", "cm"] - self.vel_units = ["m/s", "cm/s", "km/s", 'm s$^{-1}$', 'cm s$^{-1}$', - 'km s$^{-1}$', 'm s-1', 'cm s-1', 'km s-1'] - - def teardown(self): - """Runs after every method to clean up previous testing.""" - del self.test_angles, self.testInst - del self.deg_units, self.dist_units, self.vel_units - - ##################################### - # Cyclic data conversions - - def test_adjust_cyclic_data_default(self): - """ Test adjust_cyclic_data with default range """ - - test_in = np.radians(self.test_angles) - np.pi - test_angles = coords.adjust_cyclic_data(test_in) - - assert test_angles.max() < 2.0 * np.pi - assert test_angles.min() >= 0.0 - - def test_adjust_cyclic_data_custom(self): - """ Test adjust_cyclic_data with a custom range """ - - test_angles = coords.adjust_cyclic_data(self.test_angles, - high=180.0, low=-180.0) - - assert test_angles.max() < 180.0 - assert test_angles.min() >= -180.0 - - ##################################### - # Update Longitude - - def test_update_longitude(self): - """Test update_longitude """ - - coords.update_longitude(self.testInst, lon_name="longitude") - - assert np.all(self.testInst.data['longitude'] < 180.0) - assert np.all(self.testInst.data['longitude'] >= -180.0) - - @raises(ValueError) - def test_bad_lon_name_update_longitude(self): - """Test update_longitude with a bad longitude name""" - - coords.update_longitude(self.testInst, lon_name="not longitude") - - ######################### - # calc_solar_local_time - - def test_calc_solar_local_time(self): - """Test calc_solar_local_time""" - - coords.calc_solar_local_time(self.testInst, lon_name="longitude", - slt_name='slt') - target = np.array([22.66666667, 23.20027778, 23.92722222, - 0.03416667, 0.33444444, 0.65938889]) - - assert (abs(self.testInst['slt'] - target)).max() < 1.0e-6 - - def test_calc_solar_local_time_w_update_longitude(self): - """Test calc_solar_local_time with update_longitude""" - - coords.calc_solar_local_time(self.testInst, lon_name="longitude", - slt_name='slt') - coords.update_longitude(self.testInst, lon_name="longitude") - coords.calc_solar_local_time(self.testInst, lon_name="longitude", - slt_name='slt2') - - assert (abs(self.testInst['slt'] - - self.testInst['slt2'])).max() < 1.0e-6 - - @raises(ValueError) - def test_bad_lon_name_calc_solar_local_time(self): - """Test calc_solar_local_time with a bad longitude name""" - - coords.calc_solar_local_time(self.testInst, lon_name="not longitude", - slt_name='slt') - - def test_deprecation_warning_scale_units(self): - """Test deprecation warning for this function""" - - import warnings - - warnings.simplefilter("always") - scale1 = pysat.utils.scale_units("happy", "happy") - with warnings.catch_warnings(record=True) as war: - scale2 = coords.scale_units("happy", "happy") - - assert scale1 == scale2 - assert len(war) == 1 - assert war[0].category == DeprecationWarning - - ################################### - # Geodetic / Geocentric conversions - - def test_geodetic_to_geocentric_single(self): - """Test conversion from geodetic to geocentric coordinates""" - - lat, lon, rad = coords.geodetic_to_geocentric(45.0, lon_in=8.0) - - assert abs(lat - 44.807576784018046) < 1.0e-6 - assert abs(lon - 8.0) < 1.0e-6 - assert abs(rad - 6367.489543863465) < 1.0e-6 - - def test_geocentric_to_geodetic_single(self): - """Test conversion from geocentric to geodetic coordinates""" - - lat, lon, rad = coords.geodetic_to_geocentric(45.0, lon_in=8.0, - inverse=True) - - assert abs(lat - 45.192423215981954) < 1.0e-6 - assert abs(lon - 8.0) < 1.0e-6 - assert abs(rad - 6367.345908499981) < 1.0e-6 - - def test_geodetic_to_geocentric_mult(self): - """Test array conversion from geodetic to geocentric coordinates""" - - arr = np.ones(shape=(10,), dtype=float) - lat, lon, rad = coords.geodetic_to_geocentric(45.0*arr, lon_in=8.0*arr) - - assert lat.shape == arr.shape - assert lon.shape == arr.shape - assert rad.shape == arr.shape - assert abs(lat - 44.807576784018046).max() < 1.0e-6 - assert abs(lon - 8.0).max() < 1.0e-6 - assert abs(rad - 6367.489543863465).max() < 1.0e-6 - - def test_geocentric_to_geodetic_mult(self): - """Test array conversion from geocentric to geodetic coordinates""" - - arr = np.ones(shape=(10,), dtype=float) - lat, lon, rad = coords.geodetic_to_geocentric(45.0*arr, lon_in=8.0*arr, - inverse=True) - - assert lat.shape == arr.shape - assert lon.shape == arr.shape - assert rad.shape == arr.shape - assert abs(lat - 45.192423215981954).max() < 1.0e-6 - assert abs(lon - 8.0).max() < 1.0e-6 - assert abs(rad - 6367.345908499981).max() < 1.0e-6 - - def test_geodetic_to_geocentric_inverse(self): - """Tests the reversibility of geodetic to geocentric conversions""" - - lat1 = 37.5 - lon1 = 117.3 - lat2, lon2, rad_e = coords.geodetic_to_geocentric(lat1, lon_in=lon1, - inverse=False) - lat3, lon3, rad_e = coords.geodetic_to_geocentric(lat2, lon_in=lon2, - inverse=True) - assert abs(lon1-lon3) < 1.0e-6 - assert abs(lat1-lat3) < 1.0e-6 - - ############################################### - # Geodetic / Geocentric Horizontal conversions - - def test_geodetic_to_geocentric_horz_single(self): - """Test conversion from geodetic to geocentric coordinates""" - - lat, lon, rad, az, el = \ - coords.geodetic_to_geocentric_horizontal(45.0, 8.0, 52.0, 63.0) - - assert abs(lat - 44.807576784018046) < 1.0e-6 - assert abs(lon - 8.0) < 1.0e-6 - assert abs(rad - 6367.489543863465) < 1.0e-6 - assert abs(az - 51.70376774257361) < 1.0e-6 - assert abs(el - 62.8811403841008) < 1.0e-6 - - def test_geocentric_to_geodetic_horz_single(self): - """Test conversion from geocentric to geodetic coordinates""" - - lat, lon, rad, az, el = \ - coords.geodetic_to_geocentric_horizontal(45.0, 8.0, 52.0, 63.0, - inverse=True) - - assert abs(lat - 45.192423215981954) < 1.0e-6 - assert abs(lon - 8.0) < 1.0e-6 - assert abs(rad - 6367.345908499981) < 1.0e-6 - assert abs(az - 52.29896101551479) < 1.0e-6 - assert abs(el - 63.118072033649916) < 1.0e-6 - - def test_geodetic_to_geocentric_horz_mult(self): - """Test array conversion from geodetic to geocentric coordinates""" - - arr = np.ones(shape=(10,), dtype=float) - lat, lon, rad, az, el = \ - coords.geodetic_to_geocentric_horizontal(45.0*arr, 8.0*arr, - 52.0*arr, 63.0*arr) - - assert lat.shape == arr.shape - assert lon.shape == arr.shape - assert rad.shape == arr.shape - assert az.shape == arr.shape - assert el.shape == arr.shape - assert abs(lat - 44.807576784018046).max() < 1.0e-6 - assert abs(lon - 8.0).max() < 1.0e-6 - assert abs(rad - 6367.489543863465).max() < 1.0e-6 - assert abs(az - 51.70376774257361).max() < 1.0e-6 - assert abs(el - 62.8811403841008).max() < 1.0e-6 - - def test_geocentric_to_geodetic_horz_mult(self): - """Test array conversion from geocentric to geodetic coordinates""" - - arr = np.ones(shape=(10,), dtype=float) - lat, lon, rad, az, el = \ - coords.geodetic_to_geocentric_horizontal(45.0*arr, 8.0*arr, - 52.0*arr, 63.0*arr, - inverse=True) - - assert lat.shape == arr.shape - assert lon.shape == arr.shape - assert rad.shape == arr.shape - assert az.shape == arr.shape - assert el.shape == arr.shape - assert abs(lat - 45.192423215981954).max() < 1.0e-6 - assert abs(lon - 8.0).max() < 1.0e-6 - assert abs(rad - 6367.345908499981).max() < 1.0e-6 - assert abs(az - 52.29896101551479).max() < 1.0e-6 - assert abs(el - 63.118072033649916).max() < 1.0e-6 - - def test_geodetic_to_geocentric_horizontal_inverse(self): - """Tests the reversibility of geodetic to geocentric horiz conversions - - Note: inverse of az and el angles currently non-functional""" - - lat1 = -17.5 - lon1 = 187.3 - az1 = 52.0 - el1 = 63.0 - lat2, lon2, rad_e, az2, el2 = \ - coords.geodetic_to_geocentric_horizontal(lat1, lon1, az1, el1, - inverse=False) - lat3, lon3, rad_e, az3, el3 = \ - coords.geodetic_to_geocentric_horizontal(lat2, lon2, az2, el2, - inverse=True) - - assert abs(lon1-lon3) < 1.0e-6 - assert abs(lat1-lat3) < 1.0e-6 - assert abs(az1-az3) < 1.0e-6 - assert abs(el1-el3) < 1.0e-6 - - #################################### - # Spherical / Cartesian conversions - - def test_spherical_to_cartesian_single(self): - """Test conversion from spherical to cartesian coordinates""" - - x, y, z = coords.spherical_to_cartesian(45.0, 30.0, 1.0) - - assert abs(x - y) < 1.0e-6 - assert abs(z - 0.5) < 1.0e-6 - - def test_cartesian_to_spherical_single(self): - """Test conversion from cartesian to spherical coordinates""" - - x = 0.6123724356957946 - az, el, r = coords.spherical_to_cartesian(x, x, 0.5, - inverse=True) - - assert abs(az - 45.0) < 1.0e-6 - assert abs(el - 30.0) < 1.0e-6 - assert abs(r - 1.0) < 1.0e-6 - - def test_spherical_to_cartesian_mult(self): - """Test array conversion from spherical to cartesian coordinates""" - - arr = np.ones(shape=(10,), dtype=float) - x, y, z = coords.spherical_to_cartesian(45.0*arr, 30.0*arr, arr) - - assert x.shape == arr.shape - assert y.shape == arr.shape - assert z.shape == arr.shape - assert abs(x - y).max() < 1.0e-6 - assert abs(z - 0.5).max() < 1.0e-6 - - def test_cartesian_to_spherical_mult(self): - """Test array conversion from cartesian to spherical coordinates""" - - arr = np.ones(shape=(10,), dtype=float) - x = 0.6123724356957946 - az, el, r = coords.spherical_to_cartesian(x*arr, x*arr, 0.5*arr, - inverse=True) - - assert az.shape == arr.shape - assert el.shape == arr.shape - assert r.shape == arr.shape - assert abs(az - 45.0).max() < 1.0e-6 - assert abs(el - 30.0).max() < 1.0e-6 - assert abs(r - 1.0).max() < 1.0e-6 - - def test_spherical_to_cartesian_inverse(self): - """Tests the reversibility of spherical to cartesian conversions""" - - x1 = 3000.0 - y1 = 2000.0 - z1 = 2500.0 - az, el, r = coords.spherical_to_cartesian(x1, y1, z1, - inverse=True) - x2, y2, z2 = coords.spherical_to_cartesian(az, el, r, - inverse=False) - - assert abs(x1-x2) < 1.0e-6 - assert abs(y1-y2) < 1.0e-6 - assert abs(z1-z2) < 1.0e-6 - - ######################################## - # Global / Local Cartesian conversions - - def test_global_to_local_cartesian_single(self): - """Test conversion from global to local cartesian coordinates""" - - x, y, z = coords.global_to_local_cartesian(7000.0, 8000.0, 9000.0, - 37.5, 289.0, 6380.0) - - assert abs(x + 9223.175264852474) < 1.0e-6 - assert abs(y + 2239.835278362686) < 1.0e-6 - assert abs(z - 11323.126851088331) < 1.0e-6 - - def test_local_cartesian_to_global_single(self): - """Test conversion from local cartesian to global coordinates""" - - x, y, z = coords.global_to_local_cartesian(7000.0, 8000.0, 9000.0, - 37.5, 289.0, 6380.0, - inverse=True) - - assert abs(x + 5709.804676635975) < 1.0e-6 - assert abs(y + 4918.397556010223) < 1.0e-6 - assert abs(z - 15709.577500484005) < 1.0e-6 - - def test_global_to_local_cartesian_mult(self): - """Test array conversion from global to local cartesian coordinates""" - - arr = np.ones(shape=(10,), dtype=float) - x, y, z = coords.global_to_local_cartesian(7000.0*arr, 8000.0*arr, - 9000.0*arr, 37.5*arr, - 289.0*arr, 6380.0*arr) - - assert x.shape == arr.shape - assert y.shape == arr.shape - assert z.shape == arr.shape - assert abs(x + 9223.175264852474).max() < 1.0e-6 - assert abs(y + 2239.835278362686).max() < 1.0e-6 - assert abs(z - 11323.126851088331).max() < 1.0e-6 - - def test_local_cartesian_to_global_mult(self): - """Test array conversion from local cartesian to global coordinates""" - - arr = np.ones(shape=(10,), dtype=float) - x, y, z = coords.global_to_local_cartesian(7000.0*arr, 8000.0*arr, - 9000.0*arr, 37.5*arr, - 289.0*arr, 6380.0*arr, - inverse=True) - - assert x.shape == arr.shape - assert y.shape == arr.shape - assert z.shape == arr.shape - assert abs(x + 5709.804676635975).max() < 1.0e-6 - assert abs(y + 4918.397556010223).max() < 1.0e-6 - assert abs(z - 15709.577500484005).max() < 1.0e-6 - - def test_global_to_local_cartesian_inverse(self): - """Tests the reversibility of the global to loc cartesian transform""" - - x1 = 7000.0 - y1 = 8000.0 - z1 = 9500.0 - lat = 37.5 - lon = 289.0 - rad = 6380.0 - x2, y2, z2 = coords.global_to_local_cartesian(x1, y1, z1, - lat, lon, rad, - inverse=False) - x3, y3, z3 = coords.global_to_local_cartesian(x2, y2, z2, - lat, lon, rad, - inverse=True) - assert abs(x1-x3) < 1.0e-6 - assert abs(y1-y3) < 1.0e-6 - assert abs(z1-z3) < 1.0e-6 - - ######################################## - # Local Horizontal / Global conversions - - def test_local_horizontal_to_global_geo_geodetic(self): - """Tests the conversion of the local horizontal to global geo""" - - az = 30.0 - el = 45.0 - dist = 1000.0 - lat0 = 45.0 - lon0 = 0.0 - alt0 = 400.0 - - lat, lon, rad = \ - coords.local_horizontal_to_global_geo(az, el, dist, - lat0, lon0, alt0) - - assert abs(lat - 50.419037572472625) < 1.0e-6 - assert abs(lon + 7.694008395350697) < 1.0e-6 - assert abs(rad - 7172.15486518744) < 1.0e-6 - - def test_local_horizontal_to_global_geo(self): - """Tests the conversion of the local horizontal to global geo""" - - az = 30.0 - el = 45.0 - dist = 1000.0 - lat0 = 45.0 - lon0 = 0.0 - alt0 = 400.0 - - lat, lon, rad = \ - coords.local_horizontal_to_global_geo(az, el, dist, - lat0, lon0, alt0, - geodetic=False) - - assert abs(lat - 50.414315865044202) < 1.0e-6 - assert abs(lon + 7.6855551809119502) < 1.0e-6 - assert abs(rad - 7185.6983665760772) < 1.0e-6 diff --git a/pysatMadrigal/__init__.py b/pysatMadrigal/__init__.py index 0b73a26..9346093 100644 --- a/pysatMadrigal/__init__.py +++ b/pysatMadrigal/__init__.py @@ -1 +1,2 @@ +from pysatMadrigal import coords # noqa F401 from pysatMadrigal import instruments # noqa F401 diff --git a/pysat/utils/coords.py b/pysatMadrigal/coords.py similarity index 73% rename from pysat/utils/coords.py rename to pysatMadrigal/coords.py index 39e90a5..a8e07e0 100644 --- a/pysat/utils/coords.py +++ b/pysatMadrigal/coords.py @@ -1,5 +1,5 @@ """ -pysat.coords - coordinate transformations for pysat +pysatMadrigal.coords - coordinate transformations for radar instruments ========================================= pysat.coords contains a number of coordinate-transformation @@ -7,146 +7,6 @@ """ import numpy as np -import pandas as pds - - -def adjust_cyclic_data(samples, high=2.0*np.pi, low=0.0): - """Adjust cyclic values such as longitude to a different scale - - Parameters - ----------- - samples : array_like - Input array - high: float or int - Upper boundary for circular standard deviation range (default=2 pi) - low : float or int - Lower boundary for circular standard deviation range (default=0) - axis : int or NoneType - Axis along which standard deviations are computed. The default is to - compute the standard deviation of the flattened array - - Returns - -------- - out_samples : float - Circular standard deviation - - """ - - out_samples = np.asarray(samples) - sample_range = high - low - out_samples[out_samples >= high] -= sample_range - out_samples[out_samples < low] += sample_range - - return out_samples - - -def update_longitude(inst, lon_name=None, high=180.0, low=-180.0): - """ Update longitude to the desired range - - Parameters - ------------ - inst : pysat.Instrument instance - instrument object to be updated - lon_name : string - name of the longtiude data - high : float - Highest allowed longitude value (default=180.0) - low : float - Lowest allowed longitude value (default=-180.0) - - Returns - --------- - updates instrument data in column 'lon_name' - - """ - - from pysat.utils.coords import adjust_cyclic_data - - if lon_name not in inst.data.keys(): - raise ValueError('uknown longitude variable name') - - new_lon = adjust_cyclic_data(inst[lon_name], high=high, low=low) - - # Update based on data type - if inst.pandas_format: - inst[lon_name] = new_lon - else: - inst[lon_name].data = new_lon - - return - - -def calc_solar_local_time(inst, lon_name=None, slt_name='slt'): - """ Append solar local time to an instrument object - - Parameters - ------------ - inst : pysat.Instrument instance - instrument object to be updated - lon_name : string - name of the longtiude data key (assumes data are in degrees) - slt_name : string - name of the output solar local time data key (default='slt') - - Returns - --------- - updates instrument data in column specified by slt_name - - """ - - import datetime as dt - - if lon_name not in inst.data.keys(): - raise ValueError('uknown longitude variable name') - - # Convert from numpy epoch nanoseconds to UT seconds of day - ut_hr = list() - for nptime in inst.index.values.astype(int): - # Numpy times come out in nanoseconds and timestamp converts - # from seconds - dtime = dt.datetime.utcfromtimestamp(nptime * 1.0e-9) - ut_hr.append((dtime.hour * 3600.0 + dtime.minute * 60.0 + - dtime.second + dtime.microsecond * 1.0e-6) / 3600.0) - # Calculate solar local time - slt = np.array([t + inst[lon_name][i] / 15.0 for i, t in enumerate(ut_hr)]) - - # Ensure that solar local time falls between 0 and 24 hours - slt = np.mod(slt, 24.0) - - # Add the solar local time to the instrument - if inst.pandas_format: - inst[slt_name] = pds.Series(slt, index=inst.data.index) - else: - data = inst.data.assign(pysat_slt=(inst.data.coords.keys(), slt)) - data.rename({"pysat_slt": slt_name}, inplace=True) - inst.data = data - - # Add units to the metadata - inst.meta[slt_name] = {inst.meta.units_label: 'h', - inst.meta.name_label: "Solar Local Time", - inst.meta.desc_label: "Solar local time", - inst.meta.plot_label: "SLT", - inst.meta.axis_label: "SLT", - inst.meta.scale_label: "linear", - inst.meta.min_label: 0.0, - inst.meta.max_label: 24.0, - inst.meta.fill_label: np.nan} - - return - - -def scale_units(out_unit, in_unit): - """Deprecated function, moved to pysat.utils._core""" - - import warnings - from pysat import utils - - warnings.warn(' '.join(["utils.computational_form is deprecated, use", - "pysat.ssnl.computational_form instead"]), - DeprecationWarning, stacklevel=2) - unit_scale = utils.scale_units(out_unit, in_unit) - - return unit_scale def geodetic_to_geocentric(lat_in, lon_in=None, inverse=False): diff --git a/pysatMadrigal/tests/test_coords.py b/pysatMadrigal/tests/test_coords.py new file mode 100644 index 0000000..b9aa91a --- /dev/null +++ b/pysatMadrigal/tests/test_coords.py @@ -0,0 +1,352 @@ +""" +tests the pysat coords area +""" +import numpy as np + +from pysatMadrigal import coords + + +def test_geodetic_to_geocentric_single(): + """Test conversion from geodetic to geocentric coordinates""" + + lat, lon, rad = coords.geodetic_to_geocentric(45.0, lon_in=8.0) + + assert abs(lat - 44.807576784018046) < 1.0e-6 + assert abs(lon - 8.0) < 1.0e-6 + assert abs(rad - 6367.489543863465) < 1.0e-6 + + +def test_geocentric_to_geodetic_single(): + """Test conversion from geocentric to geodetic coordinates""" + + lat, lon, rad = coords.geodetic_to_geocentric(45.0, lon_in=8.0, + inverse=True) + + assert abs(lat - 45.192423215981954) < 1.0e-6 + assert abs(lon - 8.0) < 1.0e-6 + assert abs(rad - 6367.345908499981) < 1.0e-6 + + +def test_geodetic_to_geocentric_mult(): + """Test array conversion from geodetic to geocentric coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + lat, lon, rad = coords.geodetic_to_geocentric(45.0*arr, lon_in=8.0*arr) + + assert lat.shape == arr.shape + assert lon.shape == arr.shape + assert rad.shape == arr.shape + assert abs(lat - 44.807576784018046).max() < 1.0e-6 + assert abs(lon - 8.0).max() < 1.0e-6 + assert abs(rad - 6367.489543863465).max() < 1.0e-6 + + +def test_geocentric_to_geodetic_mult(): + """Test array conversion from geocentric to geodetic coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + lat, lon, rad = coords.geodetic_to_geocentric(45.0*arr, lon_in=8.0*arr, + inverse=True) + + assert lat.shape == arr.shape + assert lon.shape == arr.shape + assert rad.shape == arr.shape + assert abs(lat - 45.192423215981954).max() < 1.0e-6 + assert abs(lon - 8.0).max() < 1.0e-6 + assert abs(rad - 6367.345908499981).max() < 1.0e-6 + + +def test_geodetic_to_geocentric_inverse(): + """Tests the reversibility of geodetic to geocentric conversions""" + + lat1 = 37.5 + lon1 = 117.3 + lat2, lon2, rad_e = coords.geodetic_to_geocentric(lat1, lon_in=lon1, + inverse=False) + lat3, lon3, rad_e = coords.geodetic_to_geocentric(lat2, lon_in=lon2, + inverse=True) + assert abs(lon1-lon3) < 1.0e-6 + assert abs(lat1-lat3) < 1.0e-6 + + +############################################### +# Geodetic / Geocentric Horizontal conversions + +def test_geodetic_to_geocentric_horz_single(): + """Test conversion from geodetic to geocentric coordinates""" + + lat, lon, rad, az, el = \ + coords.geodetic_to_geocentric_horizontal(45.0, 8.0, 52.0, 63.0) + + assert abs(lat - 44.807576784018046) < 1.0e-6 + assert abs(lon - 8.0) < 1.0e-6 + assert abs(rad - 6367.489543863465) < 1.0e-6 + assert abs(az - 51.70376774257361) < 1.0e-6 + assert abs(el - 62.8811403841008) < 1.0e-6 + + +def test_geocentric_to_geodetic_horz_single(): + """Test conversion from geocentric to geodetic coordinates""" + + lat, lon, rad, az, el = \ + coords.geodetic_to_geocentric_horizontal(45.0, 8.0, 52.0, 63.0, + inverse=True) + + assert abs(lat - 45.192423215981954) < 1.0e-6 + assert abs(lon - 8.0) < 1.0e-6 + assert abs(rad - 6367.345908499981) < 1.0e-6 + assert abs(az - 52.29896101551479) < 1.0e-6 + assert abs(el - 63.118072033649916) < 1.0e-6 + + +def test_geodetic_to_geocentric_horz_mult(): + """Test array conversion from geodetic to geocentric coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + lat, lon, rad, az, el = \ + coords.geodetic_to_geocentric_horizontal(45.0*arr, 8.0*arr, + 52.0*arr, 63.0*arr) + + assert lat.shape == arr.shape + assert lon.shape == arr.shape + assert rad.shape == arr.shape + assert az.shape == arr.shape + assert el.shape == arr.shape + assert abs(lat - 44.807576784018046).max() < 1.0e-6 + assert abs(lon - 8.0).max() < 1.0e-6 + assert abs(rad - 6367.489543863465).max() < 1.0e-6 + assert abs(az - 51.70376774257361).max() < 1.0e-6 + assert abs(el - 62.8811403841008).max() < 1.0e-6 + + +def test_geocentric_to_geodetic_horz_mult(): + """Test array conversion from geocentric to geodetic coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + lat, lon, rad, az, el = \ + coords.geodetic_to_geocentric_horizontal(45.0*arr, 8.0*arr, + 52.0*arr, 63.0*arr, + inverse=True) + + assert lat.shape == arr.shape + assert lon.shape == arr.shape + assert rad.shape == arr.shape + assert az.shape == arr.shape + assert el.shape == arr.shape + assert abs(lat - 45.192423215981954).max() < 1.0e-6 + assert abs(lon - 8.0).max() < 1.0e-6 + assert abs(rad - 6367.345908499981).max() < 1.0e-6 + assert abs(az - 52.29896101551479).max() < 1.0e-6 + assert abs(el - 63.118072033649916).max() < 1.0e-6 + + +def test_geodetic_to_geocentric_horizontal_inverse(): + """Tests the reversibility of geodetic to geocentric horiz conversions + + Note: inverse of az and el angles currently non-functional""" + + lat1 = -17.5 + lon1 = 187.3 + az1 = 52.0 + el1 = 63.0 + lat2, lon2, rad_e, az2, el2 = \ + coords.geodetic_to_geocentric_horizontal(lat1, lon1, az1, el1, + inverse=False) + lat3, lon3, rad_e, az3, el3 = \ + coords.geodetic_to_geocentric_horizontal(lat2, lon2, az2, el2, + inverse=True) + + assert abs(lon1-lon3) < 1.0e-6 + assert abs(lat1-lat3) < 1.0e-6 + assert abs(az1-az3) < 1.0e-6 + assert abs(el1-el3) < 1.0e-6 + + +#################################### +# Spherical / Cartesian conversions + +def test_spherical_to_cartesian_single(): + """Test conversion from spherical to cartesian coordinates""" + + x, y, z = coords.spherical_to_cartesian(45.0, 30.0, 1.0) + + assert abs(x - y) < 1.0e-6 + assert abs(z - 0.5) < 1.0e-6 + + +def test_cartesian_to_spherical_single(): + """Test conversion from cartesian to spherical coordinates""" + + x = 0.6123724356957946 + az, el, r = coords.spherical_to_cartesian(x, x, 0.5, + inverse=True) + + assert abs(az - 45.0) < 1.0e-6 + assert abs(el - 30.0) < 1.0e-6 + assert abs(r - 1.0) < 1.0e-6 + + +def test_spherical_to_cartesian_mult(): + """Test array conversion from spherical to cartesian coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + x, y, z = coords.spherical_to_cartesian(45.0*arr, 30.0*arr, arr) + + assert x.shape == arr.shape + assert y.shape == arr.shape + assert z.shape == arr.shape + assert abs(x - y).max() < 1.0e-6 + assert abs(z - 0.5).max() < 1.0e-6 + + +def test_cartesian_to_spherical_mult(): + """Test array conversion from cartesian to spherical coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + x = 0.6123724356957946 + az, el, r = coords.spherical_to_cartesian(x*arr, x*arr, 0.5*arr, + inverse=True) + + assert az.shape == arr.shape + assert el.shape == arr.shape + assert r.shape == arr.shape + assert abs(az - 45.0).max() < 1.0e-6 + assert abs(el - 30.0).max() < 1.0e-6 + assert abs(r - 1.0).max() < 1.0e-6 + + +def test_spherical_to_cartesian_inverse(): + """Tests the reversibility of spherical to cartesian conversions""" + + x1 = 3000.0 + y1 = 2000.0 + z1 = 2500.0 + az, el, r = coords.spherical_to_cartesian(x1, y1, z1, + inverse=True) + x2, y2, z2 = coords.spherical_to_cartesian(az, el, r, + inverse=False) + + assert abs(x1-x2) < 1.0e-6 + assert abs(y1-y2) < 1.0e-6 + assert abs(z1-z2) < 1.0e-6 + + +######################################## +# Global / Local Cartesian conversions + +def test_global_to_local_cartesian_single(): + """Test conversion from global to local cartesian coordinates""" + + x, y, z = coords.global_to_local_cartesian(7000.0, 8000.0, 9000.0, + 37.5, 289.0, 6380.0) + + assert abs(x + 9223.175264852474) < 1.0e-6 + assert abs(y + 2239.835278362686) < 1.0e-6 + assert abs(z - 11323.126851088331) < 1.0e-6 + + +def test_local_cartesian_to_global_single(): + """Test conversion from local cartesian to global coordinates""" + + x, y, z = coords.global_to_local_cartesian(7000.0, 8000.0, 9000.0, + 37.5, 289.0, 6380.0, + inverse=True) + + assert abs(x + 5709.804676635975) < 1.0e-6 + assert abs(y + 4918.397556010223) < 1.0e-6 + assert abs(z - 15709.577500484005) < 1.0e-6 + + +def test_global_to_local_cartesian_mult(): + """Test array conversion from global to local cartesian coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + x, y, z = coords.global_to_local_cartesian(7000.0*arr, 8000.0*arr, + 9000.0*arr, 37.5*arr, + 289.0*arr, 6380.0*arr) + + assert x.shape == arr.shape + assert y.shape == arr.shape + assert z.shape == arr.shape + assert abs(x + 9223.175264852474).max() < 1.0e-6 + assert abs(y + 2239.835278362686).max() < 1.0e-6 + assert abs(z - 11323.126851088331).max() < 1.0e-6 + + +def test_local_cartesian_to_global_mult(): + """Test array conversion from local cartesian to global coordinates""" + + arr = np.ones(shape=(10,), dtype=float) + x, y, z = coords.global_to_local_cartesian(7000.0*arr, 8000.0*arr, + 9000.0*arr, 37.5*arr, + 289.0*arr, 6380.0*arr, + inverse=True) + + assert x.shape == arr.shape + assert y.shape == arr.shape + assert z.shape == arr.shape + assert abs(x + 5709.804676635975).max() < 1.0e-6 + assert abs(y + 4918.397556010223).max() < 1.0e-6 + assert abs(z - 15709.577500484005).max() < 1.0e-6 + + +def test_global_to_local_cartesian_inverse(): + """Tests the reversibility of the global to loc cartesian transform""" + + x1 = 7000.0 + y1 = 8000.0 + z1 = 9500.0 + lat = 37.5 + lon = 289.0 + rad = 6380.0 + x2, y2, z2 = coords.global_to_local_cartesian(x1, y1, z1, + lat, lon, rad, + inverse=False) + x3, y3, z3 = coords.global_to_local_cartesian(x2, y2, z2, + lat, lon, rad, + inverse=True) + assert abs(x1-x3) < 1.0e-6 + assert abs(y1-y3) < 1.0e-6 + assert abs(z1-z3) < 1.0e-6 + + +######################################## +# Local Horizontal / Global conversions + +def test_local_horizontal_to_global_geo_geodetic(): + """Tests the conversion of the local horizontal to global geo""" + + az = 30.0 + el = 45.0 + dist = 1000.0 + lat0 = 45.0 + lon0 = 0.0 + alt0 = 400.0 + + lat, lon, rad = \ + coords.local_horizontal_to_global_geo(az, el, dist, + lat0, lon0, alt0) + + assert abs(lat - 50.419037572472625) < 1.0e-6 + assert abs(lon + 7.694008395350697) < 1.0e-6 + assert abs(rad - 7172.15486518744) < 1.0e-6 + + +def test_local_horizontal_to_global_geo(): + """Tests the conversion of the local horizontal to global geo""" + + az = 30.0 + el = 45.0 + dist = 1000.0 + lat0 = 45.0 + lon0 = 0.0 + alt0 = 400.0 + + lat, lon, rad = \ + coords.local_horizontal_to_global_geo(az, el, dist, + lat0, lon0, alt0, + geodetic=False) + + assert abs(lat - 50.414315865044202) < 1.0e-6 + assert abs(lon + 7.6855551809119502) < 1.0e-6 + assert abs(rad - 7185.6983665760772) < 1.0e-6 From 38003ab87e6d2183ff143ad80de163207da749fe Mon Sep 17 00:00:00 2001 From: jklenzing Date: Fri, 3 Jul 2020 13:14:01 -0400 Subject: [PATCH 37/50] DOC: add changelog --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0535e07 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,15 @@ +# Change Log +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +## [0.?.?] - 2020-07-03 +- Added coords from pysat.utils + +## [0.0.3] - 2020-06-15 +- pypi compatibility + +## [0.0.2] - 2020-05-13 +- zenodo link + +## [0.0.1] - 2020-05-13 +- Alpha release From 74f71496f57af900fbd273483d379b799195065a Mon Sep 17 00:00:00 2001 From: jklenzing Date: Fri, 3 Jul 2020 16:04:33 -0400 Subject: [PATCH 38/50] STY: flake8 updates --- pysatMadrigal/coords.py | 4 +-- pysatMadrigal/tests/test_coords.py | 52 +++++++++++++++--------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/pysatMadrigal/coords.py b/pysatMadrigal/coords.py index a8e07e0..ff2a0db 100644 --- a/pysatMadrigal/coords.py +++ b/pysatMadrigal/coords.py @@ -63,8 +63,8 @@ def geodetic_to_geocentric(lat_in, lon_in=None, inverse=False): lat_out = np.degrees(np.arctan(rad_ratio_sq * tan_in)) # Calculate the Earth radius at this latitude - rad_earth = rad_eq / np.sqrt(1.0 + eprime_sq * - np.sin(np.radians(lat_out))**2) + rad_earth = rad_eq / np.sqrt(1.0 + eprime_sq + * np.sin(np.radians(lat_out))**2) # longitude remains unchanged lon_out = lon_in diff --git a/pysatMadrigal/tests/test_coords.py b/pysatMadrigal/tests/test_coords.py index b9aa91a..0fe29c5 100644 --- a/pysatMadrigal/tests/test_coords.py +++ b/pysatMadrigal/tests/test_coords.py @@ -31,7 +31,7 @@ def test_geodetic_to_geocentric_mult(): """Test array conversion from geodetic to geocentric coordinates""" arr = np.ones(shape=(10,), dtype=float) - lat, lon, rad = coords.geodetic_to_geocentric(45.0*arr, lon_in=8.0*arr) + lat, lon, rad = coords.geodetic_to_geocentric(45.0 * arr, lon_in=8.0 * arr) assert lat.shape == arr.shape assert lon.shape == arr.shape @@ -45,7 +45,7 @@ def test_geocentric_to_geodetic_mult(): """Test array conversion from geocentric to geodetic coordinates""" arr = np.ones(shape=(10,), dtype=float) - lat, lon, rad = coords.geodetic_to_geocentric(45.0*arr, lon_in=8.0*arr, + lat, lon, rad = coords.geodetic_to_geocentric(45.0 * arr, lon_in=8.0 * arr, inverse=True) assert lat.shape == arr.shape @@ -65,8 +65,8 @@ def test_geodetic_to_geocentric_inverse(): inverse=False) lat3, lon3, rad_e = coords.geodetic_to_geocentric(lat2, lon_in=lon2, inverse=True) - assert abs(lon1-lon3) < 1.0e-6 - assert abs(lat1-lat3) < 1.0e-6 + assert abs(lon1 - lon3) < 1.0e-6 + assert abs(lat1 - lat3) < 1.0e-6 ############################################### @@ -104,8 +104,8 @@ def test_geodetic_to_geocentric_horz_mult(): arr = np.ones(shape=(10,), dtype=float) lat, lon, rad, az, el = \ - coords.geodetic_to_geocentric_horizontal(45.0*arr, 8.0*arr, - 52.0*arr, 63.0*arr) + coords.geodetic_to_geocentric_horizontal(45.0 * arr, 8.0 * arr, + 52.0 * arr, 63.0 * arr) assert lat.shape == arr.shape assert lon.shape == arr.shape @@ -124,8 +124,8 @@ def test_geocentric_to_geodetic_horz_mult(): arr = np.ones(shape=(10,), dtype=float) lat, lon, rad, az, el = \ - coords.geodetic_to_geocentric_horizontal(45.0*arr, 8.0*arr, - 52.0*arr, 63.0*arr, + coords.geodetic_to_geocentric_horizontal(45.0 * arr, 8.0 * arr, + 52.0 * arr, 63.0 * arr, inverse=True) assert lat.shape == arr.shape @@ -156,10 +156,10 @@ def test_geodetic_to_geocentric_horizontal_inverse(): coords.geodetic_to_geocentric_horizontal(lat2, lon2, az2, el2, inverse=True) - assert abs(lon1-lon3) < 1.0e-6 - assert abs(lat1-lat3) < 1.0e-6 - assert abs(az1-az3) < 1.0e-6 - assert abs(el1-el3) < 1.0e-6 + assert abs(lon1 - lon3) < 1.0e-6 + assert abs(lat1 - lat3) < 1.0e-6 + assert abs(az1 - az3) < 1.0e-6 + assert abs(el1 - el3) < 1.0e-6 #################################### @@ -190,7 +190,7 @@ def test_spherical_to_cartesian_mult(): """Test array conversion from spherical to cartesian coordinates""" arr = np.ones(shape=(10,), dtype=float) - x, y, z = coords.spherical_to_cartesian(45.0*arr, 30.0*arr, arr) + x, y, z = coords.spherical_to_cartesian(45.0 * arr, 30.0 * arr, arr) assert x.shape == arr.shape assert y.shape == arr.shape @@ -204,7 +204,7 @@ def test_cartesian_to_spherical_mult(): arr = np.ones(shape=(10,), dtype=float) x = 0.6123724356957946 - az, el, r = coords.spherical_to_cartesian(x*arr, x*arr, 0.5*arr, + az, el, r = coords.spherical_to_cartesian(x * arr, x * arr, 0.5 * arr, inverse=True) assert az.shape == arr.shape @@ -226,9 +226,9 @@ def test_spherical_to_cartesian_inverse(): x2, y2, z2 = coords.spherical_to_cartesian(az, el, r, inverse=False) - assert abs(x1-x2) < 1.0e-6 - assert abs(y1-y2) < 1.0e-6 - assert abs(z1-z2) < 1.0e-6 + assert abs(x1 - x2) < 1.0e-6 + assert abs(y1 - y2) < 1.0e-6 + assert abs(z1 - z2) < 1.0e-6 ######################################## @@ -261,9 +261,9 @@ def test_global_to_local_cartesian_mult(): """Test array conversion from global to local cartesian coordinates""" arr = np.ones(shape=(10,), dtype=float) - x, y, z = coords.global_to_local_cartesian(7000.0*arr, 8000.0*arr, - 9000.0*arr, 37.5*arr, - 289.0*arr, 6380.0*arr) + x, y, z = coords.global_to_local_cartesian(7000.0 * arr, 8000.0 * arr, + 9000.0 * arr, 37.5 * arr, + 289.0 * arr, 6380.0 * arr) assert x.shape == arr.shape assert y.shape == arr.shape @@ -277,9 +277,9 @@ def test_local_cartesian_to_global_mult(): """Test array conversion from local cartesian to global coordinates""" arr = np.ones(shape=(10,), dtype=float) - x, y, z = coords.global_to_local_cartesian(7000.0*arr, 8000.0*arr, - 9000.0*arr, 37.5*arr, - 289.0*arr, 6380.0*arr, + x, y, z = coords.global_to_local_cartesian(7000.0 * arr, 8000.0 * arr, + 9000.0 * arr, 37.5 * arr, + 289.0 * arr, 6380.0 * arr, inverse=True) assert x.shape == arr.shape @@ -305,9 +305,9 @@ def test_global_to_local_cartesian_inverse(): x3, y3, z3 = coords.global_to_local_cartesian(x2, y2, z2, lat, lon, rad, inverse=True) - assert abs(x1-x3) < 1.0e-6 - assert abs(y1-y3) < 1.0e-6 - assert abs(z1-z3) < 1.0e-6 + assert abs(x1 - x3) < 1.0e-6 + assert abs(y1 - y3) < 1.0e-6 + assert abs(z1 - z3) < 1.0e-6 ######################################## From e442808924cdfca0d8dc95d732a180c7ff5fe873 Mon Sep 17 00:00:00 2001 From: jklenzing Date: Mon, 6 Jul 2020 11:26:50 -0400 Subject: [PATCH 39/50] STY: put coords under utils --- pysatMadrigal/__init__.py | 2 +- pysatMadrigal/tests/{test_coords.py => test_utils_coords.py} | 2 +- pysatMadrigal/utils/__init__.py | 1 + pysatMadrigal/{ => utils}/coords.py | 0 4 files changed, 3 insertions(+), 2 deletions(-) rename pysatMadrigal/tests/{test_coords.py => test_utils_coords.py} (99%) create mode 100644 pysatMadrigal/utils/__init__.py rename pysatMadrigal/{ => utils}/coords.py (100%) diff --git a/pysatMadrigal/__init__.py b/pysatMadrigal/__init__.py index 9346093..c1a628f 100644 --- a/pysatMadrigal/__init__.py +++ b/pysatMadrigal/__init__.py @@ -1,2 +1,2 @@ -from pysatMadrigal import coords # noqa F401 from pysatMadrigal import instruments # noqa F401 +from pysatMadrigal import utils # noqa F401 diff --git a/pysatMadrigal/tests/test_coords.py b/pysatMadrigal/tests/test_utils_coords.py similarity index 99% rename from pysatMadrigal/tests/test_coords.py rename to pysatMadrigal/tests/test_utils_coords.py index 0fe29c5..61b4934 100644 --- a/pysatMadrigal/tests/test_coords.py +++ b/pysatMadrigal/tests/test_utils_coords.py @@ -3,7 +3,7 @@ """ import numpy as np -from pysatMadrigal import coords +from pysatMadrigal.utils import coords def test_geodetic_to_geocentric_single(): diff --git a/pysatMadrigal/utils/__init__.py b/pysatMadrigal/utils/__init__.py new file mode 100644 index 0000000..0854b8d --- /dev/null +++ b/pysatMadrigal/utils/__init__.py @@ -0,0 +1 @@ +from pysatMadrigal.utils import coords # noqa F401 diff --git a/pysatMadrigal/coords.py b/pysatMadrigal/utils/coords.py similarity index 100% rename from pysatMadrigal/coords.py rename to pysatMadrigal/utils/coords.py From 25ad97b7c599b7fd9f796c8435b39ffa0a270f54 Mon Sep 17 00:00:00 2001 From: jklenzing Date: Mon, 6 Jul 2020 19:35:27 -0400 Subject: [PATCH 40/50] TST: update max-line-length --- setup.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.cfg b/setup.cfg index 22d9e74..3e786fa 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,6 @@ +[flake8] +max-line-length = 80 + [tool:pytest] markers = all_inst: tests all instruments From 49e460abc3585da1b1804e37d31fb9aede686e72 Mon Sep 17 00:00:00 2001 From: jklenzing Date: Mon, 6 Jul 2020 19:35:38 -0400 Subject: [PATCH 41/50] TST: restructure coords tests --- pysatMadrigal/tests/test_utils_coords.py | 731 +++++++++++++---------- 1 file changed, 411 insertions(+), 320 deletions(-) diff --git a/pysatMadrigal/tests/test_utils_coords.py b/pysatMadrigal/tests/test_utils_coords.py index 61b4934..3be51f5 100644 --- a/pysatMadrigal/tests/test_utils_coords.py +++ b/pysatMadrigal/tests/test_utils_coords.py @@ -6,347 +6,438 @@ from pysatMadrigal.utils import coords -def test_geodetic_to_geocentric_single(): - """Test conversion from geodetic to geocentric coordinates""" +class TestGeodeticGeocentric(): - lat, lon, rad = coords.geodetic_to_geocentric(45.0, lon_in=8.0) + def setup(self): + """Runs before every method to create a clean testing setup.""" + arr = np.ones(shape=(10,), dtype=float) + self.val = {'lat': 45.0, 'lon': 8.0, 'az': 52.0, 'el': 63.0} + self.arr = {} + for key in self.val.keys(): + self.arr[key] = self.val[key] * arr - assert abs(lat - 44.807576784018046) < 1.0e-6 - assert abs(lon - 8.0) < 1.0e-6 - assert abs(rad - 6367.489543863465) < 1.0e-6 + def teardown(self): + """Runs after every method to clean up previous testing.""" + del self.val, self.arr, + def test_geodetic_to_geocentric_single(self): + """Test conversion from geodetic to geocentric coordinates""" -def test_geocentric_to_geodetic_single(): - """Test conversion from geocentric to geodetic coordinates""" + lat, lon, rad = coords.geodetic_to_geocentric(self.val['lat'], + lon_in=self.val['lon']) - lat, lon, rad = coords.geodetic_to_geocentric(45.0, lon_in=8.0, - inverse=True) - - assert abs(lat - 45.192423215981954) < 1.0e-6 - assert abs(lon - 8.0) < 1.0e-6 - assert abs(rad - 6367.345908499981) < 1.0e-6 - - -def test_geodetic_to_geocentric_mult(): - """Test array conversion from geodetic to geocentric coordinates""" - - arr = np.ones(shape=(10,), dtype=float) - lat, lon, rad = coords.geodetic_to_geocentric(45.0 * arr, lon_in=8.0 * arr) - - assert lat.shape == arr.shape - assert lon.shape == arr.shape - assert rad.shape == arr.shape - assert abs(lat - 44.807576784018046).max() < 1.0e-6 - assert abs(lon - 8.0).max() < 1.0e-6 - assert abs(rad - 6367.489543863465).max() < 1.0e-6 - - -def test_geocentric_to_geodetic_mult(): - """Test array conversion from geocentric to geodetic coordinates""" - - arr = np.ones(shape=(10,), dtype=float) - lat, lon, rad = coords.geodetic_to_geocentric(45.0 * arr, lon_in=8.0 * arr, - inverse=True) + assert abs(lat - 44.807576784018046) < 1.0e-6 + assert abs(lon - 8.0) < 1.0e-6 + assert abs(rad - 6367.489543863465) < 1.0e-6 - assert lat.shape == arr.shape - assert lon.shape == arr.shape - assert rad.shape == arr.shape - assert abs(lat - 45.192423215981954).max() < 1.0e-6 - assert abs(lon - 8.0).max() < 1.0e-6 - assert abs(rad - 6367.345908499981).max() < 1.0e-6 + def test_geocentric_to_geodetic_single(self): + """Test conversion from geocentric to geodetic coordinates""" - -def test_geodetic_to_geocentric_inverse(): - """Tests the reversibility of geodetic to geocentric conversions""" - - lat1 = 37.5 - lon1 = 117.3 - lat2, lon2, rad_e = coords.geodetic_to_geocentric(lat1, lon_in=lon1, - inverse=False) - lat3, lon3, rad_e = coords.geodetic_to_geocentric(lat2, lon_in=lon2, + lat, lon, rad = coords.geodetic_to_geocentric(self.val['lat'], + lon_in=self.val['lon'], inverse=True) - assert abs(lon1 - lon3) < 1.0e-6 - assert abs(lat1 - lat3) < 1.0e-6 - - -############################################### -# Geodetic / Geocentric Horizontal conversions - -def test_geodetic_to_geocentric_horz_single(): - """Test conversion from geodetic to geocentric coordinates""" - - lat, lon, rad, az, el = \ - coords.geodetic_to_geocentric_horizontal(45.0, 8.0, 52.0, 63.0) - - assert abs(lat - 44.807576784018046) < 1.0e-6 - assert abs(lon - 8.0) < 1.0e-6 - assert abs(rad - 6367.489543863465) < 1.0e-6 - assert abs(az - 51.70376774257361) < 1.0e-6 - assert abs(el - 62.8811403841008) < 1.0e-6 - - -def test_geocentric_to_geodetic_horz_single(): - """Test conversion from geocentric to geodetic coordinates""" - - lat, lon, rad, az, el = \ - coords.geodetic_to_geocentric_horizontal(45.0, 8.0, 52.0, 63.0, - inverse=True) - - assert abs(lat - 45.192423215981954) < 1.0e-6 - assert abs(lon - 8.0) < 1.0e-6 - assert abs(rad - 6367.345908499981) < 1.0e-6 - assert abs(az - 52.29896101551479) < 1.0e-6 - assert abs(el - 63.118072033649916) < 1.0e-6 - - -def test_geodetic_to_geocentric_horz_mult(): - """Test array conversion from geodetic to geocentric coordinates""" - - arr = np.ones(shape=(10,), dtype=float) - lat, lon, rad, az, el = \ - coords.geodetic_to_geocentric_horizontal(45.0 * arr, 8.0 * arr, - 52.0 * arr, 63.0 * arr) - - assert lat.shape == arr.shape - assert lon.shape == arr.shape - assert rad.shape == arr.shape - assert az.shape == arr.shape - assert el.shape == arr.shape - assert abs(lat - 44.807576784018046).max() < 1.0e-6 - assert abs(lon - 8.0).max() < 1.0e-6 - assert abs(rad - 6367.489543863465).max() < 1.0e-6 - assert abs(az - 51.70376774257361).max() < 1.0e-6 - assert abs(el - 62.8811403841008).max() < 1.0e-6 - - -def test_geocentric_to_geodetic_horz_mult(): - """Test array conversion from geocentric to geodetic coordinates""" - - arr = np.ones(shape=(10,), dtype=float) - lat, lon, rad, az, el = \ - coords.geodetic_to_geocentric_horizontal(45.0 * arr, 8.0 * arr, - 52.0 * arr, 63.0 * arr, - inverse=True) - - assert lat.shape == arr.shape - assert lon.shape == arr.shape - assert rad.shape == arr.shape - assert az.shape == arr.shape - assert el.shape == arr.shape - assert abs(lat - 45.192423215981954).max() < 1.0e-6 - assert abs(lon - 8.0).max() < 1.0e-6 - assert abs(rad - 6367.345908499981).max() < 1.0e-6 - assert abs(az - 52.29896101551479).max() < 1.0e-6 - assert abs(el - 63.118072033649916).max() < 1.0e-6 - - -def test_geodetic_to_geocentric_horizontal_inverse(): - """Tests the reversibility of geodetic to geocentric horiz conversions - - Note: inverse of az and el angles currently non-functional""" - - lat1 = -17.5 - lon1 = 187.3 - az1 = 52.0 - el1 = 63.0 - lat2, lon2, rad_e, az2, el2 = \ - coords.geodetic_to_geocentric_horizontal(lat1, lon1, az1, el1, - inverse=False) - lat3, lon3, rad_e, az3, el3 = \ - coords.geodetic_to_geocentric_horizontal(lat2, lon2, az2, el2, - inverse=True) - - assert abs(lon1 - lon3) < 1.0e-6 - assert abs(lat1 - lat3) < 1.0e-6 - assert abs(az1 - az3) < 1.0e-6 - assert abs(el1 - el3) < 1.0e-6 - - -#################################### -# Spherical / Cartesian conversions - -def test_spherical_to_cartesian_single(): - """Test conversion from spherical to cartesian coordinates""" - - x, y, z = coords.spherical_to_cartesian(45.0, 30.0, 1.0) - - assert abs(x - y) < 1.0e-6 - assert abs(z - 0.5) < 1.0e-6 + assert abs(lat - 45.192423215981954) < 1.0e-6 + assert abs(lon - 8.0) < 1.0e-6 + assert abs(rad - 6367.345908499981) < 1.0e-6 -def test_cartesian_to_spherical_single(): - """Test conversion from cartesian to spherical coordinates""" + def test_geodetic_to_geocentric_mult(self): + """Test array conversion from geodetic to geocentric coordinates""" - x = 0.6123724356957946 - az, el, r = coords.spherical_to_cartesian(x, x, 0.5, - inverse=True) + lat, lon, rad = coords.geodetic_to_geocentric(self.arr['lat'], + lon_in=self.arr['lon']) - assert abs(az - 45.0) < 1.0e-6 - assert abs(el - 30.0) < 1.0e-6 - assert abs(r - 1.0) < 1.0e-6 + assert lat.shape == self.arr['lat'].shape + assert lon.shape == self.arr['lat'].shape + assert rad.shape == self.arr['lat'].shape + assert abs(lat - 44.807576784018046).max() < 1.0e-6 + assert abs(lon - 8.0).max() < 1.0e-6 + assert abs(rad - 6367.489543863465).max() < 1.0e-6 + def test_geocentric_to_geodetic_mult(self): + """Test array conversion from geocentric to geodetic coordinates""" -def test_spherical_to_cartesian_mult(): - """Test array conversion from spherical to cartesian coordinates""" - - arr = np.ones(shape=(10,), dtype=float) - x, y, z = coords.spherical_to_cartesian(45.0 * arr, 30.0 * arr, arr) - - assert x.shape == arr.shape - assert y.shape == arr.shape - assert z.shape == arr.shape - assert abs(x - y).max() < 1.0e-6 - assert abs(z - 0.5).max() < 1.0e-6 - - -def test_cartesian_to_spherical_mult(): - """Test array conversion from cartesian to spherical coordinates""" - - arr = np.ones(shape=(10,), dtype=float) - x = 0.6123724356957946 - az, el, r = coords.spherical_to_cartesian(x * arr, x * arr, 0.5 * arr, - inverse=True) + lat, lon, rad = coords.geodetic_to_geocentric(self.arr['lat'], + lon_in=self.arr['lon'], + inverse=True) - assert az.shape == arr.shape - assert el.shape == arr.shape - assert r.shape == arr.shape - assert abs(az - 45.0).max() < 1.0e-6 - assert abs(el - 30.0).max() < 1.0e-6 - assert abs(r - 1.0).max() < 1.0e-6 + assert lat.shape == self.arr['lat'].shape + assert lon.shape == self.arr['lat'].shape + assert rad.shape == self.arr['lat'].shape + assert abs(lat - 45.192423215981954).max() < 1.0e-6 + assert abs(lon - 8.0).max() < 1.0e-6 + assert abs(rad - 6367.345908499981).max() < 1.0e-6 + + def test_geodetic_to_geocentric_inverse(self): + """Tests the reversibility of geodetic to geocentric conversions""" + + lat1 = 37.5 + lon1 = 117.3 + lat2, lon2, rad_e = coords.geodetic_to_geocentric(lat1, lon_in=lon1, + inverse=False) + lat3, lon3, rad_e = coords.geodetic_to_geocentric(lat2, lon_in=lon2, + inverse=True) + assert abs(lon1 - lon3) < 1.0e-6 + assert abs(lat1 - lat3) < 1.0e-6 + + ############################################### + # Geodetic / Geocentric Horizontal conversions + + def test_geodetic_to_geocentric_horz_single(self): + """Test conversion from geodetic to geocentric coordinates""" + + lat, lon, rad, az, el = \ + coords.geodetic_to_geocentric_horizontal(self.val['lat'], + self.val['lon'], + self.val['az'], + self.val['el']) + + assert abs(lat - 44.807576784018046) < 1.0e-6 + assert abs(lon - 8.0) < 1.0e-6 + assert abs(rad - 6367.489543863465) < 1.0e-6 + assert abs(az - 51.70376774257361) < 1.0e-6 + assert abs(el - 62.8811403841008) < 1.0e-6 + + def test_geocentric_to_geodetic_horz_single(self): + """Test conversion from geocentric to geodetic coordinates""" + + lat, lon, rad, az, el = \ + coords.geodetic_to_geocentric_horizontal(self.val['lat'], + self.val['lon'], + self.val['az'], + self.val['el'], + inverse=True) + + assert abs(lat - 45.192423215981954) < 1.0e-6 + assert abs(lon - 8.0) < 1.0e-6 + assert abs(rad - 6367.345908499981) < 1.0e-6 + assert abs(az - 52.29896101551479) < 1.0e-6 + assert abs(el - 63.118072033649916) < 1.0e-6 + + def test_geodetic_to_geocentric_horz_mult(self): + """Test array conversion from geodetic to geocentric coordinates""" + + lat, lon, rad, az, el = \ + coords.geodetic_to_geocentric_horizontal(self.arr['lat'], + self.arr['lon'], + self.arr['az'], + self.arr['el']) + + assert lat.shape == self.arr['lat'].shape + assert lon.shape == self.arr['lat'].shape + assert rad.shape == self.arr['lat'].shape + assert az.shape == self.arr['lat'].shape + assert el.shape == self.arr['lat'].shape + assert abs(lat - 44.807576784018046).max() < 1.0e-6 + assert abs(lon - 8.0).max() < 1.0e-6 + assert abs(rad - 6367.489543863465).max() < 1.0e-6 + assert abs(az - 51.70376774257361).max() < 1.0e-6 + assert abs(el - 62.8811403841008).max() < 1.0e-6 + + def test_geocentric_to_geodetic_horz_mult(self): + """Test array conversion from geocentric to geodetic coordinates""" + + lat, lon, rad, az, el = \ + coords.geodetic_to_geocentric_horizontal(self.arr['lat'], + self.arr['lon'], + self.arr['az'], + self.arr['el'], + inverse=True) + + assert lat.shape == self.arr['lat'].shape + assert lon.shape == self.arr['lat'].shape + assert rad.shape == self.arr['lat'].shape + assert az.shape == self.arr['lat'].shape + assert el.shape == self.arr['lat'].shape + assert abs(lat - 45.192423215981954).max() < 1.0e-6 + assert abs(lon - 8.0).max() < 1.0e-6 + assert abs(rad - 6367.345908499981).max() < 1.0e-6 + assert abs(az - 52.29896101551479).max() < 1.0e-6 + assert abs(el - 63.118072033649916).max() < 1.0e-6 + + def test_geodetic_to_geocentric_horizontal_inverse(self): + """Tests the reversibility of geodetic to geocentric horiz conversions + + Note: inverse of az and el angles currently non-functional""" + + lat1 = -17.5 + lon1 = 187.3 + az1 = 52.0 + el1 = 63.0 + lat2, lon2, rad_e, az2, el2 = \ + coords.geodetic_to_geocentric_horizontal(lat1, lon1, az1, el1, + inverse=False) + lat3, lon3, rad_e, az3, el3 = \ + coords.geodetic_to_geocentric_horizontal(lat2, lon2, az2, el2, + inverse=True) + + assert abs(lon1 - lon3) < 1.0e-6 + assert abs(lat1 - lat3) < 1.0e-6 + assert abs(az1 - az3) < 1.0e-6 + assert abs(el1 - el3) < 1.0e-6 + + +class TestSphereCart(): + + def setup(self): + """Runs before every method to create a clean testing setup.""" + arr = np.ones(shape=(10,), dtype=float) + self.val = {'az': 45.0, 'el': 30.0, 'r': 1.0, + 'x': 0.6123724356957946, 'z': 0.5} + self.arr = {} + for key in self.val.keys(): + self.arr[key] = self.val[key] * arr + + def teardown(self): + """Runs after every method to clean up previous testing.""" + del self.val, self.arr, + + def test_spherical_to_cartesian_single(self): + """Test conversion from spherical to cartesian coordinates""" + + x, y, z = coords.spherical_to_cartesian(self.val['az'], + self.val['el'], + self.val['r']) + + assert abs(x - y) < 1.0e-6 + assert abs(z - 0.5) < 1.0e-6 + + def test_cartesian_to_spherical_single(self): + """Test conversion from cartesian to spherical coordinates""" + + az, el, r = coords.spherical_to_cartesian(self.val['x'], self.val['x'], + self.val['z'], inverse=True) + + assert abs(az - 45.0) < 1.0e-6 + assert abs(el - 30.0) < 1.0e-6 + assert abs(r - 1.0) < 1.0e-6 + + def test_spherical_to_cartesian_mult(self): + """Test array conversion from spherical to cartesian coordinates""" + + x, y, z = coords.spherical_to_cartesian(self.arr['az'], + self.arr['el'], + self.arr['r']) + + assert x.shape == self.arr['x'].shape + assert y.shape == self.arr['x'].shape + assert z.shape == self.arr['x'].shape + assert abs(x - y).max() < 1.0e-6 + assert abs(z - 0.5).max() < 1.0e-6 + + def test_cartesian_to_spherical_mult(self): + """Test array conversion from cartesian to spherical coordinates""" + + az, el, r = coords.spherical_to_cartesian(self.arr['x'], + self.arr['x'], + self.arr['z'], + inverse=True) + assert az.shape == self.arr['x'].shape + assert el.shape == self.arr['x'].shape + assert r.shape == self.arr['x'].shape + assert abs(az - 45.0).max() < 1.0e-6 + assert abs(el - 30.0).max() < 1.0e-6 + assert abs(r - 1.0).max() < 1.0e-6 -def test_spherical_to_cartesian_inverse(): - """Tests the reversibility of spherical to cartesian conversions""" + def test_spherical_to_cartesian_inverse(self): + """Tests the reversibility of spherical to cartesian conversions""" - x1 = 3000.0 - y1 = 2000.0 - z1 = 2500.0 - az, el, r = coords.spherical_to_cartesian(x1, y1, z1, - inverse=True) - x2, y2, z2 = coords.spherical_to_cartesian(az, el, r, - inverse=False) + x1 = 3000.0 + y1 = 2000.0 + z1 = 2500.0 + az, el, r = coords.spherical_to_cartesian(x1, y1, z1, + inverse=True) + x2, y2, z2 = coords.spherical_to_cartesian(az, el, r, + inverse=False) - assert abs(x1 - x2) < 1.0e-6 - assert abs(y1 - y2) < 1.0e-6 - assert abs(z1 - z2) < 1.0e-6 + assert abs(x1 - x2) < 1.0e-6 + assert abs(y1 - y2) < 1.0e-6 + assert abs(z1 - z2) < 1.0e-6 ######################################## # Global / Local Cartesian conversions -def test_global_to_local_cartesian_single(): - """Test conversion from global to local cartesian coordinates""" - - x, y, z = coords.global_to_local_cartesian(7000.0, 8000.0, 9000.0, - 37.5, 289.0, 6380.0) - - assert abs(x + 9223.175264852474) < 1.0e-6 - assert abs(y + 2239.835278362686) < 1.0e-6 - assert abs(z - 11323.126851088331) < 1.0e-6 - - -def test_local_cartesian_to_global_single(): - """Test conversion from local cartesian to global coordinates""" - - x, y, z = coords.global_to_local_cartesian(7000.0, 8000.0, 9000.0, - 37.5, 289.0, 6380.0, - inverse=True) - - assert abs(x + 5709.804676635975) < 1.0e-6 - assert abs(y + 4918.397556010223) < 1.0e-6 - assert abs(z - 15709.577500484005) < 1.0e-6 - - -def test_global_to_local_cartesian_mult(): - """Test array conversion from global to local cartesian coordinates""" - - arr = np.ones(shape=(10,), dtype=float) - x, y, z = coords.global_to_local_cartesian(7000.0 * arr, 8000.0 * arr, - 9000.0 * arr, 37.5 * arr, - 289.0 * arr, 6380.0 * arr) - - assert x.shape == arr.shape - assert y.shape == arr.shape - assert z.shape == arr.shape - assert abs(x + 9223.175264852474).max() < 1.0e-6 - assert abs(y + 2239.835278362686).max() < 1.0e-6 - assert abs(z - 11323.126851088331).max() < 1.0e-6 - - -def test_local_cartesian_to_global_mult(): - """Test array conversion from local cartesian to global coordinates""" - - arr = np.ones(shape=(10,), dtype=float) - x, y, z = coords.global_to_local_cartesian(7000.0 * arr, 8000.0 * arr, - 9000.0 * arr, 37.5 * arr, - 289.0 * arr, 6380.0 * arr, - inverse=True) - - assert x.shape == arr.shape - assert y.shape == arr.shape - assert z.shape == arr.shape - assert abs(x + 5709.804676635975).max() < 1.0e-6 - assert abs(y + 4918.397556010223).max() < 1.0e-6 - assert abs(z - 15709.577500484005).max() < 1.0e-6 - - -def test_global_to_local_cartesian_inverse(): - """Tests the reversibility of the global to loc cartesian transform""" - - x1 = 7000.0 - y1 = 8000.0 - z1 = 9500.0 - lat = 37.5 - lon = 289.0 - rad = 6380.0 - x2, y2, z2 = coords.global_to_local_cartesian(x1, y1, z1, - lat, lon, rad, - inverse=False) - x3, y3, z3 = coords.global_to_local_cartesian(x2, y2, z2, - lat, lon, rad, - inverse=True) - assert abs(x1 - x3) < 1.0e-6 - assert abs(y1 - y3) < 1.0e-6 - assert abs(z1 - z3) < 1.0e-6 - - -######################################## -# Local Horizontal / Global conversions - -def test_local_horizontal_to_global_geo_geodetic(): - """Tests the conversion of the local horizontal to global geo""" - - az = 30.0 - el = 45.0 - dist = 1000.0 - lat0 = 45.0 - lon0 = 0.0 - alt0 = 400.0 - - lat, lon, rad = \ - coords.local_horizontal_to_global_geo(az, el, dist, - lat0, lon0, alt0) - - assert abs(lat - 50.419037572472625) < 1.0e-6 - assert abs(lon + 7.694008395350697) < 1.0e-6 - assert abs(rad - 7172.15486518744) < 1.0e-6 - - -def test_local_horizontal_to_global_geo(): - """Tests the conversion of the local horizontal to global geo""" - - az = 30.0 - el = 45.0 - dist = 1000.0 - lat0 = 45.0 - lon0 = 0.0 - alt0 = 400.0 - - lat, lon, rad = \ - coords.local_horizontal_to_global_geo(az, el, dist, - lat0, lon0, alt0, - geodetic=False) - - assert abs(lat - 50.414315865044202) < 1.0e-6 - assert abs(lon + 7.6855551809119502) < 1.0e-6 - assert abs(rad - 7185.6983665760772) < 1.0e-6 +class TestGlobalLocal(): + + def setup(self): + """Runs before every method to create a clean testing setup.""" + arr = np.ones(shape=(10,), dtype=float) + self.val = {'x': 7000.0, 'y': 8000.0, 'z': 9000.0, + 'lat': 37.5, 'lon': 289.0, 'rad': 6380.0} + self.arr = {} + for key in self.val.keys(): + self.arr[key] = self.val[key] * arr + + def teardown(self): + """Runs after every method to clean up previous testing.""" + del self.val, self.arr, + + def test_global_to_local_cartesian_single(self): + """Test conversion from global to local cartesian coordinates""" + + x, y, z = coords.global_to_local_cartesian(self.val['x'], + self.val['y'], + self.val['z'], + self.val['lat'], + self.val['lon'], + self.val['rad']) + + assert abs(x + 9223.175264852474) < 1.0e-6 + assert abs(y + 2239.835278362686) < 1.0e-6 + assert abs(z - 11323.126851088331) < 1.0e-6 + + def test_local_cartesian_to_global_single(self): + """Test conversion from local cartesian to global coordinates""" + + x, y, z = coords.global_to_local_cartesian(self.val['x'], + self.val['y'], + self.val['z'], + self.val['lat'], + self.val['lon'], + self.val['rad'], + inverse=True) + + assert abs(x + 5709.804676635975) < 1.0e-6 + assert abs(y + 4918.397556010223) < 1.0e-6 + assert abs(z - 15709.577500484005) < 1.0e-6 + + def test_global_to_local_cartesian_mult(self): + """Test array conversion from global to local cartesian coordinates""" + + x, y, z = coords.global_to_local_cartesian(self.arr['x'], + self.arr['y'], + self.arr['z'], + self.arr['lat'], + self.arr['lon'], + self.arr['rad']) + + assert x.shape == self.arr['x'].shape + assert y.shape == self.arr['x'].shape + assert z.shape == self.arr['x'].shape + assert abs(x + 9223.175264852474).max() < 1.0e-6 + assert abs(y + 2239.835278362686).max() < 1.0e-6 + assert abs(z - 11323.126851088331).max() < 1.0e-6 + + def test_local_cartesian_to_global_mult(self): + """Test array conversion from local cartesian to global coordinates""" + + x, y, z = coords.global_to_local_cartesian(self.arr['x'], + self.arr['y'], + self.arr['z'], + self.arr['lat'], + self.arr['lon'], + self.arr['rad'], + inverse=True) + + assert x.shape == self.arr['x'].shape + assert y.shape == self.arr['x'].shape + assert z.shape == self.arr['x'].shape + assert abs(x + 5709.804676635975).max() < 1.0e-6 + assert abs(y + 4918.397556010223).max() < 1.0e-6 + assert abs(z - 15709.577500484005).max() < 1.0e-6 + + def test_global_to_local_cartesian_inverse(self): + """Tests the reversibility of the global to loc cartesian transform""" + + x2, y2, z2 = coords.global_to_local_cartesian(self.val['x'], + self.val['y'], + self.val['z'], + self.val['lat'], + self.val['lon'], + self.val['rad'], + inverse=False) + x3, y3, z3 = coords.global_to_local_cartesian(x2, y2, z2, + self.val['lat'], + self.val['lon'], + self.val['rad'], + inverse=True) + assert abs(self.val['x'] - x3) < 1.0e-6 + assert abs(self.val['y'] - y3) < 1.0e-6 + assert abs(self.val['z'] - z3) < 1.0e-6 + + +class TestLocalHorzGlobal(): + """Tests for local horizontal to global geo and back """ + + def setup(self): + """Runs before every method to create a clean testing setup.""" + arr = np.ones(shape=(10,), dtype=float) + self.val = {'az': 30.0, 'el': 45.0, 'dist': 1000.0, + 'lat': 45.0, 'lon': 0.0, 'alt': 400.0} + self.arr = {} + for key in self.val.keys(): + self.arr[key] = self.val[key] * arr + + def teardown(self): + """Runs after every method to clean up previous testing.""" + del self.val, self.arr, + + def test_local_horizontal_to_global_geo_geodetic(self): + """Tests the conversion of the local horizontal to global geo""" + + lat, lon, rad = \ + coords.local_horizontal_to_global_geo(self.val['az'], + self.val['el'], + self.val['dist'], + self.val['lat'], + self.val['lon'], + self.val['alt']) + + assert abs(lat - 50.419037572472625) < 1.0e-6 + assert abs(lon + 7.694008395350697) < 1.0e-6 + assert abs(rad - 7172.15486518744) < 1.0e-6 + + def test_local_horizontal_to_global_geo(self): + """Tests the conversion of the local horizontal to global geo""" + + lat, lon, rad = \ + coords.local_horizontal_to_global_geo(self.val['az'], + self.val['el'], + self.val['dist'], + self.val['lat'], + self.val['lon'], + self.val['alt'], + geodetic=False) + + assert abs(lat - 50.414315865044202) < 1.0e-6 + assert abs(lon + 7.6855551809119502) < 1.0e-6 + assert abs(rad - 7185.6983665760772) < 1.0e-6 + + def test_local_horizontal_to_global_geo_geodetic_mult(self): + """Tests the conversion of the local horizontal to global geo""" + + lat, lon, rad = \ + coords.local_horizontal_to_global_geo(self.arr['az'], + self.arr['el'], + self.arr['dist'], + self.arr['lat'], + self.arr['lon'], + self.arr['alt']) + + assert lat.shape == self.arr['lat'].shape + assert lon.shape == self.arr['lat'].shape + assert rad.shape == self.arr['lat'].shape + assert abs(lat - 50.419037572472625).max() < 1.0e-6 + assert abs(lon + 7.694008395350697).max() < 1.0e-6 + assert abs(rad - 7172.15486518744).max() < 1.0e-6 + + def test_local_horizontal_to_global_geo_mult(self): + """Tests the conversion of the local horizontal to global geo""" + + lat, lon, rad = \ + coords.local_horizontal_to_global_geo(self.arr['az'], + self.arr['el'], + self.arr['dist'], + self.arr['lat'], + self.arr['lon'], + self.arr['alt'], + geodetic=False) + + assert lat.shape == self.arr['lat'].shape + assert lon.shape == self.arr['lat'].shape + assert rad.shape == self.arr['lat'].shape + assert abs(lat - 50.414315865044202).max() < 1.0e-6 + assert abs(lon + 7.6855551809119502).max() < 1.0e-6 + assert abs(rad - 7185.6983665760772).max() < 1.0e-6 From b12b90dc8a47780505e74dee08b7449e11025001 Mon Sep 17 00:00:00 2001 From: Angeline Burrell Date: Mon, 13 Jul 2020 10:54:09 -0400 Subject: [PATCH 42/50] BUG: downstream changes Fixed downstream use of `coords` to use local coordinate routine. Also fixed import order. --- pysatMadrigal/instruments/jro_isr.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pysatMadrigal/instruments/jro_isr.py b/pysatMadrigal/instruments/jro_isr.py index b92d13a..dd0ca6d 100644 --- a/pysatMadrigal/instruments/jro_isr.py +++ b/pysatMadrigal/instruments/jro_isr.py @@ -35,13 +35,14 @@ from __future__ import absolute_import import datetime as dt import functools +import logging import numpy as np -from pysatMadrigal.instruments.methods import madrigal as mad_meth from pysat.instruments.methods import general as mm_gen -from pysat.utils import coords -import logging +from pysatMadrigal.instruments.methods import madrigal as mad_meth +from pysatMadrigal.utils import coords + logger = logging.getLogger(__name__) From 40fce9511c48c9d8efba9334653af1a9b78d1439 Mon Sep 17 00:00:00 2001 From: jklenzing Date: Mon, 13 Jul 2020 15:52:14 -0400 Subject: [PATCH 43/50] TST: modernize geod to geoc tests --- pysatMadrigal/tests/test_utils_coords.py | 95 +++++++++--------------- 1 file changed, 35 insertions(+), 60 deletions(-) diff --git a/pysatMadrigal/tests/test_utils_coords.py b/pysatMadrigal/tests/test_utils_coords.py index 3be51f5..6855246 100644 --- a/pysatMadrigal/tests/test_utils_coords.py +++ b/pysatMadrigal/tests/test_utils_coords.py @@ -3,9 +3,44 @@ """ import numpy as np +import pytest + from pysatMadrigal.utils import coords +@pytest.mark.parametrize("Nvals", [1, 10]) +@pytest.mark.parametrize("inverse,input,target", + [(False, [45.0, 8.0], + [44.8075768, 8.0, 6367.48954386]), + (True, [45.0, 8.0], + [45.1924232, 8.0, 6367.3459085])]) +def test_geodetic_to_geocentric_multi(Nvals, input, inverse, target): + """Test array conversion from geodetic to geocentric coordinates""" + + lat_in = input[0] * np.ones(shape=(Nvals,), dtype=float) + lon_in = input[1] * np.ones(shape=(Nvals,), dtype=float) + + output = coords.geodetic_to_geocentric(lat_in, lon_in=lon_in, + inverse=inverse) + + for i in range(3): + assert output[i].shape == lat_in.shape + assert abs(output[i] - target[i]).max() < 1.0e-6 + + +def test_geodetic_to_geocentric_and_back(): + """Tests the reversibility of geodetic to geocentric conversions""" + + input = [35.0, 17.0] + temp_vals = coords.geodetic_to_geocentric(input[0], lon_in=input[1], + inverse=False) + output = coords.geodetic_to_geocentric(temp_vals[0], + lon_in=temp_vals[1], + inverse=True) + for i in range(2): + assert abs(input[i] - output[i]) < 1.0e-6 + + class TestGeodeticGeocentric(): def setup(self): @@ -20,66 +55,6 @@ def teardown(self): """Runs after every method to clean up previous testing.""" del self.val, self.arr, - def test_geodetic_to_geocentric_single(self): - """Test conversion from geodetic to geocentric coordinates""" - - lat, lon, rad = coords.geodetic_to_geocentric(self.val['lat'], - lon_in=self.val['lon']) - - assert abs(lat - 44.807576784018046) < 1.0e-6 - assert abs(lon - 8.0) < 1.0e-6 - assert abs(rad - 6367.489543863465) < 1.0e-6 - - def test_geocentric_to_geodetic_single(self): - """Test conversion from geocentric to geodetic coordinates""" - - lat, lon, rad = coords.geodetic_to_geocentric(self.val['lat'], - lon_in=self.val['lon'], - inverse=True) - - assert abs(lat - 45.192423215981954) < 1.0e-6 - assert abs(lon - 8.0) < 1.0e-6 - assert abs(rad - 6367.345908499981) < 1.0e-6 - - def test_geodetic_to_geocentric_mult(self): - """Test array conversion from geodetic to geocentric coordinates""" - - lat, lon, rad = coords.geodetic_to_geocentric(self.arr['lat'], - lon_in=self.arr['lon']) - - assert lat.shape == self.arr['lat'].shape - assert lon.shape == self.arr['lat'].shape - assert rad.shape == self.arr['lat'].shape - assert abs(lat - 44.807576784018046).max() < 1.0e-6 - assert abs(lon - 8.0).max() < 1.0e-6 - assert abs(rad - 6367.489543863465).max() < 1.0e-6 - - def test_geocentric_to_geodetic_mult(self): - """Test array conversion from geocentric to geodetic coordinates""" - - lat, lon, rad = coords.geodetic_to_geocentric(self.arr['lat'], - lon_in=self.arr['lon'], - inverse=True) - - assert lat.shape == self.arr['lat'].shape - assert lon.shape == self.arr['lat'].shape - assert rad.shape == self.arr['lat'].shape - assert abs(lat - 45.192423215981954).max() < 1.0e-6 - assert abs(lon - 8.0).max() < 1.0e-6 - assert abs(rad - 6367.345908499981).max() < 1.0e-6 - - def test_geodetic_to_geocentric_inverse(self): - """Tests the reversibility of geodetic to geocentric conversions""" - - lat1 = 37.5 - lon1 = 117.3 - lat2, lon2, rad_e = coords.geodetic_to_geocentric(lat1, lon_in=lon1, - inverse=False) - lat3, lon3, rad_e = coords.geodetic_to_geocentric(lat2, lon_in=lon2, - inverse=True) - assert abs(lon1 - lon3) < 1.0e-6 - assert abs(lat1 - lat3) < 1.0e-6 - ############################################### # Geodetic / Geocentric Horizontal conversions From 4bbb9c44d64dcd1375b193429f3025e31eb4252a Mon Sep 17 00:00:00 2001 From: jklenzing Date: Thu, 23 Jul 2020 12:01:44 -0400 Subject: [PATCH 44/50] TST: make sure both ndarray and float are tested --- pysatMadrigal/tests/test_utils_coords.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/pysatMadrigal/tests/test_utils_coords.py b/pysatMadrigal/tests/test_utils_coords.py index 6855246..8021c21 100644 --- a/pysatMadrigal/tests/test_utils_coords.py +++ b/pysatMadrigal/tests/test_utils_coords.py @@ -14,18 +14,25 @@ [44.8075768, 8.0, 6367.48954386]), (True, [45.0, 8.0], [45.1924232, 8.0, 6367.3459085])]) -def test_geodetic_to_geocentric_multi(Nvals, input, inverse, target): +def test_geodetic_to_geocentric(Nvals, input, inverse, target): """Test array conversion from geodetic to geocentric coordinates""" - lat_in = input[0] * np.ones(shape=(Nvals,), dtype=float) - lon_in = input[1] * np.ones(shape=(Nvals,), dtype=float) + if Nvals > 1: + # inputs as an ndarray + lat_in = input[0] * np.ones(shape=(Nvals,), dtype=float) + lon_in = input[1] * np.ones(shape=(Nvals,), dtype=float) + else: + # inputs as a float + lat_in = input[0] + lon_in = input[1] output = coords.geodetic_to_geocentric(lat_in, lon_in=lon_in, inverse=inverse) - for i in range(3): - assert output[i].shape == lat_in.shape + for i in range(len(target)): assert abs(output[i] - target[i]).max() < 1.0e-6 + if Nvals > 1: + assert output[i].shape == lat_in.shape def test_geodetic_to_geocentric_and_back(): From fae4012421cd7c59ae2c9ca5102e7761008e48f1 Mon Sep 17 00:00:00 2001 From: jklenzing Date: Thu, 23 Jul 2020 12:20:10 -0400 Subject: [PATCH 45/50] BUG: test for float --- pysatMadrigal/tests/test_utils_coords.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pysatMadrigal/tests/test_utils_coords.py b/pysatMadrigal/tests/test_utils_coords.py index 8021c21..9d1d658 100644 --- a/pysatMadrigal/tests/test_utils_coords.py +++ b/pysatMadrigal/tests/test_utils_coords.py @@ -30,9 +30,11 @@ def test_geodetic_to_geocentric(Nvals, input, inverse, target): inverse=inverse) for i in range(len(target)): - assert abs(output[i] - target[i]).max() < 1.0e-6 if Nvals > 1: + assert abs(output[i] - target[i]).max() < 1.0e-6 assert output[i].shape == lat_in.shape + else: + assert abs(output[i] - target[i]) < 1.0e-6 def test_geodetic_to_geocentric_and_back(): From ce2cc0b28022959bd19746fbd378b48a6251a74d Mon Sep 17 00:00:00 2001 From: jklenzing Date: Sat, 15 Aug 2020 14:47:16 -0400 Subject: [PATCH 46/50] TST: rewrite using hydrid style --- pysatMadrigal/tests/test_utils_coords.py | 492 +++++++++-------------- 1 file changed, 185 insertions(+), 307 deletions(-) diff --git a/pysatMadrigal/tests/test_utils_coords.py b/pysatMadrigal/tests/test_utils_coords.py index 9d1d658..95d8ab9 100644 --- a/pysatMadrigal/tests/test_utils_coords.py +++ b/pysatMadrigal/tests/test_utils_coords.py @@ -8,257 +8,199 @@ from pysatMadrigal.utils import coords -@pytest.mark.parametrize("Nvals", [1, 10]) -@pytest.mark.parametrize("inverse,input,target", - [(False, [45.0, 8.0], - [44.8075768, 8.0, 6367.48954386]), - (True, [45.0, 8.0], - [45.1924232, 8.0, 6367.3459085])]) -def test_geodetic_to_geocentric(Nvals, input, inverse, target): - """Test array conversion from geodetic to geocentric coordinates""" - - if Nvals > 1: - # inputs as an ndarray - lat_in = input[0] * np.ones(shape=(Nvals,), dtype=float) - lon_in = input[1] * np.ones(shape=(Nvals,), dtype=float) - else: - # inputs as a float - lat_in = input[0] - lon_in = input[1] - - output = coords.geodetic_to_geocentric(lat_in, lon_in=lon_in, - inverse=inverse) - - for i in range(len(target)): - if Nvals > 1: - assert abs(output[i] - target[i]).max() < 1.0e-6 - assert output[i].shape == lat_in.shape - else: - assert abs(output[i] - target[i]) < 1.0e-6 - - -def test_geodetic_to_geocentric_and_back(): - """Tests the reversibility of geodetic to geocentric conversions""" - - input = [35.0, 17.0] - temp_vals = coords.geodetic_to_geocentric(input[0], lon_in=input[1], - inverse=False) - output = coords.geodetic_to_geocentric(temp_vals[0], - lon_in=temp_vals[1], - inverse=True) - for i in range(2): - assert abs(input[i] - output[i]) < 1.0e-6 - - class TestGeodeticGeocentric(): def setup(self): """Runs before every method to create a clean testing setup.""" - arr = np.ones(shape=(10,), dtype=float) self.val = {'lat': 45.0, 'lon': 8.0, 'az': 52.0, 'el': 63.0} - self.arr = {} - for key in self.val.keys(): - self.arr[key] = self.val[key] * arr def teardown(self): """Runs after every method to clean up previous testing.""" - del self.val, self.arr, - - ############################################### - # Geodetic / Geocentric Horizontal conversions + del self.val + + @pytest.mark.parametrize("kwargs,target", + [({}, [44.8075768, 8.0, 6367.48954386]), + ({'inverse': False}, + [44.8075768, 8.0, 6367.48954386]), + ({'inverse': True}, + [45.1924232, 8.0, 6367.3459085])]) + def test_geodetic_to_geocentric(self, kwargs, target): + """Test conversion from geodetic to geocentric coordinates""" - def test_geodetic_to_geocentric_horz_single(self): + lat, lon, rad = coords.geodetic_to_geocentric(self.val['lat'], + lon_in=self.val['lon'], + **kwargs) + + assert np.all(abs(lat - target[0]) < 1.0e-6) + assert np.all(abs(lon - target[1]) < 1.0e-6) + assert np.all(abs(rad - target[2]) < 1.0e-6) + if isinstance(lat, np.ndarray): + assert lat.shape == self.val['lat'].shape + assert lon.shape == self.val['lat'].shape + assert rad.shape == self.val['lat'].shape + + def test_geodetic_to_geocentric_and_back(self): + """Tests the reversibility of geodetic to geocentric conversions""" + + lat2, lon2, rad = coords.geodetic_to_geocentric(self.val['lat'], + lon_in=self.val['lon'], + inverse=False) + lat3, lon3, rad = coords.geodetic_to_geocentric(lat2, + lon_in=lon2, + inverse=True) + assert np.all(abs(self.val['lat'] - lat3) < 1.0e-6) + assert np.all(abs(self.val['lon'] - lon3) < 1.0e-6) + + @pytest.mark.parametrize("kwargs,target", + [({}, [44.8075768, 8.0, 6367.48954386, + 51.7037677, 62.8811403]), + ({'inverse': False}, + [44.8075768, 8.0, 6367.48954386, + 51.7037677, 62.8811403]), + ({'inverse': True}, + [45.1924232, 8.0, 6367.3459085, + 52.2989610, 63.1180720])]) + def test_geodetic_to_geocentric_horizontal(self, kwargs, target): """Test conversion from geodetic to geocentric coordinates""" lat, lon, rad, az, el = \ coords.geodetic_to_geocentric_horizontal(self.val['lat'], self.val['lon'], self.val['az'], - self.val['el']) + self.val['el'], + **kwargs) + + assert np.all(abs(lat - target[0]) < 1.0e-6) + assert np.all(abs(lon - target[1]) < 1.0e-6) + assert np.all(abs(rad - target[2]) < 1.0e-6) + assert np.all(abs(az - target[3]) < 1.0e-6) + assert np.all(abs(el - target[4]) < 1.0e-6) + if isinstance(lat, np.ndarray): + assert lat.shape == self.val['lat'].shape + assert lon.shape == self.val['lat'].shape + assert rad.shape == self.val['lat'].shape + assert az.shape == self.val['lat'].shape + assert el.shape == self.val['lat'].shape + + def test_geodetic_to_geocentric_horizontal_and_back(self): + """Tests the reversibility of geodetic to geocentric horiz conversions - assert abs(lat - 44.807576784018046) < 1.0e-6 - assert abs(lon - 8.0) < 1.0e-6 - assert abs(rad - 6367.489543863465) < 1.0e-6 - assert abs(az - 51.70376774257361) < 1.0e-6 - assert abs(el - 62.8811403841008) < 1.0e-6 + Note: inverse of az and el angles currently non-functional - def test_geocentric_to_geodetic_horz_single(self): - """Test conversion from geocentric to geodetic coordinates""" + """ - lat, lon, rad, az, el = \ + lat2, lon2, rad_e, az2, el2 = \ coords.geodetic_to_geocentric_horizontal(self.val['lat'], self.val['lon'], self.val['az'], self.val['el'], - inverse=True) - - assert abs(lat - 45.192423215981954) < 1.0e-6 - assert abs(lon - 8.0) < 1.0e-6 - assert abs(rad - 6367.345908499981) < 1.0e-6 - assert abs(az - 52.29896101551479) < 1.0e-6 - assert abs(el - 63.118072033649916) < 1.0e-6 - - def test_geodetic_to_geocentric_horz_mult(self): - """Test array conversion from geodetic to geocentric coordinates""" - - lat, lon, rad, az, el = \ - coords.geodetic_to_geocentric_horizontal(self.arr['lat'], - self.arr['lon'], - self.arr['az'], - self.arr['el']) - - assert lat.shape == self.arr['lat'].shape - assert lon.shape == self.arr['lat'].shape - assert rad.shape == self.arr['lat'].shape - assert az.shape == self.arr['lat'].shape - assert el.shape == self.arr['lat'].shape - assert abs(lat - 44.807576784018046).max() < 1.0e-6 - assert abs(lon - 8.0).max() < 1.0e-6 - assert abs(rad - 6367.489543863465).max() < 1.0e-6 - assert abs(az - 51.70376774257361).max() < 1.0e-6 - assert abs(el - 62.8811403841008).max() < 1.0e-6 - - def test_geocentric_to_geodetic_horz_mult(self): - """Test array conversion from geocentric to geodetic coordinates""" - - lat, lon, rad, az, el = \ - coords.geodetic_to_geocentric_horizontal(self.arr['lat'], - self.arr['lon'], - self.arr['az'], - self.arr['el'], - inverse=True) - - assert lat.shape == self.arr['lat'].shape - assert lon.shape == self.arr['lat'].shape - assert rad.shape == self.arr['lat'].shape - assert az.shape == self.arr['lat'].shape - assert el.shape == self.arr['lat'].shape - assert abs(lat - 45.192423215981954).max() < 1.0e-6 - assert abs(lon - 8.0).max() < 1.0e-6 - assert abs(rad - 6367.345908499981).max() < 1.0e-6 - assert abs(az - 52.29896101551479).max() < 1.0e-6 - assert abs(el - 63.118072033649916).max() < 1.0e-6 - - def test_geodetic_to_geocentric_horizontal_inverse(self): - """Tests the reversibility of geodetic to geocentric horiz conversions - - Note: inverse of az and el angles currently non-functional""" - - lat1 = -17.5 - lon1 = 187.3 - az1 = 52.0 - el1 = 63.0 - lat2, lon2, rad_e, az2, el2 = \ - coords.geodetic_to_geocentric_horizontal(lat1, lon1, az1, el1, inverse=False) lat3, lon3, rad_e, az3, el3 = \ coords.geodetic_to_geocentric_horizontal(lat2, lon2, az2, el2, inverse=True) - assert abs(lon1 - lon3) < 1.0e-6 - assert abs(lat1 - lat3) < 1.0e-6 - assert abs(az1 - az3) < 1.0e-6 - assert abs(el1 - el3) < 1.0e-6 + assert np.all(abs(self.val['lon'] - lon3) < 1.0e-6) + assert np.all(abs(self.val['lat'] - lat3) < 1.0e-6) + assert np.all(abs(self.val['az'] - az3) < 1.0e-6) + assert np.all(abs(self.val['el'] - el3) < 1.0e-6) -class TestSphereCart(): +class TestGeodeticGeocentricArray(TestGeodeticGeocentric): def setup(self): """Runs before every method to create a clean testing setup.""" arr = np.ones(shape=(10,), dtype=float) - self.val = {'az': 45.0, 'el': 30.0, 'r': 1.0, - 'x': 0.6123724356957946, 'z': 0.5} - self.arr = {} - for key in self.val.keys(): - self.arr[key] = self.val[key] * arr + self.val = {'lat': 45.0 * arr, + 'lon': 8.0 * arr, + 'az': 52.0 * arr, + 'el': 63.0 * arr} def teardown(self): """Runs after every method to clean up previous testing.""" - del self.val, self.arr, - - def test_spherical_to_cartesian_single(self): - """Test conversion from spherical to cartesian coordinates""" - - x, y, z = coords.spherical_to_cartesian(self.val['az'], - self.val['el'], - self.val['r']) - - assert abs(x - y) < 1.0e-6 - assert abs(z - 0.5) < 1.0e-6 + del self.val - def test_cartesian_to_spherical_single(self): - """Test conversion from cartesian to spherical coordinates""" - az, el, r = coords.spherical_to_cartesian(self.val['x'], self.val['x'], - self.val['z'], inverse=True) +class TestSphereCartesian(): - assert abs(az - 45.0) < 1.0e-6 - assert abs(el - 30.0) < 1.0e-6 - assert abs(r - 1.0) < 1.0e-6 - - def test_spherical_to_cartesian_mult(self): - """Test array conversion from spherical to cartesian coordinates""" - - x, y, z = coords.spherical_to_cartesian(self.arr['az'], - self.arr['el'], - self.arr['r']) - - assert x.shape == self.arr['x'].shape - assert y.shape == self.arr['x'].shape - assert z.shape == self.arr['x'].shape - assert abs(x - y).max() < 1.0e-6 - assert abs(z - 0.5).max() < 1.0e-6 + def setup(self): + """Runs before every method to create a clean testing setup.""" + self.val = {'az': 45.0, 'el': 30.0, 'r': 1.0, + 'x': 0.6123724356957946, 'z': 0.5} - def test_cartesian_to_spherical_mult(self): - """Test array conversion from cartesian to spherical coordinates""" + def teardown(self): + """Runs after every method to clean up previous testing.""" + del self.val + + @pytest.mark.parametrize("kwargs,input,target", + [({}, ['az', 'el', 'r'], + ['x', 'x', 'z']), + ({'inverse': False}, ['az', 'el', 'r'], + ['x', 'x', 'z']), + ({'inverse': True}, ['x', 'x', 'z'], + ['az', 'el', 'r'])]) + def test_spherical_to_cartesian(self, kwargs, input, target): + """Test conversion from spherical to cartesian coordinates""" - az, el, r = coords.spherical_to_cartesian(self.arr['x'], - self.arr['x'], - self.arr['z'], - inverse=True) + a, b, c = coords.spherical_to_cartesian(self.val[input[0]], + self.val[input[1]], + self.val[input[2]], + **kwargs) - assert az.shape == self.arr['x'].shape - assert el.shape == self.arr['x'].shape - assert r.shape == self.arr['x'].shape - assert abs(az - 45.0).max() < 1.0e-6 - assert abs(el - 30.0).max() < 1.0e-6 - assert abs(r - 1.0).max() < 1.0e-6 + assert np.all(abs(a - self.val[target[0]]) < 1.0e-6) + assert np.all(abs(b - self.val[target[1]]) < 1.0e-6) + assert np.all(abs(c - self.val[target[2]]) < 1.0e-6) + if isinstance(self.val['az'], np.ndarray): + assert a.shape == self.val['x'].shape + assert b.shape == self.val['x'].shape + assert c.shape == self.val['x'].shape - def test_spherical_to_cartesian_inverse(self): + def test_spherical_to_cartesian_and_back(self): """Tests the reversibility of spherical to cartesian conversions""" - x1 = 3000.0 - y1 = 2000.0 - z1 = 2500.0 - az, el, r = coords.spherical_to_cartesian(x1, y1, z1, + az, el, r = coords.spherical_to_cartesian(self.val['x'], + self.val['x'], + self.val['z'], inverse=True) x2, y2, z2 = coords.spherical_to_cartesian(az, el, r, inverse=False) - assert abs(x1 - x2) < 1.0e-6 - assert abs(y1 - y2) < 1.0e-6 - assert abs(z1 - z2) < 1.0e-6 + assert np.all(abs(self.val['x'] - x2) < 1.0e-6) + assert np.all(abs(self.val['x'] - y2) < 1.0e-6) + assert np.all(abs(self.val['z'] - z2) < 1.0e-6) + +class TestSphereCartesianArray(TestSphereCartesian): + + def setup(self): + """Runs before every method to create a clean testing setup.""" + arr = np.ones(shape=(10,), dtype=float) + self.val = {'az': 45.0 * arr, 'el': 30.0 * arr, 'r': 1.0 * arr, + 'x': 0.6123724356957946 * arr, 'z': 0.5 * arr} + + def teardown(self): + """Runs after every method to clean up previous testing.""" + del self.val -######################################## -# Global / Local Cartesian conversions class TestGlobalLocal(): def setup(self): """Runs before every method to create a clean testing setup.""" - arr = np.ones(shape=(10,), dtype=float) self.val = {'x': 7000.0, 'y': 8000.0, 'z': 9000.0, 'lat': 37.5, 'lon': 289.0, 'rad': 6380.0} - self.arr = {} - for key in self.val.keys(): - self.arr[key] = self.val[key] * arr def teardown(self): """Runs after every method to clean up previous testing.""" - del self.val, self.arr, - - def test_global_to_local_cartesian_single(self): + del self.val + + @pytest.mark.parametrize("kwargs,target", + [({}, + [-9223.1752649, -2239.8352784, 11323.1268511]), + ({'inverse': False}, + [-9223.1752649, -2239.8352784, 11323.1268511]), + ({'inverse': True}, + [-5709.804677, -4918.397556, 15709.5775005])]) + def test_global_to_local_cartesian(self, kwargs, target): """Test conversion from global to local cartesian coordinates""" x, y, z = coords.global_to_local_cartesian(self.val['x'], @@ -266,63 +208,18 @@ def test_global_to_local_cartesian_single(self): self.val['z'], self.val['lat'], self.val['lon'], - self.val['rad']) - - assert abs(x + 9223.175264852474) < 1.0e-6 - assert abs(y + 2239.835278362686) < 1.0e-6 - assert abs(z - 11323.126851088331) < 1.0e-6 + self.val['rad'], + **kwargs) - def test_local_cartesian_to_global_single(self): - """Test conversion from local cartesian to global coordinates""" + assert np.all(abs(x - target[0]) < 1.0e-6) + assert np.all(abs(y - target[1]) < 1.0e-6) + assert np.all(abs(z - target[2]) < 1.0e-6) + if isinstance(self.val['x'], np.ndarray): + assert x.shape == self.val['x'].shape + assert y.shape == self.val['x'].shape + assert z.shape == self.val['x'].shape - x, y, z = coords.global_to_local_cartesian(self.val['x'], - self.val['y'], - self.val['z'], - self.val['lat'], - self.val['lon'], - self.val['rad'], - inverse=True) - - assert abs(x + 5709.804676635975) < 1.0e-6 - assert abs(y + 4918.397556010223) < 1.0e-6 - assert abs(z - 15709.577500484005) < 1.0e-6 - - def test_global_to_local_cartesian_mult(self): - """Test array conversion from global to local cartesian coordinates""" - - x, y, z = coords.global_to_local_cartesian(self.arr['x'], - self.arr['y'], - self.arr['z'], - self.arr['lat'], - self.arr['lon'], - self.arr['rad']) - - assert x.shape == self.arr['x'].shape - assert y.shape == self.arr['x'].shape - assert z.shape == self.arr['x'].shape - assert abs(x + 9223.175264852474).max() < 1.0e-6 - assert abs(y + 2239.835278362686).max() < 1.0e-6 - assert abs(z - 11323.126851088331).max() < 1.0e-6 - - def test_local_cartesian_to_global_mult(self): - """Test array conversion from local cartesian to global coordinates""" - - x, y, z = coords.global_to_local_cartesian(self.arr['x'], - self.arr['y'], - self.arr['z'], - self.arr['lat'], - self.arr['lon'], - self.arr['rad'], - inverse=True) - - assert x.shape == self.arr['x'].shape - assert y.shape == self.arr['x'].shape - assert z.shape == self.arr['x'].shape - assert abs(x + 5709.804676635975).max() < 1.0e-6 - assert abs(y + 4918.397556010223).max() < 1.0e-6 - assert abs(z - 15709.577500484005).max() < 1.0e-6 - - def test_global_to_local_cartesian_inverse(self): + def test_global_to_local_cartesian_and_back(self): """Tests the reversibility of the global to loc cartesian transform""" x2, y2, z2 = coords.global_to_local_cartesian(self.val['x'], @@ -337,43 +234,43 @@ def test_global_to_local_cartesian_inverse(self): self.val['lon'], self.val['rad'], inverse=True) - assert abs(self.val['x'] - x3) < 1.0e-6 - assert abs(self.val['y'] - y3) < 1.0e-6 - assert abs(self.val['z'] - z3) < 1.0e-6 + assert np.all(abs(self.val['x'] - x3) < 1.0e-6) + assert np.all(abs(self.val['y'] - y3) < 1.0e-6) + assert np.all(abs(self.val['z'] - z3) < 1.0e-6) -class TestLocalHorzGlobal(): - """Tests for local horizontal to global geo and back """ +class TestGlobalLocalArray(TestGlobalLocal): def setup(self): """Runs before every method to create a clean testing setup.""" arr = np.ones(shape=(10,), dtype=float) - self.val = {'az': 30.0, 'el': 45.0, 'dist': 1000.0, - 'lat': 45.0, 'lon': 0.0, 'alt': 400.0} - self.arr = {} - for key in self.val.keys(): - self.arr[key] = self.val[key] * arr + self.val = {'x': 7000.0 * arr, 'y': 8000.0 * arr, 'z': 9000.0 * arr, + 'lat': 37.5 * arr, 'lon': 289.0 * arr, 'rad': 6380.0 * arr} def teardown(self): """Runs after every method to clean up previous testing.""" - del self.val, self.arr, + del self.val - def test_local_horizontal_to_global_geo_geodetic(self): - """Tests the conversion of the local horizontal to global geo""" - lat, lon, rad = \ - coords.local_horizontal_to_global_geo(self.val['az'], - self.val['el'], - self.val['dist'], - self.val['lat'], - self.val['lon'], - self.val['alt']) +class TestLocalHorizontalGlobal(): + """Tests for local horizontal to global geo and back """ - assert abs(lat - 50.419037572472625) < 1.0e-6 - assert abs(lon + 7.694008395350697) < 1.0e-6 - assert abs(rad - 7172.15486518744) < 1.0e-6 + def setup(self): + """Runs before every method to create a clean testing setup.""" + self.val = {'az': 30.0, 'el': 45.0, 'dist': 1000.0, + 'lat': 45.0, 'lon': 0.0, 'alt': 400.0} - def test_local_horizontal_to_global_geo(self): + def teardown(self): + """Runs after every method to clean up previous testing.""" + del self.val + + @pytest.mark.parametrize("kwargs,target", + [({}, [50.4190376, -7.6940084, 7172.1548652]), + ({'geodetic': True}, + [50.4190376, -7.6940084, 7172.1548652]), + ({'geodetic': False}, + [50.4143159, -7.6855552, 7185.6983666])]) + def test_local_horizontal_to_global_geo(self, kwargs, target): """Tests the conversion of the local horizontal to global geo""" lat, lon, rad = \ @@ -383,45 +280,26 @@ def test_local_horizontal_to_global_geo(self): self.val['lat'], self.val['lon'], self.val['alt'], - geodetic=False) + **kwargs) - assert abs(lat - 50.414315865044202) < 1.0e-6 - assert abs(lon + 7.6855551809119502) < 1.0e-6 - assert abs(rad - 7185.6983665760772) < 1.0e-6 + assert np.all(abs(lat - target[0]) < 1.0e-6) + assert np.all(abs(lon - target[1]) < 1.0e-6) + assert np.all(abs(rad - target[2]) < 1.0e-6) + if isinstance(self.val['lat'], np.ndarray): + assert lat.shape == self.val['lat'].shape + assert lon.shape == self.val['lat'].shape + assert rad.shape == self.val['lat'].shape - def test_local_horizontal_to_global_geo_geodetic_mult(self): - """Tests the conversion of the local horizontal to global geo""" - lat, lon, rad = \ - coords.local_horizontal_to_global_geo(self.arr['az'], - self.arr['el'], - self.arr['dist'], - self.arr['lat'], - self.arr['lon'], - self.arr['alt']) - - assert lat.shape == self.arr['lat'].shape - assert lon.shape == self.arr['lat'].shape - assert rad.shape == self.arr['lat'].shape - assert abs(lat - 50.419037572472625).max() < 1.0e-6 - assert abs(lon + 7.694008395350697).max() < 1.0e-6 - assert abs(rad - 7172.15486518744).max() < 1.0e-6 - - def test_local_horizontal_to_global_geo_mult(self): - """Tests the conversion of the local horizontal to global geo""" +class TestLocalHorizontalGlobalArray(TestLocalHorizontalGlobal): + """Tests for local horizontal to global geo and back """ - lat, lon, rad = \ - coords.local_horizontal_to_global_geo(self.arr['az'], - self.arr['el'], - self.arr['dist'], - self.arr['lat'], - self.arr['lon'], - self.arr['alt'], - geodetic=False) - - assert lat.shape == self.arr['lat'].shape - assert lon.shape == self.arr['lat'].shape - assert rad.shape == self.arr['lat'].shape - assert abs(lat - 50.414315865044202).max() < 1.0e-6 - assert abs(lon + 7.6855551809119502).max() < 1.0e-6 - assert abs(rad - 7185.6983665760772).max() < 1.0e-6 + def setup(self): + """Runs before every method to create a clean testing setup.""" + arr = np.ones(shape=(10,), dtype=float) + self.val = {'az': 30.0 * arr, 'el': 45.0 * arr, 'dist': 1000.0 * arr, + 'lat': 45.0 * arr, 'lon': 0.0 * arr, 'alt': 400.0 * arr} + + def teardown(self): + """Runs after every method to clean up previous testing.""" + del self.val From 3281bf36ca90eefa2a72ba9e861d411dbd4d28c5 Mon Sep 17 00:00:00 2001 From: jklenzing Date: Sat, 15 Aug 2020 14:50:41 -0400 Subject: [PATCH 47/50] STY: use x and y --- pysatMadrigal/tests/test_utils_coords.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/pysatMadrigal/tests/test_utils_coords.py b/pysatMadrigal/tests/test_utils_coords.py index 95d8ab9..7a8c8a3 100644 --- a/pysatMadrigal/tests/test_utils_coords.py +++ b/pysatMadrigal/tests/test_utils_coords.py @@ -125,7 +125,9 @@ class TestSphereCartesian(): def setup(self): """Runs before every method to create a clean testing setup.""" self.val = {'az': 45.0, 'el': 30.0, 'r': 1.0, - 'x': 0.6123724356957946, 'z': 0.5} + 'x': 0.6123724356957946, + 'y': 0.6123724356957946, + 'z': 0.5} def teardown(self): """Runs after every method to clean up previous testing.""" @@ -133,10 +135,10 @@ def teardown(self): @pytest.mark.parametrize("kwargs,input,target", [({}, ['az', 'el', 'r'], - ['x', 'x', 'z']), + ['x', 'y', 'z']), ({'inverse': False}, ['az', 'el', 'r'], - ['x', 'x', 'z']), - ({'inverse': True}, ['x', 'x', 'z'], + ['x', 'y', 'z']), + ({'inverse': True}, ['x', 'y', 'z'], ['az', 'el', 'r'])]) def test_spherical_to_cartesian(self, kwargs, input, target): """Test conversion from spherical to cartesian coordinates""" @@ -158,14 +160,14 @@ def test_spherical_to_cartesian_and_back(self): """Tests the reversibility of spherical to cartesian conversions""" az, el, r = coords.spherical_to_cartesian(self.val['x'], - self.val['x'], + self.val['y'], self.val['z'], inverse=True) x2, y2, z2 = coords.spherical_to_cartesian(az, el, r, inverse=False) assert np.all(abs(self.val['x'] - x2) < 1.0e-6) - assert np.all(abs(self.val['x'] - y2) < 1.0e-6) + assert np.all(abs(self.val['y'] - y2) < 1.0e-6) assert np.all(abs(self.val['z'] - z2) < 1.0e-6) @@ -175,7 +177,9 @@ def setup(self): """Runs before every method to create a clean testing setup.""" arr = np.ones(shape=(10,), dtype=float) self.val = {'az': 45.0 * arr, 'el': 30.0 * arr, 'r': 1.0 * arr, - 'x': 0.6123724356957946 * arr, 'z': 0.5 * arr} + 'x': 0.6123724356957946 * arr, + 'y': 0.6123724356957946 * arr, + 'z': 0.5 * arr} def teardown(self): """Runs after every method to clean up previous testing.""" From 8cf12dffb69c1682c39c736093ecbc77faa52d59 Mon Sep 17 00:00:00 2001 From: jklenzing Date: Sat, 15 Aug 2020 18:51:49 -0400 Subject: [PATCH 48/50] STY: whitespace --- pysatMadrigal/tests/test_utils_coords.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysatMadrigal/tests/test_utils_coords.py b/pysatMadrigal/tests/test_utils_coords.py index 7a8c8a3..f88a842 100644 --- a/pysatMadrigal/tests/test_utils_coords.py +++ b/pysatMadrigal/tests/test_utils_coords.py @@ -136,7 +136,7 @@ def teardown(self): @pytest.mark.parametrize("kwargs,input,target", [({}, ['az', 'el', 'r'], ['x', 'y', 'z']), - ({'inverse': False}, ['az', 'el', 'r'], + ({'inverse': False}, ['az', 'el', 'r'], ['x', 'y', 'z']), ({'inverse': True}, ['x', 'y', 'z'], ['az', 'el', 'r'])]) From 9e6cff920ed5eb047b939c27bc8bd1273cb0e001 Mon Sep 17 00:00:00 2001 From: Jeff Klenzing Date: Mon, 17 Aug 2020 09:30:22 -0400 Subject: [PATCH 49/50] STY: minimize local variables in tests Co-authored-by: Angeline Burrell --- pysatMadrigal/tests/test_utils_coords.py | 56 ++++++++++-------------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/pysatMadrigal/tests/test_utils_coords.py b/pysatMadrigal/tests/test_utils_coords.py index f88a842..6b1ef40 100644 --- a/pysatMadrigal/tests/test_utils_coords.py +++ b/pysatMadrigal/tests/test_utils_coords.py @@ -13,10 +13,12 @@ class TestGeodeticGeocentric(): def setup(self): """Runs before every method to create a clean testing setup.""" self.val = {'lat': 45.0, 'lon': 8.0, 'az': 52.0, 'el': 63.0} + self.out = None + self.loc = None def teardown(self): """Runs after every method to clean up previous testing.""" - del self.val + del self.val, self.out, self.loc @pytest.mark.parametrize("kwargs,target", [({}, [44.8075768, 8.0, 6367.48954386]), @@ -31,25 +33,22 @@ def test_geodetic_to_geocentric(self, kwargs, target): lon_in=self.val['lon'], **kwargs) - assert np.all(abs(lat - target[0]) < 1.0e-6) - assert np.all(abs(lon - target[1]) < 1.0e-6) - assert np.all(abs(rad - target[2]) < 1.0e-6) - if isinstance(lat, np.ndarray): - assert lat.shape == self.val['lat'].shape - assert lon.shape == self.val['lat'].shape - assert rad.shape == self.val['lat'].shape + for i, self.loc in enumerate(self.out): + assert np.all(abs(self.loc - target[i]) < 1.0e-6) + if isinstance(self.loc, np.ndarray): + assert self.loc.shape == self.val['lat'].shape def test_geodetic_to_geocentric_and_back(self): """Tests the reversibility of geodetic to geocentric conversions""" - lat2, lon2, rad = coords.geodetic_to_geocentric(self.val['lat'], + self.out = coords.geodetic_to_geocentric(self.val['lat'], lon_in=self.val['lon'], inverse=False) - lat3, lon3, rad = coords.geodetic_to_geocentric(lat2, - lon_in=lon2, + self.loc = coords.geodetic_to_geocentric(self.out[0], + lon_in=self.out[1], inverse=True) - assert np.all(abs(self.val['lat'] - lat3) < 1.0e-6) - assert np.all(abs(self.val['lon'] - lon3) < 1.0e-6) + assert np.all(abs(self.val['lat'] - self.loc[0]) < 1.0e-6) + assert np.all(abs(self.val['lon'] - self.loc[1]) < 1.0e-6) @pytest.mark.parametrize("kwargs,target", [({}, [44.8075768, 8.0, 6367.48954386, @@ -63,24 +62,17 @@ def test_geodetic_to_geocentric_and_back(self): def test_geodetic_to_geocentric_horizontal(self, kwargs, target): """Test conversion from geodetic to geocentric coordinates""" - lat, lon, rad, az, el = \ + self.out = \ coords.geodetic_to_geocentric_horizontal(self.val['lat'], self.val['lon'], self.val['az'], self.val['el'], **kwargs) - assert np.all(abs(lat - target[0]) < 1.0e-6) - assert np.all(abs(lon - target[1]) < 1.0e-6) - assert np.all(abs(rad - target[2]) < 1.0e-6) - assert np.all(abs(az - target[3]) < 1.0e-6) - assert np.all(abs(el - target[4]) < 1.0e-6) - if isinstance(lat, np.ndarray): - assert lat.shape == self.val['lat'].shape - assert lon.shape == self.val['lat'].shape - assert rad.shape == self.val['lat'].shape - assert az.shape == self.val['lat'].shape - assert el.shape == self.val['lat'].shape + for i, self.loc in enumerate(self.out): + assert np.all(abs(self.loc - target[i]) < 1.0e-6) + if isinstance(self.loc, np.ndarray): + assert self.loc.shape == self.val['lat'].shape def test_geodetic_to_geocentric_horizontal_and_back(self): """Tests the reversibility of geodetic to geocentric horiz conversions @@ -89,20 +81,20 @@ def test_geodetic_to_geocentric_horizontal_and_back(self): """ - lat2, lon2, rad_e, az2, el2 = \ + self.out = \ coords.geodetic_to_geocentric_horizontal(self.val['lat'], self.val['lon'], self.val['az'], self.val['el'], inverse=False) - lat3, lon3, rad_e, az3, el3 = \ - coords.geodetic_to_geocentric_horizontal(lat2, lon2, az2, el2, + self.loc = \ + coords.geodetic_to_geocentric_horizontal(self.out[0], self.out[1], self.out[3], self.out[4], inverse=True) - assert np.all(abs(self.val['lon'] - lon3) < 1.0e-6) - assert np.all(abs(self.val['lat'] - lat3) < 1.0e-6) - assert np.all(abs(self.val['az'] - az3) < 1.0e-6) - assert np.all(abs(self.val['el'] - el3) < 1.0e-6) + assert np.all(abs(self.val['lon'] - self.loc[1]) < 1.0e-6) + assert np.all(abs(self.val['lat'] - self.loc[0]) < 1.0e-6) + assert np.all(abs(self.val['az'] - self.loc[3]) < 1.0e-6) + assert np.all(abs(self.val['el'] - self.loc[4]) < 1.0e-6) class TestGeodeticGeocentricArray(TestGeodeticGeocentric): From 91a8cd236f1da35051e1cdd2338c27953584feb8 Mon Sep 17 00:00:00 2001 From: jklenzing Date: Mon, 17 Aug 2020 10:00:31 -0400 Subject: [PATCH 50/50] STY: compact tests --- pysatMadrigal/tests/test_utils_coords.py | 193 ++++++++++++----------- 1 file changed, 98 insertions(+), 95 deletions(-) diff --git a/pysatMadrigal/tests/test_utils_coords.py b/pysatMadrigal/tests/test_utils_coords.py index 6b1ef40..4b2549e 100644 --- a/pysatMadrigal/tests/test_utils_coords.py +++ b/pysatMadrigal/tests/test_utils_coords.py @@ -29,9 +29,9 @@ def teardown(self): def test_geodetic_to_geocentric(self, kwargs, target): """Test conversion from geodetic to geocentric coordinates""" - lat, lon, rad = coords.geodetic_to_geocentric(self.val['lat'], - lon_in=self.val['lon'], - **kwargs) + self.out = coords.geodetic_to_geocentric(self.val['lat'], + lon_in=self.val['lon'], + **kwargs) for i, self.loc in enumerate(self.out): assert np.all(abs(self.loc - target[i]) < 1.0e-6) @@ -42,11 +42,11 @@ def test_geodetic_to_geocentric_and_back(self): """Tests the reversibility of geodetic to geocentric conversions""" self.out = coords.geodetic_to_geocentric(self.val['lat'], - lon_in=self.val['lon'], - inverse=False) + lon_in=self.val['lon'], + inverse=False) self.loc = coords.geodetic_to_geocentric(self.out[0], - lon_in=self.out[1], - inverse=True) + lon_in=self.out[1], + inverse=True) assert np.all(abs(self.val['lat'] - self.loc[0]) < 1.0e-6) assert np.all(abs(self.val['lon'] - self.loc[1]) < 1.0e-6) @@ -62,12 +62,11 @@ def test_geodetic_to_geocentric_and_back(self): def test_geodetic_to_geocentric_horizontal(self, kwargs, target): """Test conversion from geodetic to geocentric coordinates""" - self.out = \ - coords.geodetic_to_geocentric_horizontal(self.val['lat'], - self.val['lon'], - self.val['az'], - self.val['el'], - **kwargs) + self.out = coords.geodetic_to_geocentric_horizontal(self.val['lat'], + self.val['lon'], + self.val['az'], + self.val['el'], + **kwargs) for i, self.loc in enumerate(self.out): assert np.all(abs(self.loc - target[i]) < 1.0e-6) @@ -81,18 +80,19 @@ def test_geodetic_to_geocentric_horizontal_and_back(self): """ - self.out = \ - coords.geodetic_to_geocentric_horizontal(self.val['lat'], - self.val['lon'], - self.val['az'], - self.val['el'], - inverse=False) - self.loc = \ - coords.geodetic_to_geocentric_horizontal(self.out[0], self.out[1], self.out[3], self.out[4], - inverse=True) + self.out = coords.geodetic_to_geocentric_horizontal(self.val['lat'], + self.val['lon'], + self.val['az'], + self.val['el'], + inverse=False) + self.loc = coords.geodetic_to_geocentric_horizontal(self.out[0], + self.out[1], + self.out[3], + self.out[4], + inverse=True) - assert np.all(abs(self.val['lon'] - self.loc[1]) < 1.0e-6) assert np.all(abs(self.val['lat'] - self.loc[0]) < 1.0e-6) + assert np.all(abs(self.val['lon'] - self.loc[1]) < 1.0e-6) assert np.all(abs(self.val['az'] - self.loc[3]) < 1.0e-6) assert np.all(abs(self.val['el'] - self.loc[4]) < 1.0e-6) @@ -106,10 +106,12 @@ def setup(self): 'lon': 8.0 * arr, 'az': 52.0 * arr, 'el': 63.0 * arr} + self.out = None + self.loc = None def teardown(self): """Runs after every method to clean up previous testing.""" - del self.val + del self.val, self.out, self.loc class TestSphereCartesian(): @@ -120,10 +122,12 @@ def setup(self): 'x': 0.6123724356957946, 'y': 0.6123724356957946, 'z': 0.5} + self.out = None + self.loc = None def teardown(self): """Runs after every method to clean up previous testing.""" - del self.val + del self.val, self.out, self.loc @pytest.mark.parametrize("kwargs,input,target", [({}, ['az', 'el', 'r'], @@ -135,32 +139,27 @@ def teardown(self): def test_spherical_to_cartesian(self, kwargs, input, target): """Test conversion from spherical to cartesian coordinates""" - a, b, c = coords.spherical_to_cartesian(self.val[input[0]], - self.val[input[1]], - self.val[input[2]], - **kwargs) + self.out = coords.spherical_to_cartesian(self.val[input[0]], + self.val[input[1]], + self.val[input[2]], + **kwargs) - assert np.all(abs(a - self.val[target[0]]) < 1.0e-6) - assert np.all(abs(b - self.val[target[1]]) < 1.0e-6) - assert np.all(abs(c - self.val[target[2]]) < 1.0e-6) - if isinstance(self.val['az'], np.ndarray): - assert a.shape == self.val['x'].shape - assert b.shape == self.val['x'].shape - assert c.shape == self.val['x'].shape + for i, self.loc in enumerate(self.out): + assert np.all(abs(self.loc - self.val[target[i]]) < 1.0e-6) + if isinstance(self.loc, np.ndarray): + assert self.loc.shape == self.val[input[0]].shape def test_spherical_to_cartesian_and_back(self): """Tests the reversibility of spherical to cartesian conversions""" - az, el, r = coords.spherical_to_cartesian(self.val['x'], - self.val['y'], - self.val['z'], - inverse=True) - x2, y2, z2 = coords.spherical_to_cartesian(az, el, r, - inverse=False) + self.out = coords.spherical_to_cartesian(self.val['x'], self.val['y'], + self.val['z'], inverse=True) + self.out = coords.spherical_to_cartesian(self.out[0], self.out[1], + self.out[2], inverse=False) - assert np.all(abs(self.val['x'] - x2) < 1.0e-6) - assert np.all(abs(self.val['y'] - y2) < 1.0e-6) - assert np.all(abs(self.val['z'] - z2) < 1.0e-6) + assert np.all(abs(self.val['x'] - self.out[0]) < 1.0e-6) + assert np.all(abs(self.val['y'] - self.out[1]) < 1.0e-6) + assert np.all(abs(self.val['z'] - self.out[2]) < 1.0e-6) class TestSphereCartesianArray(TestSphereCartesian): @@ -172,10 +171,12 @@ def setup(self): 'x': 0.6123724356957946 * arr, 'y': 0.6123724356957946 * arr, 'z': 0.5 * arr} + self.out = None + self.loc = None def teardown(self): """Runs after every method to clean up previous testing.""" - del self.val + del self.val, self.out, self.loc class TestGlobalLocal(): @@ -184,10 +185,12 @@ def setup(self): """Runs before every method to create a clean testing setup.""" self.val = {'x': 7000.0, 'y': 8000.0, 'z': 9000.0, 'lat': 37.5, 'lon': 289.0, 'rad': 6380.0} + self.out = None + self.loc = None def teardown(self): """Runs after every method to clean up previous testing.""" - del self.val + del self.val, self.out, self.loc @pytest.mark.parametrize("kwargs,target", [({}, @@ -199,40 +202,38 @@ def teardown(self): def test_global_to_local_cartesian(self, kwargs, target): """Test conversion from global to local cartesian coordinates""" - x, y, z = coords.global_to_local_cartesian(self.val['x'], - self.val['y'], - self.val['z'], - self.val['lat'], - self.val['lon'], - self.val['rad'], - **kwargs) - - assert np.all(abs(x - target[0]) < 1.0e-6) - assert np.all(abs(y - target[1]) < 1.0e-6) - assert np.all(abs(z - target[2]) < 1.0e-6) - if isinstance(self.val['x'], np.ndarray): - assert x.shape == self.val['x'].shape - assert y.shape == self.val['x'].shape - assert z.shape == self.val['x'].shape + self.out = coords.global_to_local_cartesian(self.val['x'], + self.val['y'], + self.val['z'], + self.val['lat'], + self.val['lon'], + self.val['rad'], + **kwargs) + + for i, self.loc in enumerate(self.out): + assert np.all(abs(self.loc - target[i]) < 1.0e-6) + if isinstance(self.loc, np.ndarray): + assert self.loc.shape == self.val['x'].shape def test_global_to_local_cartesian_and_back(self): """Tests the reversibility of the global to loc cartesian transform""" - x2, y2, z2 = coords.global_to_local_cartesian(self.val['x'], - self.val['y'], - self.val['z'], - self.val['lat'], - self.val['lon'], - self.val['rad'], - inverse=False) - x3, y3, z3 = coords.global_to_local_cartesian(x2, y2, z2, - self.val['lat'], - self.val['lon'], - self.val['rad'], - inverse=True) - assert np.all(abs(self.val['x'] - x3) < 1.0e-6) - assert np.all(abs(self.val['y'] - y3) < 1.0e-6) - assert np.all(abs(self.val['z'] - z3) < 1.0e-6) + self.out = coords.global_to_local_cartesian(self.val['x'], + self.val['y'], + self.val['z'], + self.val['lat'], + self.val['lon'], + self.val['rad'], + inverse=False) + self.out = coords.global_to_local_cartesian(self.out[0], self.out[1], + self.out[2], + self.val['lat'], + self.val['lon'], + self.val['rad'], + inverse=True) + assert np.all(abs(self.val['x'] - self.out[0]) < 1.0e-6) + assert np.all(abs(self.val['y'] - self.out[1]) < 1.0e-6) + assert np.all(abs(self.val['z'] - self.out[2]) < 1.0e-6) class TestGlobalLocalArray(TestGlobalLocal): @@ -242,10 +243,12 @@ def setup(self): arr = np.ones(shape=(10,), dtype=float) self.val = {'x': 7000.0 * arr, 'y': 8000.0 * arr, 'z': 9000.0 * arr, 'lat': 37.5 * arr, 'lon': 289.0 * arr, 'rad': 6380.0 * arr} + self.out = None + self.loc = None def teardown(self): """Runs after every method to clean up previous testing.""" - del self.val + del self.val, self.out, self.loc class TestLocalHorizontalGlobal(): @@ -255,10 +258,12 @@ def setup(self): """Runs before every method to create a clean testing setup.""" self.val = {'az': 30.0, 'el': 45.0, 'dist': 1000.0, 'lat': 45.0, 'lon': 0.0, 'alt': 400.0} + self.out = None + self.loc = None def teardown(self): """Runs after every method to clean up previous testing.""" - del self.val + del self.val, self.out, self.loc @pytest.mark.parametrize("kwargs,target", [({}, [50.4190376, -7.6940084, 7172.1548652]), @@ -269,22 +274,18 @@ def teardown(self): def test_local_horizontal_to_global_geo(self, kwargs, target): """Tests the conversion of the local horizontal to global geo""" - lat, lon, rad = \ - coords.local_horizontal_to_global_geo(self.val['az'], - self.val['el'], - self.val['dist'], - self.val['lat'], - self.val['lon'], - self.val['alt'], - **kwargs) + self.out = coords.local_horizontal_to_global_geo(self.val['az'], + self.val['el'], + self.val['dist'], + self.val['lat'], + self.val['lon'], + self.val['alt'], + **kwargs) - assert np.all(abs(lat - target[0]) < 1.0e-6) - assert np.all(abs(lon - target[1]) < 1.0e-6) - assert np.all(abs(rad - target[2]) < 1.0e-6) - if isinstance(self.val['lat'], np.ndarray): - assert lat.shape == self.val['lat'].shape - assert lon.shape == self.val['lat'].shape - assert rad.shape == self.val['lat'].shape + for i, self.loc in enumerate(self.out): + assert np.all(abs(self.loc - target[i]) < 1.0e-6) + if isinstance(self.loc, np.ndarray): + assert self.loc.shape == self.val['lat'].shape class TestLocalHorizontalGlobalArray(TestLocalHorizontalGlobal): @@ -295,7 +296,9 @@ def setup(self): arr = np.ones(shape=(10,), dtype=float) self.val = {'az': 30.0 * arr, 'el': 45.0 * arr, 'dist': 1000.0 * arr, 'lat': 45.0 * arr, 'lon': 0.0 * arr, 'alt': 400.0 * arr} + self.out = None + self.loc = None def teardown(self): """Runs after every method to clean up previous testing.""" - del self.val + del self.val, self.out, self.loc