diff --git a/awot/__init__.py b/awot/__init__.py index 8166922..95a8b1b 100755 --- a/awot/__init__.py +++ b/awot/__init__.py @@ -10,7 +10,7 @@ :toctree: generated/ """ -from __future__ import absolute_import + # import subpackages from . import io diff --git a/awot/display/rtd.py b/awot/display/rtd.py index 8de10e1..81038d3 100755 --- a/awot/display/rtd.py +++ b/awot/display/rtd.py @@ -19,17 +19,17 @@ """ # Load the needed packages -from __future__ import print_function + from mpl_toolkits.basemap import Basemap, cm import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import axes3d from matplotlib import ticker import numpy as np -import awot.graph.common as common +import awot.graph.common as gp -def polar_sweep(Var, rot, range, nlevs=30, +def polar_sweep(data, rot, ranges, nlevs=30, vmin=None, vmax=None, cmap=None, mask_outside=True, rng_rings=None, rot_angs=None, title=None, cb_flag=True, cb_orient='horizontal', cb_lab=None): @@ -38,9 +38,9 @@ def polar_sweep(Var, rot, range, nlevs=30, Parameters ---------- - Var : float array + data : float array Data values to plot. - range : float array + ranges : float array Range along ray. rot : float array Rotation angle with respect to instrument [degrees]. @@ -74,11 +74,11 @@ def polar_sweep(Var, rot, range, nlevs=30, """ # Plot the polar coordinate radar data fig, ax, p = gp.plot_polar_contour( - Var, rot, range, nlevs=nlevs, - vmin=vmin, vmax=vmax, cmap=cmap, mask_outside=True) + data, rot, ranges, nlevs=nlevs, + vmin=vmin, vmax=vmax, cmap=cmap) # Set the range and turn grid on - ax.set_rmax(1.05 * range.max()) + ax.set_rmax(1.05 * ranges.max()) ax.grid(True) # Set the title @@ -206,7 +206,7 @@ def plot_sweep_grid(Xcoord, Ycoord, Values, ax=None, title=None, return p -def sweep_to_Cart(Var, range, rot, tilt, ax=None, data_proj='fore', +def sweep_to_Cart(Var, ranges, rot, tilt, ax=None, data_proj='fore', title=None, vmin=-24., vmax=64., cmap=None, mask_outside=True, grid_on=True, xlims=None, ylims=None, xlab=None, ylab=None, cb_flag=True, cb_orient='horizontal', @@ -222,7 +222,7 @@ def sweep_to_Cart(Var, range, rot, tilt, ax=None, data_proj='fore', ---------- Var : float array Data values to plot. - range : float array + ranges : float array Range along ray. rot : float array Rotation angle with respect to instrument [degrees]. @@ -280,18 +280,18 @@ def sweep_to_Cart(Var, range, rot, tilt, ax=None, data_proj='fore', "either 'fore' or 'aft', assuming 'fore'") Fact = 1. - range = np.array(range) # Make sure that the data is numpy array + ranges = np.array(ranges) # Make sure that the data is numpy array values = np.array(Var) # Make sure that the data is numpy array - values = values.reshape(len(rot), len(range)) # Resize it to work + values = values.reshape(len(rot), len(ranges)) # Resize it to work # mask the data where outside the limits if mask_outside: Var = np.ma.masked_outside(Var, vmin, vmax) # Create 2D variables to plot contour against - r, Rot = np.meshgrid(range, np.radians(rot)) - r2, Tilt = np.meshgrid(range, np.radians(tilt)) + r, Rot = np.meshgrid(ranges, np.radians(rot)) + r2, Tilt = np.meshgrid(ranges, np.radians(tilt)) # Convert r, Rot to Cartesian (x,z) X = Fact * r * np.sin(Rot) * np.sin(Tilt) @@ -299,7 +299,7 @@ def sweep_to_Cart(Var, range, rot, tilt, ax=None, data_proj='fore', # Set axis limits if passed if xlims is None: - xlims = (-1.05 * range.max(), 1.05 * range.max()) + xlims = (-1.05 * ranges.max(), 1.05 * ranges.max()) else: xlims = xlims if ylims is None: @@ -316,7 +316,7 @@ def sweep_to_Cart(Var, range, rot, tilt, ax=None, data_proj='fore', return p -def sweep_aircraft_relative(Var, range, tilt, rot, ax=None, title=None, +def sweep_aircraft_relative(Var, ranges, tilt, rot, ax=None, title=None, vmin=-24., vmax=64., cmap=None, mask_outside=True, grid_on=True, xlims=None, ylims=None, xlab=None, ylab=None, cb_flag=True, cb_orient='horizontal', @@ -332,7 +332,7 @@ def sweep_aircraft_relative(Var, range, tilt, rot, ax=None, title=None, ---------- Var : float array Data values to plot. - range : float array + ranges : float array Range along ray. tilt : float array Radar ray Tilt angle with respect to platform [degrees]. @@ -377,18 +377,18 @@ def sweep_aircraft_relative(Var, range, tilt, rot, ax=None, title=None, This mapping does NOT take into account corrections for roll, pitch, or drift of the aircraft. """ - range = np.array(range) # Make sure that the data is numpy array + ranges = np.array(ranges) # Make sure that the data is numpy array values = np.array(Var) # Make sure that the data is numpy array - values = values.reshape(len(rot), len(range)) # Resize it to work + values = values.reshape(len(rot), len(ranges)) # Resize it to work # mask the data where outside the limits if mask_outside: Var = np.ma.masked_outside(Var, vmin, vmax) # Create 2D variables to plot contour against - r, Rot = np.meshgrid(range, np.radians(rot)) - r2, Tilt = np.meshgrid(range, np.radians(tilt)) + r, Rot = np.meshgrid(ranges, np.radians(rot)) + r2, Tilt = np.meshgrid(ranges, np.radians(tilt)) # Convert polar (r,Rot,Tilt) to Cartesian (x,y,z) X = r * np.cos(Tilt) * np.sin(Rot) @@ -397,7 +397,7 @@ def sweep_aircraft_relative(Var, range, tilt, rot, ax=None, title=None, # Set axis limits if passed if xlims is None: - xlims = (-1.05 * range.max(), 1.05 * range.max()) + xlims = (-1.05 * ranges.max(), 1.05 * ranges.max()) else: xlims = xlims if ylims is None: @@ -415,7 +415,7 @@ def sweep_aircraft_relative(Var, range, tilt, rot, ax=None, title=None, def sweep_track_relative( - Var, range, tilt, rot, roll, drift, pitch, ax=None, title=None, + Var, ranges, tilt, rot, roll, drift, pitch, ax=None, title=None, vmin=-24., vmax=64., cmap=None, mask_outside=True, grid_on=True, xlims=None, ylims=None, xlab=None, ylab=None, cb_flag=True, cb_orient='horizontal', cb_lab=None): @@ -428,7 +428,7 @@ def sweep_track_relative( ---------- Var : float array Data values to plot. - range : float array + ranges : float array Range along ray. tilt : float array Radar ray Tilt angle with respect to platform [degrees]. @@ -480,21 +480,21 @@ def sweep_track_relative( This mapping corrects for roll, pitch, and drift of the aircraft. This is considered a leveled, heading-relative coordinate system. """ - range = np.array(range) # Make sure that the data is numpy array + ranges = np.array(ranges) # Make sure that the data is numpy array values = np.array(Var) # Make sure that the data is numpy array - values = values.reshape(len(rot), len(range)) # Resize it to work + values = values.reshape(len(rot), len(ranges)) # Resize it to work # mask the data where outside the limits if mask_outside: Var = np.ma.masked_outside(Var, vmin, vmax) # Create 2D variables to plot contour against - r, Rot = np.meshgrid(range, np.radians(rot)) - r2, Tilt = np.meshgrid(range, np.radians(tilt)) - r3, Roll = np.meshgrid(range, np.radians(roll)) - r4, Pitch = np.meshgrid(range, np.radians(pitch)) - r5, Drift = np.meshgrid(range, np.radians(drift)) + r, Rot = np.meshgrid(ranges, np.radians(rot)) + r2, Tilt = np.meshgrid(ranges, np.radians(tilt)) + r3, Roll = np.meshgrid(ranges, np.radians(roll)) + r4, Pitch = np.meshgrid(ranges, np.radians(pitch)) + r5, Drift = np.meshgrid(ranges, np.radians(drift)) del r2, r3, r4, r5 # Convert r, Rot to Cartesian (x,y) @@ -509,7 +509,7 @@ def sweep_track_relative( # Set axis limits if passed if xlims is None: - xlims = (-1.05 * range.max(), 1.05 * range.max()) + xlims = (-1.05 * ranges.max(), 1.05 * ranges.max()) else: xlims = xlims if ylims is None: @@ -526,7 +526,7 @@ def sweep_track_relative( return p -def sweep_earth_relative(Var, range, tilt, rot, roll, heading, pitch, +def sweep_earth_relative(Var, ranges, tilt, rot, roll, heading, pitch, ax=None, title=None, vmin=-24., vmax=64., cmap=None, mask_outside=True, grid_on=True, xlims=None, ylims=None, xlab=None, ylab=None, @@ -542,7 +542,7 @@ def sweep_earth_relative(Var, range, tilt, rot, roll, heading, pitch, ---------- Var : float array Data values to plot. - range : float array + ranges : float array Range along ray. tilt : float array Radar ray Tilt angle with respect to platform [degrees]. @@ -595,21 +595,21 @@ def sweep_earth_relative(Var, range, tilt, rot, roll, heading, pitch, This is considered a leveled, heading-relative coordinate system. """ - range = np.array(range) # Make sure that the data is numpy array + ranges = np.array(ranges) # Make sure that the data is numpy array values = np.array(Var) # Make sure that the data is numpy array - values = values.reshape(len(rot), len(range)) # Resize it to work + values = values.reshape(len(rot), len(ranges)) # Resize it to work # mask the data where outside the limits if mask_outside: Var = np.ma.masked_outside(Var, vmin, vmax) # Create 2D variables to plot contour against - r, Rot = np.meshgrid(range, np.radians(rot)) - r2, Tilt = np.meshgrid(range, np.radians(tilt)) - r3, Roll = np.meshgrid(range, np.radians(roll)) - r4, Pitch = np.meshgrid(range, np.radians(pitch)) - r5, Heading = np.meshgrid(range, np.radians(heading)) + r, Rot = np.meshgrid(ranges, np.radians(rot)) + r2, Tilt = np.meshgrid(ranges, np.radians(tilt)) + r3, Roll = np.meshgrid(ranges, np.radians(roll)) + r4, Pitch = np.meshgrid(ranges, np.radians(pitch)) + r5, Heading = np.meshgrid(ranges, np.radians(heading)) del r2, r3, r4, r5 # Convert r, Rot to Cartesian (x,y) @@ -624,7 +624,7 @@ def sweep_earth_relative(Var, range, tilt, rot, roll, heading, pitch, # Set axis limits if passed if xlims is None: - xlims = (-1.05 * range.max(), 1.05 * range.max()) + xlims = (-1.05 * ranges.max(), 1.05 * ranges.max()) else: xlims = xlims if ylims is None: diff --git a/awot/graph/common.py b/awot/graph/common.py index c2f1e56..bf30e2c 100755 --- a/awot/graph/common.py +++ b/awot/graph/common.py @@ -6,7 +6,7 @@ """ -from __future__ import print_function + import numpy as np from mpl_toolkits.basemap import Basemap import matplotlib.pyplot as plt @@ -909,19 +909,19 @@ def create_polar_fig_ax(nrows=1, ncols=1, figsize=(5, 5)): ################## -def save_figure(name='awot_plot', type="png", dpi=300): +def save_figure(name='awot_plot', fig_type="png", dpi=300): '''Save the current plot. Parameters ------------ name : str Figure name. - type : str + fig_type : str Figure format, default to .png file type. dpi : int Resolution in dots per inch. ''' - plt.savefig(name+'.'+type, format=figType, dpi=dpi) + plt.savefig(f"{name}.{fig_type}", format=fig_type, dpi=dpi) ################# # Get methods # @@ -954,7 +954,7 @@ def get_masked_data(data, mask_procedure, mask_tuple): vmax = mask_tuple[2] data = np.ma.masked_outside(data, vmin, vmax) else: - print("Check the mask_procedure operation string!") + print("--> Check the mask_procedure operation string!") return data @@ -974,7 +974,7 @@ def _get_start_datetime(time, start_time): startInt[4], startInt[5], startInt[6]) except: import warnings - warnings.warn(common.DATE_STRING_FORMAT) + warnings.warn(DATE_STRING_FORMAT) return # Check to see if date time specified is beyond start @@ -1000,7 +1000,7 @@ def _get_end_datetime(time, end_time): endInt[4], endInt[5], endInt[6]) except: import warnings - warnings.warn(common.DATE_STRING_FORMAT) + warnings.warn(DATE_STRING_FORMAT) return # Check to see if date time specified is beyond start @@ -1072,9 +1072,8 @@ def _check_basemap(instance, strong=True): if instance.basemap is None: if strong: raise ValueError('Please supply basemap instance') - return None else: - print("WARNING: A basemap instance may be required for some plots") + print("--> WARNING: A basemap instance may be required for some plots") return False @@ -1089,5 +1088,5 @@ def _check_field(instance, field): The key name of the field to check """ if instance[field] is None: - print("This field has no value!!") + print(f"--> {field} field has no value!!") return diff --git a/awot/graph/flight_level.py b/awot/graph/flight_level.py index 34dd711..e4e6948 100755 --- a/awot/graph/flight_level.py +++ b/awot/graph/flight_level.py @@ -11,7 +11,7 @@ Improve time series variable plotting? """ -from __future__ import print_function + import numpy as np import os import matplotlib.pyplot as plt @@ -507,7 +507,7 @@ def plot_radar_cross_section( # Return masked or unmasked variable var, data = self._get_radar_variable_dict_data(radar, field) if mask_procedure is not None: - data = get_masked_data(data, mask_procedure, mask_tuple) + data = common.get_masked_data(data, mask_procedure, mask_tuple) # Create contour level array clevels = np.linspace(cminmax[0], cminmax[1], clevs) diff --git a/awot/graph/flight_level_3d.py b/awot/graph/flight_level_3d.py new file mode 100755 index 0000000..beae05c --- /dev/null +++ b/awot/graph/flight_level_3d.py @@ -0,0 +1,1220 @@ +""" +awot.graph.flight_level_3d +========================== + +A group of scripts to create 3D plots of flight level data. + +TODO: +Fix the time_stamp module to work with savefig. Okay on screen output, +but for some reason it bombs when trying to save. + +Improve time series variable plotting? +""" + + +import numpy as np +import os +import matplotlib.pyplot as plt +#from matplotlib.lines import Line2D +from mpl_toolkits.mplot3d.art3d import Line3DCollection, Line3D +from matplotlib.colors import Normalize +from matplotlib.dates import DateFormatter, date2num +from matplotlib.dates import ( + SecondLocator, MinuteLocator, HourLocator, DayLocator) +from matplotlib import ticker +from datetime import datetime +import scipy.ndimage as scim + +from . import common +from .. import util + + +class FlightLevel3D(object): + """Class for flight level plots.""" + + def __init__(self, flightdata, basemap=None, + lon_name=None, lat_name=None, + alt_name=None, time_name=None, + uwind_name=None, vwind_name=None, + ): + ''' + Intitialize the class to create plots. + + Parameters + ---------- + flightdata : dict + AWOT flight data dictionary instance. + basemap : basemap instance + Basemap instance to use for plotting. + lon_name : str + Variable name to use for longitude array. + None uses AWOT default mapping. + lat_name : str + Variable name to use for latitude array. + None uses AWOT default mapping. + alt_name : str + Variable name to use for altitude array. + None uses AWOT default mapping. + time_name : str + Variable name to use for time array. + None uses AWOT default mapping. + uwind_name : str + Variable name to use for zonal wind array. + None uses AWOT default mapping. + vwind_name : str + Variable name to use for meridional wind array. + None uses AWOT default mapping. + ''' + if lon_name is None: + lonkey = 'longitude' + else: + lonkey = lon_name + if lat_name is None: + latkey = 'latitude' + else: + latkey = lat_name + if alt_name is None: + altkey = 'altitude' + else: + altkey = alt_name + if time_name is None: + timekey = 'time' + else: + timekey = time_name + if uwind_name is None: + ukey = 'Uwind' + else: + ukey = uwind_name + if vwind_name is None: + vkey = 'Vwind' + else: + vkey = vwind_name + self.longitude = flightdata[lonkey] + self.latitude = flightdata[latkey] + self.altitude = flightdata[altkey] + self.flight_number = flightdata['flight_number'] + self.project = flightdata['project'] + self.platform = flightdata['platform'] + self.Uwind = flightdata[ukey] + self.Vwind = flightdata[vkey] + self.time = flightdata[timekey] + self.flight_data = flightdata + self.basemap = basemap + common._check_basemap(self, strong=False) + + if self.basemap is not None: + # Calculate x,y map position coordinates + self.x, self.y = self.basemap( + self.longitude['data'][:], self.latitude['data'][:]) + +##################### +# Basemap methods # +##################### + + + def create_basemap(self, corners=None, proj=None, resolution='l', + area_thresh=None, lon_0=None, lat_0=None, + xpad=None, ypad=None, + meridians=True, parallels=True, + lon_spacing=2., lat_spacing=2., + coastlines=True, countries=True, states=False, + counties=False, rivers=False, etopo=False): + ''' Create a basemap instance. ''' + bm, ax = common.create_basemap_3d(corners=corners, proj=proj, + resolution=resolution, + area_thresh=area_thresh, + lon_0=lon_0, lat_0=lat_0, + meridians=meridians, + parallels=parallels, + lon_spacing=lon_spacing, + lat_spacing=lat_spacing, + xpad=xpad, ypad=ypad, + coastlines=coastlines, + countries=countries, states=states, + counties=counties, rivers=rivers, + etopo=etopo) + self.basemap = bm + self.x, self.y = self.basemap( + self.longitude['data'][:], self.latitude['data'][:]) + return + +################# +# Track plots # +################# + + def plot_trackmap_3d(self, track_cmap=None, + track_color='k', lw=1.5, ls=None, alpha=1., + start_time=None, end_time=None, + min_altitude=None, max_altitude=None, + addlegend=False, legLoc='lower right', legLab=None, + addtitle=False, title=None, ax=None, fig=None, +# save_kmz=False, kmz_filepath=None, kmz_filename=None, + **kwargs): + """ + Create a plot of the aircraft flight track, with optional enhancements. + Note that the legend only works with single color track at this time. + + Parameters + ---------- + track_cmap : Matplotlib colormap + If color_by_altitude True, it will use this colormap. + track_color : str + If color_by_altitude False, this will be the track color. + lw : float or int + Line width to use for track. + ls : str + Line style string. Default to solid line. + alpha : float + Alpha value for transparency (0 = transparent, 1. = solid). + start_time : str + UTC time to use as start time for subsetting in datetime format. + (e.g. 2014-08-20 12:30:00) + end_time : str + UTC time to use as an end time for subsetting in datetime format. + (e.g. 2014-08-20 16:30:00) + min_altitude : float + Minimum altitude to use in mapping color. + max_altitude : float + Maximum altitude to use in mapping color. + addlegend : bool + Defaults to No legend drawn. + legLoc : str + See matplotlib legend object documentation. + addtitle : bool + Defaults to no title for plot. + title : str + See matplotlib axis object documentation. + ax : Matplotlib axis instance + Axis to plot on. + None will use the current axis. + fig : Matplotlib figure instance + Figure whih to add the colorbar. + None will use the current figure. + + **kwargs will pass specific arguments to subprograms, + see Basemap and Matplotlib. + """ + # parse parameters + ax, fig = common._parse_ax_fig(ax, fig) + + # Check inputs + if legLab is None: + legLab = self.flight_number + + # Get start and end times (this deals with subsets) + dt_start = self._get_datetime(start_time, get_start=True) + dt_end = self._get_datetime(end_time, get_end=True) + + # Subset the data (will use min/max if None given) + xsub, ysub = self._get_x_y_time_subset( + start_time, end_time, return_time=False) + lonsub, latsub = self._get_lon_lat_time_subset(start_time, end_time) + timeSub, altsub = self._get_time_var_time_subset( + 'altitude', start_time, end_time) + + # Clean up the masked data for plotting + all_mask = latsub.mask + lonsub.mask + altsub.mask + lonmask = np.ma.array(lonsub, mask=all_mask).compressed() + latmask = np.ma.array(latsub, mask=all_mask).compressed() + altmask = np.ma.array(altsub, mask=all_mask).compressed() + + xmask, ymask = self.basemap(lonmask, latmask) + + # Set a couple of keyword defaults for KMZ option + show_legend, legend_label = False, ' ' + + # Plot the track either coloring by altitude or as single color + if track_cmap is None: + track_cmap = plt.get_cmap() + if ls is None: + ls = '-' + p = ax.plot(xmask, ymask, altmask, color=track_color, + linewidth=lw, linestyle=ls, alpha=alpha) + + # Label the axes + ax.set_xlabel('Longitude') + ax.set_ylabel('Latitude') + ax.set_zlabel('Altitude (m)') + if min_altitude is None: + min_altitude = np.min(altmask) + if max_altitude is None: + max_altitude = np.max(altmask) + ax.set_xlim3d(np.min(xmask), np.max(xmask)) + ax.set_ylim3d(np.min(ymask), np.max(ymask)) + ax.set_zlim3d(min_altitude, max_altitude) + + # Save the KMZ file if requested +# if save_kmz: +# lonrange = (np.min(lonmask), np.max(lonmask)) +# latrange = (np.min(latmask), np.max(latmask)) +# times = [dt_start, dt_end] +# if kmz_filepath is None: +# kmz_filepath = os.getcwd() +# if kmz_filename is None: +# kmz_filename = ('awot_' + self.platform + '_' + +# self.flight_number + '_altitude' + '.kmz') +# util.write_kmz.write_kmz(fig, ax, p, lonrange, latrange, times, +# file_path=kmz_filepath, +# file_name=kmz_filename, +# show_legend=show_legend, +# legend_label=legend_label) + if addlegend: + self.basemap.ax.legend(loc=legLoc, fontsize=11, frameon=True, + handlelength=2, handletextpad=1, + labelspacing=.2) + + if addtitle: + if title is None: + title = self.project + ' ' + self.platform + self.basemap.ax.set_title(title) + + def plot_variable3d(self, field, track_cmap=None, + track_lw=1.5, alpha=1., + start_time=None, end_time=None, + min_altitude=None, max_altitude=None, + min_value=None, max_value=None, + add_cb=True, cborientation='vertical', + cbshrink=0.5, cbaspect=20, + cblabel=None, addlegend=False, legLoc='lower right', + legLab=None, addtitle=False, title=None, + ax=None, fig=None, +# save_kmz=False, kmz_filepath=None, kmz_filename=None, + **kwargs): + """ + Create a plot of the aircraft flight track, with optional enhancements. + Note that the legend only works with single color track at this time. + + Parameters + ---------- + field : float + Variable passed to plot, defaults to temperature. + track_cmap : Matplotlib colormap instance + Colormap to use for coding values. + track_lw : float or int + Line width to use for track. + alpha : float + Alpha value for transparency (0 = transparent, 1. = solid). + start_time : str + UTC time to use as start time for subsetting in datetime format. + (e.g. 2014-08-20 12:30:00) + end_time : str + UTC time to use as an end time for subsetting in datetime format. + (e.g. 2014-08-20 16:30:00) + min_altitude : float + Minimum altitude to use in mapping color. + max_altitude : float + Maximum altitude to use in mapping color. + min_value : float + Minimum value to use in mapping color. + max_value : float + Maximum value to use in mapping color. + add_cb : bool + True plots a colorbar, Fasle does not. + cborientation : str + Orientation of colorbar, options: 'vertical' or 'horizontal'. + cbshrink : float + Fractional size to shrink colorbar. + cbaspect : float + Ratio between height and width of colorbar. + addlegend : bool + Defaults to No legend drawn. + legLoc : str + See matplotlib legend object documentation. + addtitle : bool + Defaults to no title for plot. + title : str + See matplotlib axis object documentation. + ax : Matplotlib axis instance + Axis to plot. None will use the current axis. + fig : Matplotlib figure instance + Figure which to add the colorbar. + None will use the current figure. + + **kwargs will pass specific arguments to subprograms, + see Basemap and Matplotlib. + """ + + # Check to see if field exists + common._check_field(self.flight_data, field) + + # parse parameters + ax, fig = common._parse_ax_fig(ax, fig) + + # Get start and end times (this deals with subsets) + dt_start = self._get_datetime(start_time, get_start=True) + dt_end = self._get_datetime(end_time, get_end=True) + + # Subset the data (will use min/max if None given) + xsub, ysub = self._get_x_y_time_subset( + start_time, end_time, return_time=False) + lonsub, latsub = self._get_lon_lat_time_subset(start_time, end_time) + altsub = self._get_var_time_subset( + 'altitude', start_time, end_time) + varsub = self._get_var_time_subset( + field, start_time, end_time) + + # Clean up the masked data for plotting + all_mask = latsub.mask + lonsub.mask + varsub.mask + lonmask = np.ma.array(lonsub, mask=all_mask).compressed() + latmask = np.ma.array(latsub, mask=all_mask).compressed() + altmask = np.ma.array(altsub, mask=all_mask).compressed() + varmask = np.ma.array(varsub, mask=all_mask).compressed() + + xmask, ymask = self.basemap(lonmask, latmask) + + # Check inputs + if min_value is None: + min_value = varmask.min() + + if max_value is None: + max_value = varmask.max() + + if legLab is None: + legLab = self.flight_number + + # Set a couple of keyword defaults for KMZ option + show_legend, legend_label = False, ' ' + + # Plot the track + if track_cmap is None: + track_cmap = plt.get_cmap() + p = self._colorline(xmask, ymask, altmask, varmask, cmap=track_cmap, + vmin=min_value, vmax=max_value, + linewidth=track_lw, alpha=alpha) + ax.add_collection3d(p, zdir='z') + + # Label the axes + ax.set_xlabel('Longitude') + ax.set_ylabel('Latitude') + ax.set_zlabel('Altitude (m)') + if min_altitude is None: + min_altitude = np.min(altmask) + if max_altitude is None: + max_altitude = np.max(altmask) + ax.set_zlim(min_altitude, max_altitude) + + if add_cb: + cb = fig.colorbar(p, orientation=cborientation, + shrink=cbshrink, aspect=cbaspect) + cb.set_label(cblabel) + show_legend = True + legend_label = cblabel + + # Save the KMZ file if requested +# if save_kmz: +# lonrange = (np.min(lonmask), np.max(lonmask)) +# latrange = (np.min(latmask), np.max(latmask)) +# times = [dt_start, dt_end] +# if kmz_filepath is None: +# kmz_filepath = os.getcwd() +# if kmz_filename is None: +# kmz_filename = ('awot_' + self.platform + '_' + +# self.flight_number + '_' + cblabel + +# '_' + '.kmz') +# util.write_kmz.write_kmz(fig, ax, p, lonrange, latrange, times, +# file_path=kmz_filepath, +# file_name=kmz_filename, +# show_legend=show_legend, +# legend_label=legend_label) + + if addlegend: + self.basemap.ax.legend( + loc=legLoc, fontsize=11, frameon=True, + handlelength=2, handletextpad=1, labelspacing=.2) + + if addtitle: + if title is None: + title = self.project + ' ' + self.platform + ax.set_title(title) + + def plot_radar_cross_section( + self, radar, field, mask_procedure=None, mask_tuple=None, + start_time=None, end_time=None, title=" ", title_size=20, + cminmax=(0., 60.), clevs=25, vmin=15., vmax=60., + cmap='gist_ncar', clabel='dBZ', + color_bar=True, cb_orient='vertical', cb_pad=.05, cb_tick_int=2, + cb_fontsize=None, cb_ticklabel_size=None, + x_axis_array='distance', date_format='%H:%M', tz=None, + date_minor_string='minute', plot_km=False, + ax=None, fig=None): + ''' + Plot a cross-section along a flight path segment. + + Parameters + ---------- + radar : AWOT radar object + RadarHorizontalPlot object. + field : str + 3-D variable inf RadarHorizontalPlot object + (e.g. Reflectivity ['dBZ']) to use in plot. + start_pt, end_pt : tuple + (lat, lon) Tuple of start, end points for cross-section. + xs_length : int + Number of + start_time : string + UTC time to use as start time for subsetting in datetime format. + (e.g. 2014-08-20 12:30:00) + end_time : string + UTC time to use as an end time for subsetting in datetime format. + (e.g. 2014-08-20 16:30:00) + mask_procedure : str + String indicating how to apply mask via numpy, possibilities are: + 'less', 'less_equal', 'greater', 'greater_equal', 'equal', + 'inside', 'outside'. + mask_tuple : (str, float[, float]) + Tuple containing the field name and value(s) below which to mask + field prior to plotting. + cminmax : tuple + (min,max) values for controur levels. + clevs : integer + Number of contour levels. + vmin : float + Minimum contour value to display. + vmax : float + Maximum contour value to display. + cmap : string + Matplotlib color map to use. + clabel : string + Label for colorbar (e.g. units 'dBZ'). + title : string + Plot title. + title_size : int + Font size of title to display. + color_bar : boolean + True to add colorbar, False does not. + cb_fontsize : int + Font size of the colorbar label. + cb_ticklabel_size : int + Font size of colorbar tick labels. + cb_pad : str + Pad to move colorbar, in the form "5%", pos is to right + for righthand location. + cb_orient : str + Colorbar orientation, either 'vertical' or 'horizontal'. + cb_tick_int : int + Interval to use for colorbar tick labels, + higher number "thins" labels. + x_axis_array: str + X-axis array to plot against either 'distance' [default] or 'time'. + date_format : str + Format of the time string for x-axis labels. + tz : str + Time zone info to use when creating axis labels (see datetime). + date_minor_string : str + Sting to set minor ticks of date axis, + 'second','minute','hour','day' supported. + plot_km : boolean + True to convert meters to kilometers for cappi_height. False + retains meters information. + ax : Matplotlib axis instance + Axis to plot on. None will use the current axis. + fig : Matplotlib figure instance + Figure which to add the plot. None will use the current figure. + ''' + # parse parameters + ax, fig = common._parse_ax_fig(ax, fig) + + # Get start and end times (this deals with subsets) + dt_start = self._get_datetime(start_time, get_start=True) + dt_end = self._get_datetime(end_time, get_end=True) + + # Subset the data (will use min/max if None given) + xsub, ysub = self._get_x_y_time_subset( + start_time, end_time, return_time=False) + lonsub, latsub = self._get_lon_lat_time_subset(start_time, end_time) + timeSub = self._get_time_subset(start_time, end_time) + + # Return masked or unmasked variable + Var, Data = self._get_radar_variable_dict_data(radar, field) + if mask_procedure is not None: + Data = common.get_masked_data(Data, mask_procedure, mask_tuple) + + # Create contour level array + clevels = np.linspace(cminmax[0], cminmax[1], clevs) + + # Create an array to hold the interpolated cross-section + xs_data = np.empty([len(lonsub), len(radar['height']['data'][:])]) + + # Create arrays for cross-section lon-lat index points + xsY = np.empty(len(lonsub)) + xsX = np.empty(len(lonsub)) + Xdist = np.empty(len(lonsub)) + Ydist = np.empty(len(lonsub)) + xsDist = np.empty(len(lonsub)) + + for ii in range(len(lonsub)): + xsX[ii] = self._get_lon_index(lonsub[ii], radar) + xsY[ii] = self._get_lat_index(latsub[ii], radar) + + # Calculate the distance array along the cross-section + # Need to keep a running tally moving through track array + if ii == 0: + Xdist[ii] = 0. + Ydist[ii] = 0. + xsDist[ii] = 0. + else: + Xdist[ii] = np.absolute( + (np.pi * common.EARTH_RADIUS / 180.) * + (lonsub[ii] - lonsub[ii - 1])) + Ydist[ii] = np.absolute( + (np.pi * common.EARTH_RADIUS / 180.) * + (latsub[ii] - latsub[ii - 1])) + xsDist[ii] = (np.sqrt(Xdist[ii]**2 + Ydist[ii]**2) + ) + xsDist[ii - 1] + + # Loop through each level to create cross-section and stack them + for nlev in range(len(radar['height']['data'][:])): + # Extract the values along the line, using cubic interpolation + xs_data[:, nlev] = scim.map_coordinates( + Data[nlev, :, :], np.vstack((xsY, xsX)), prefilter=False) + # , mode='nearest') + + # Calculate the distance array along the cross-section + if x_axis_array == 'distance': + Xax = xsDist + elif x_axis_array == 'time': + Xax = date2num(timeSub) + + # Convert Height, distance arrays to 2D + if plot_km: + Ht2D, Xax2D = np.meshgrid(radar['height']['data'][:]/1000., Xax) + ylabel = 'Altitude (km)' + else: + Ht2D, Xax2D = np.meshgrid(radar['height']['data'][:], Xax) + ylabel = 'Altitude (m)' + + p = ax.pcolormesh(Xax2D, Ht2D, np.ma.masked_less_equal(xs_data, -800.), + vmin=vmin, vmax=vmax, cmap=cmap) + + ax.set_ylabel(ylabel) + if x_axis_array == 'distance': + ax.set_xlabel('Distance along track (km)') + elif x_axis_array == 'time': + ax.xaxis_date() + # Set the date format + date_Fmt = DateFormatter(date_format, tz=tz) + # Set the x-axis date format and ticks + ax.xaxis.set_major_formatter(date_Fmt) + if date_minor_string == 'second': + ax.xaxis.set_minor_locator(SecondLocator()) + elif date_minor_string == 'minute': + ax.xaxis.set_minor_locator(MinuteLocator()) + elif date_minor_string == 'hour': + ax.xaxis.set_minor_locator(HourLocator()) + elif date_minor_string == 'day': + ax.xaxis.set_minor_locator(DayLocator()) + + # Add title + ax.set_title(title, fontsize=title_size) + + # Add Colorbar + if color_bar: + cbStr = Var['long_name'] + ' (' + Var['units'] + ')' + cb = common.add_colorbar(ax, p, orientation=cb_orient, pad=cb_pad, + label=cbStr, fontsize=cb_fontsize, + ticklabel_size=cb_ticklabel_size, + clevs=clevs, tick_interval=cb_tick_int) + + # Add title + ax.set_title(title, fontsize=title_size) + +############################ +# Basemap add-on methods # +############################ + + def draw_boundary(self, **kwargs): + """ + Draw a boundary around the map. + + Parameters + ---------- + See basemap documentation. + """ + self.basemap.drawmapboundary(**kwargs) + + def draw_scale(self, location='lower left', lon=None, lat=None, + lon0=None, lat0=None, length=None, **kwargs): + """ + Draw a reference scale on the map. + + Parameters + ---------- + location : str + Where to put the scale, default is 'lower left'. + Options are 'lower left', 'upper left', 'lower right', + 'upper right', 'upper middle', 'lower middle'. + + All other parameters, See basemap documentation. + + Note that the location parameter can be overridden by setting + the lon and lat variables explicitly. + """ + # Check on where to put the label, defaults to the else statement + # that calculates 'lower left' + if (location.lower() == 'upper left') or \ + (location.lower() == 'upper_left'): + xScaleOffset = ( + self.basemap.urcrnrlon - self.basemap.llcrnrlon) * 0.10 + yScaleOffset = ( + self.basemap.urcrnrlat - self.basemap.llcrnrlat) * 0.90 + elif (location.lower() == 'upper middle') or \ + (location.lower() == 'upper_middle'): + xScaleOffset = ( + self.basemap.urcrnrlon - self.basemap.llcrnrlon) * 0.50 + yScaleOffset = ( + self.basemap.urcrnrlat - self.basemap.llcrnrlat) * 0.90 + elif (location.lower() == 'upper right') or \ + (location.lower() == 'upper_right'): + xScaleOffset = ( + self.basemap.urcrnrlon - self.basemap.llcrnrlon) * 0.90 + yScaleOffset = ( + self.basemap.urcrnrlat - self.basemap.llcrnrlat) * 0.90 + elif (location.lower() == 'lower right') or \ + (location.lower() == 'lower_right'): + xScaleOffset = ( + self.basemap.urcrnrlon - self.basemap.llcrnrlon) * 0.90 + yScaleOffset = ( + self.basemap.urcrnrlat - self.basemap.llcrnrlat) * 0.10 + elif (location.lower() == 'lower middle') or \ + (location.lower() == 'lower_middle'): + xScaleOffset = ( + self.basemap.urcrnrlon - self.basemap.llcrnrlon) * 0.50 + yScaleOffset = ( + self.basemap.urcrnrlat - self.basemap.llcrnrlat) * 0.10 + else: + xScaleOffset = ( + self.basemap.urcrnrlon - self.basemap.llcrnrlon) * 0.10 + yScaleOffset = ( + self.basemap.urcrnrlat - self.basemap.llcrnrlat) * 0.10 + + if lon is None: + lon = self.basemap.llcrnrlon + xScaleOffset + if lat is None: + lat = self.basemap.llcrnrlat + yScaleOffset + if lon0 is None: + lon0 = (self.basemap.llcrnrlon + self.basemap.urcrnrlon) / 2. + if lat0 is None: + lat0 = (self.basemap.llcrnrlat + self.basemap.urcrnrlat) / 2. + if length is None: + length = 100 + + self.basemap.drawmapscale(lon=lon, lat=lat, lon0=lon0, lat0=lat0, + length=length, barstyle='fancy', + labelstyle='simple', **kwargs) + + def draw_barbs(self, barbspacing=10, barbcolor='k', flagcolor='k', + blength=8, lw=0.5, start_time=None, end_time=None, + **kwargs): + """ + Draw a reference scale on the map. + + Parameters + ---------- + barbspacing : int + Plot every nth via the barbspacing provided. + See basemap documentation. + """ + # Get start and end times (this deals with subsets) + dt_start = self._get_datetime(start_time, get_start=True) + dt_end = self._get_datetime(end_time, get_end=True) + + # Subset the data + X = self.x[(self.time['data'][:] >= dt_start) & + (self.time['data'][:] <= dt_end)] + Y = self.y[(self.time['data'][:] >= dt_start) & + (self.time['data'][:] <= dt_end)] + Uwnd = self.Uwind['data'][(self.time['data'][:] >= dt_start) & + (self.time['data'][:] <= dt_end)] + Vwnd = self.Vwind['data'][(self.time['data'][:] >= dt_start) & + (self.time['data'][:] <= dt_end)] + + # Only plot every nth barb from the barbspacing parameter + self.basemap.barbs(X[::barbspacing], Y[::barbspacing], + Uwnd[::barbspacing], Vwnd[::barbspacing], + barbcolor=barbcolor, flagcolor=flagcolor, + linewidth=lw, **kwargs) + + def plot_point(self, lon, lat, symbol='ro', label_text=None, + label_offset=(None, None), text_size=None, **kwargs): + """ + Plot a point on the current map. + + Additional arguments are passed to basemap.plot. + + Parameters + ---------- + lon : float + Longitude of point to plot. + lat : float + Latitude of point to plot. + symbol : str + Matplotlib compatible string which specified the symbol of the + point. + label_text : str + Optional text to label symbol with. If None no label will be added. + label_offset : [float, float] + Offset in lon, lat degrees for the bottom left corner of the label + text relative to the point. A value of None will use 0.01 default. + """ + lon_offset, lat_offset = label_offset + if lon_offset is None: + lon_offset = 0.01 + if lat_offset is None: + lat_offset = 0.01 + if text_size is None: + text_size = 12 + + # Plot the symbol + self.basemap.plot(lon, lat, symbol, latlon=True, **kwargs) + # Attach the text + if label_text is not None: + # basemap does not have a text method so we must determine + # the x and y points and plot them on the basemap's axis. + x_text, y_text = self.basemap(lon + lon_offset, + lat + lat_offset) + self.basemap.ax.text(x_text, y_text, label_text, size=text_size) + + def plot_line_geo(self, line_lons, line_lats, line_style='r-', **kwargs): + """ + Plot a line segments on the current map given values in lat and lon. + + Additional arguments are passed to basemap.plot. + + Parameters + ---------- + line_lons : array + Longitude of line segment to plot. + line_lats : array + Latitude of line segment to plot. + line_style : str + Matplotlib compatible string which specifies the line style. + """ + X, Y = self.basemap(line_lons, line_lats) + self.basemap.plot(X, Y, line_style, **kwargs) + + def time_stamps(self, labelspacing=1800, symbol='k*', + size=12, color='k', label_offset=(None, None), + start_time=None, end_time=None, ax=None, **kwargs): + """ + Add time stamps along track according to labelspacing. + + Parameters + ---------- + labelspacing : int + Plot every nth via the labelpacing provided. + The default assumes seconds and labels every 30 minutes. + symbol : str + Color-symbol combo per matplotlib.pyplot.plot + size : int + Font size used. + color : str + Matplotlib color. + label_offset : [float, float] + Offset in lon, lat degrees for the bottom left corner of the label + text relative to the point. A value of None will use 0.01 default. + start_time : str + UTC time to use as start time for subsetting in datetime format. + (e.g. 2014-08-20 12:30:00) + end_time : str + UTC time to use as an end time for subsetting in datetime format. + (e.g. 2014-08-20 16:30:00) + ax : Matplotlib axis instance + Axis on which to plot. + """ + # parse parameters + ax = common._parse_ax(ax) + + # Only plot every nth barb from the barbspacing parameter + lon_offset, lat_offset = label_offset + if lon_offset is None: + lon_offset = 0.01 + if lat_offset is None: + lat_offset = 0.01 + + # Get start and end times (this deals with subsets) + dt_start = self._get_datetime(start_time, get_start=True) + dt_end = self._get_datetime(end_time, get_end=True) + + time = self.time['data'][(self.time['data'][:] >= dt_start) & + (self.time['data'][:] <= dt_end)] + lon = self.longitude['data'][(self.time['data'][:] >= dt_start) & + (self.time['data'][:] <= dt_end)] + lat = self.latitude['data'][(self.time['data'][:] >= dt_start) & + (self.time['data'][:] <= dt_end)] + + # Find the number of points to plot + labeltimes = time[::labelspacing] + lons = lon[::labelspacing] + lats = lat[::labelspacing] + xpos_mrkr, ypos_mrkr = self.basemap(lons, lats) + xpos_text, ypos_text = self.basemap(lons + lon_offset, + lats + lat_offset) + + for nn in range(len(labeltimes)): + # Check to make sure there are no missing lon/lat values and + # only plot the valid points + # This causes fatal error when trying to save figure later + if np.logical_and(np.isfinite(xpos_mrkr[nn]), + np.isfinite(ypos_mrkr[nn])): + + # Plot the symbol + ax.plot(xpos_mrkr[nn], ypos_mrkr[nn], symbol, **kwargs) + label_text = labeltimes[nn].strftime("%H:%M") + # Attach the text + ax.text(xpos_text[nn], ypos_text[nn], label_text, + fontsize=size, color=color) + + def label_time_point(self, label_time, label_text=None, + add_symbol=False, symbol='k*', size=12, color='k', + label_offset=(None, None), ax=None, **kwargs): + """ + Add a label at a particular time along track. + + Parameters + ---------- + label_time : str + UTC time to use as start time for subsetting in datetime format. + (e.g. 2014-08-20 12:30:00) + label_text : str + Optional label to use, if none given a time stamp is generated. + add_symbol : bool + True to add a symbol at the label_time, provided by symbol keyword. + symbol : str + Color-symbol combo per matplotlib.pyplot.plot + size : int + Font size used. + color : str + Matplotlib color + label_offset : [float, float] + Offset in lon, lat degrees for the bottom left corner of the label + text relative to the point. A value of None will use 0.01 default. + ax : Matplotlib axis instance + Axis on which to plot. + """ + + # parse parameters + ax = common._parse_ax(ax) + + # Only plot every nth barb from the barbspacing parameter + lon_offset, lat_offset = label_offset + if lon_offset is None: + lon_offset = 0.01 + if lat_offset is None: + lat_offset = 0.01 + + # Get start and end times (this deals with subsets) + dt_point = self._get_datetime(label_time) + + time = self.time['data'][(self.time['data'][:] >= dt_point)] + lon = self.longitude['data'][(self.time['data'][:] >= dt_point)] + lat = self.latitude['data'][(self.time['data'][:] >= dt_point)] + + xpos_mrkr, ypos_mrkr = self.basemap(lon[0], lat[0]) + xpos_text, ypos_text = self.basemap( + lon[0] + lon_offset, lat[0] + lat_offset) + + # Check to make sure there are no missing lon/lat values and + # only plot the valid points + # This causes fatal error when trying to save figure later + if np.logical_and(np.isfinite(xpos_mrkr), np.isfinite(ypos_mrkr)): + # Plot the symbol + if add_symbol: + ax.plot(xpos_mrkr, ypos_mrkr, symbol, **kwargs) + if label_text is None: + label_text = time[0].strftime("%H:%M") + + # Attach the text + ax.text(xpos_text, ypos_text, label_text, + fontsize=size, color=color) + return + +################## +# Line methods # +################## + # Original Multiline color plotting found at + # http://nbviewer.ipython.org/github/dpsanders/matplotlib-examples/blob/master/colorline.ipynb + # Modified for this class# + + def _colorline(self, x, y, z, data, cmap=None, norm=None, + linewidth=1.5, alpha=0.5, vmin=None, vmax=None): + ''' + Plot a colored line with coordinates x and y. + Optionally specify colors in the array z. + Optionally specify a colormap, a norm function and a line width. + + Parameters + ---------- + x : float array + X coordinates to map. + Y : float array + Y coordinates to map. + z : float array + Variable data to plot. + cmap : str + String of colormap to use. + norm : float array + Normalized array for colormapping. + linewidth : float + Sets the width of the line drawn. + alpha : float + Sets the transparency of the line (0 = transparent, 1 = opaque). + vmin : float + Sets the minimum to scale colormap luminance. + vmax : float + Sets the maximum to scale colormap luminance. + ''' + # Get the colormap to work with + if cmap is None: + cmap = 'spectral' + + if vmin is None: + vmin = data.min() + + if vmax is None: + vmax = data.max() + + # Use the vmin/vmax as a mask + data = np.ma.masked_outside(data, vmin, vmax) + + # Set the normalization to the min and max + if norm is None: + norm = plt.Normalize(vmin, vmax) + + # Create list of line segments from x and y coordinates, + # in the correct format for LineCollection: + # an array of the form numlines x (points per line) x 2 (x & y) array + points = np.array([x, y, z]).T.reshape(-1, 1, 3) + segments = np.concatenate([points[:-1], points[1:]], axis=1) + + lc = Line3DCollection(segments, cmap=cmap, norm=norm, + linewidth=linewidth, alpha=alpha) + lc.set_array(data) + + return lc + + # NOTE: This is in here for legacy, RECOMMENDED NOT TO USE + def draw_marker_track(self, cmap=None, msize=2, mstyle='.'): + """ + Draws a track by plotting each marker and colored based on height + This is REALLY slow for large number of points. + Left in for reference. + + Highly recommended to not use this function. + """ + # Color code the marker based on height + def get_marker_color(altitude): + if ((altitude > 25.) & (altitude <= 150.)): + mcol = 'm' + return mcol + elif ((altitude > 150.) & (altitude <= 460.)): + mcol = 'b' + return mcol + elif (altitude > 460.) & (altitude <= 915.): + mcol = 'g' + return mcol + elif (altitude > 915.) & (altitude <= 1525.): + mcol = 'c' + return mcol + elif (altitude > 1525.) & (altitude <= 2290.): + mcol = 'y' + return mcol + elif (altitude > 2290.) & (altitude <= 3050.): + mcol = 'r' + return mcol + elif (altitude > 3050.): + mcol = 'k' + return mcol + else: + mcol = 'w' + return mcol + + # Get the colormap to work with + if cmap is None: + cmap = 'spectral' + + # Loop through each point. + # Unfortunately as of Aug 19, 2014 basemap.scatter + # was not working. No display and I have no idea why! + # This is SLOW and not recommended. + for lon, lat, alt in zip( + self.longitude['data'][:].compressed(), + self.latitude['data'][:].compressed(), + self.altitude['data'][:].compressed()): + mcol = get_marker_color(alt) + self.basemap.plot(lon, lat, color=mcol, latlon=True, + marker=mstyle, markersize=msize) + +################# +# Get methods # +################# + + def _get_datetime(self, time_string, get_start=False, get_end=False): + '''Get a start time as datetime instance for subsetting.''' + # Check to see if time is subsetted + if time_string is None: + if get_start is True: + dt = self.time['data'][:].min() + if get_end is True: + dt = self.time['data'][:].max() + else: + tStr = [time_string[0:4], time_string[5:7], time_string[8:10], + time_string[11:13], time_string[14:16], + time_string[17:19], '0'] + tInt = [int(s) for s in tStr] + try: + dt = datetime(tInt[0], tInt[1], tInt[2], tInt[3], + tInt[4], tInt[5], tInt[6]) + except: + import warnings + warnings.warn(common.DATE_STRING_FORMAT) + return + + return dt + + def _get_start_datetime(self, start_time): + ''' + Get a start time as datetime instance for subsetting. + ''' + # Check to see if time is subsetted + if start_time is None: + dt_start = self.time['data'][:].min() + else: + startStr = [start_time[0:4], start_time[5:7], start_time[8:10], + start_time[11:13], start_time[14:16], + start_time[17:19], '0'] + startInt = [int(s) for s in startStr] + try: + dt_start = datetime( + startInt[0], startInt[1], startInt[2], startInt[3], + startInt[4], startInt[5], startInt[6]) + except: + import warnings + warnings.warn(common.DATE_STRING_FORMAT) + return + + return dt_start + + def _get_end_datetime(self, end_time): + ''' + Get a start time as datetime instance for subsetting. + ''' + # Check to see if the time is subsetted + if end_time is None: + dt_end = self.time['data'][:].max() + else: + endStr = [end_time[0:4], end_time[5:7], end_time[8:10], + end_time[11:13], end_time[14:16], end_time[17:19], '0'] + endInt = [int(s) for s in endStr] + try: + dt_end = datetime(endInt[0], endInt[1], endInt[2], endInt[3], + endInt[4], endInt[5], endInt[6]) + except: + import warnings + warnings.warn(common.DATE_STRING_FORMAT) + return + + return dt_end + + def _get_x_y_time_subset(self, start_time, end_time, return_time=False): + ''' + Get a subsetted X and Y to control track length if input by user. + ''' + # Check to see if time is subsetted + dt_start = self._get_datetime(start_time, get_start=True) + dt_end = self._get_datetime(end_time, get_end=True) + + x = self.x[(self.time['data'][:] >= dt_start) & + (self.time['data'][:] <= dt_end)] + y = self.y[(self.time['data'][:] >= dt_start) & + (self.time['data'][:] <= dt_end)] + + if return_time: + time = self.time['data'][(self.time['data'][:] >= dt_start) & + (self.time['data'][:] <= dt_end)] + + return x, y, time + else: + + return x, y + + def _get_lon_lat_time_subset(self, start_time, end_time): + ''' + Get a subsetted Lon and Lat to control track length if input by user. + ''' + # Check to see if time is subsetted + dt_start = self._get_datetime(start_time, get_start=True) + dt_end = self._get_datetime(end_time, get_end=True) + + lon = self.longitude['data'][(self.time['data'][:] >= dt_start) & + (self.time['data'][:] <= dt_end)] + lat = self.latitude['data'][(self.time['data'][:] >= dt_start) & + (self.time['data'][:] <= dt_end)] + + return lon, lat + + def _get_var_time_subset(self, field, start_time, end_time): + ''' + Get a subsetted time and Variable to control track + length if input by user. + ''' + # Check to see if time is subsetted + dt_start = self._get_datetime(start_time, get_start=True) + dt_end = self._get_datetime(end_time, get_end=True) + var = self.flight_data[field] + vsub = var['data'][(self.time['data'][:] >= dt_start) & + (self.time['data'][:] <= dt_end)] + return vsub + + def _get_time_var_time_subset(self, field, start_time, end_time): + ''' + Get a subsetted time and Variable to control track + length if input by user. + ''' + # Check to see if time is subsetted + dt_start = self._get_datetime(start_time, get_start=True) + dt_end = self._get_datetime(end_time, get_end=True) + tsub = self.time['data'][(self.time['data'][:] >= dt_start) & + (self.time['data'][:] <= dt_end)] + var = self.flight_data[field] + vsub = var['data'][(self.time['data'][:] >= dt_start) & + (self.time['data'][:] <= dt_end)] + return tsub, vsub + + def _get_time_subset(self, start_time, end_time): + ''' + Get a subsetted time to control track length if input by user. + ''' + # Check to see if time is subsetted + dt_start = self._get_datetime(start_time, get_start=True) + dt_end = self._get_datetime(end_time, get_end=True) + tsub = self.time['data'][(self.time['data'][:] >= dt_start) & + (self.time['data'][:] <= dt_end)] + return tsub + + def _get_lat_index(self, value, radar): + ''' Calculate the exact index position within latitude array. ''' + # Find the spacing + dp = radar['latitude']['data'][1] - radar['latitude']['data'][0] + + # Calculate the relative position + pos = (value - radar['latitude']['data'][0]) / dp + return pos + + def _get_lon_index(self, value, radar): + ''' Calculate the exact index position within longitude array. ''' + # Find the spacing + dp = radar['longitude']['data'][1] - radar['longitude']['data'][0] + + # Calculate the relative position + pos = (value - radar['longitude']['data'][0]) / dp + return pos + + def _get_radar_variable_dict_data(self, radar, field): + ''' Get the variable from the fields dictionary. ''' + Var, data = radar['fields'][field], radar['fields'][field]['data'][:] + return Var, data + + def _get_var_dict(self, field): + ''' Get the variable from the flight dictionary. ''' + var, data = self.flight_data[field], self.flight_data[field]['data'][:] + return var, data diff --git a/awot/graph/microphysical_vertical.py b/awot/graph/microphysical_vertical.py index ebf052d..67bac1f 100755 --- a/awot/graph/microphysical_vertical.py +++ b/awot/graph/microphysical_vertical.py @@ -14,7 +14,7 @@ # sweep_earth_relative - Polar coord data transformed to earth-relative frame # Load the needed packages -from __future__ import print_function + import numpy as np import matplotlib.pyplot as plt from matplotlib.dates import DateFormatter, date2num @@ -208,8 +208,7 @@ def time_height_image(self, field, cb_label = r'log$_{10}$[' + cb_label + ']' # Print out min and max values to screen - print("Minimum value of %s = %g" % (field, np.ma.min(Data))) - print("Maximum value of %s = %g" % (field, np.ma.max(Data))) + print(f"--> Minimum/Maximum value of {field} = {np.ma.min(Data)}/{np.ma.max(Data)}") # Get vmin/vmax if not given if vmin is None: @@ -234,7 +233,7 @@ def time_height_image(self, field, cmap, norm = from_levels_and_colors( discrete_cmap_levels, cm(levpos), extend='max') except: - print("Keyword error: 'discrete_cmap_levels' must " + print("--> Keyword error: 'discrete_cmap_levels' must " "be a list of float or integer") # Plot the time series @@ -376,8 +375,7 @@ def track_height_image(self, field, track_key=None, cb_label = r'log$_{10}$[' + cb_label + ']' # Print out min and max values to screen - print("Minimum value of %s = %g" % (field, np.ma.min(Data))) - print("Maximum value of %s = %g" % (field, np.ma.max(Data))) + print(f"--> Minimum/Maximum value of {field} = {np.ma.min(Data)}/{np.ma.max(Data)}") # Get vmin/vmax if not given if vmin is None: @@ -402,7 +400,7 @@ def track_height_image(self, field, track_key=None, cmap, norm = from_levels_and_colors( discrete_cmap_levels, cm(levpos), extend='max') except: - print("Keyword error: 'discrete_cmap_levels' must " + print("--> Keyword error: 'discrete_cmap_levels' must " "be a list of float or integer") # Plot the time series diff --git a/awot/graph/radar_3d.py b/awot/graph/radar_3d.py index 6665d29..8491f7b 100644 --- a/awot/graph/radar_3d.py +++ b/awot/graph/radar_3d.py @@ -8,11 +8,11 @@ """ -from __future__ import print_function + from mpl_toolkits.mplot3d import axes3d import numpy as np -from .common import find_nearest_indices, get_masked_data +from .common import (find_nearest_indices, get_masked_data, _get_variable_dict, _get_variable_dict_data, _parse_fig) class Radar3DPlot(object): @@ -42,7 +42,7 @@ def __init__(self, radardata): def DPJgrid_3d(self, surf_field, surf_min=-5., surf_max=5., surf_cmap='RdBu_r', rstride=5, cstride=5, - plot_contour=False, cont_field=None, ppi_height=2., + plot_contour=False, con_field=None, ppi_height=2., cminmax=(0., 60.), clevs=25, vmin=15., vmax=60., cmap='gist_ncar', alpha=0., zlims=(-5., 5.), dlat=1., dlon=1., @@ -55,8 +55,6 @@ def DPJgrid_3d(self, surf_field, ---------- surf_field : str Name of field to use for the 3D surface plot. - ppi_height : float - Height at which to plot the horizontal field. surf_min : float Minimum surface value to display. @@ -70,6 +68,9 @@ def DPJgrid_3d(self, surf_field, Column stride. plot_contour : bool True to plot a contour along the axes. + cond_field (str): Contour field to plot + ppi_height : float + Height at which to plot the horizontal field. cminmax : tuple (min,max) values for controur levels. clevs : integer @@ -101,10 +102,10 @@ def DPJgrid_3d(self, surf_field, Figure to add the plot to. None will use the current figure. """ # parse parameters - fig = self._parse_fig(fig) + fig = _parse_fig(fig) # Get variable - Var, Data = self._get_variable_dict_data(surf_field) + Var, Data = _get_variable_dict_data(surf_field) # Create contour level array clevels = np.linspace(vmin, vmax, clevs) @@ -126,17 +127,17 @@ def DPJgrid_3d(self, surf_field, # Lat2D,Lon2D,W,rstride=rstride,cstride=cstride,alpha=alf) ax.set_xlim(Lat2D.min(), Lat2D.max()) ax.set_ylim(Lon2D.min(), Lon2D.max()) - ax.set_zlim(zlim) + ax.set_zlim(zlims) ax.set_xlabel('Latitude') ax.set_ylabel('Longitude') ax.set_zlabel(' Altitude (km)') # ax.view_init(20., cb=fig.colorbar(pS, shrink=0.6) - cb.set_label(Var['long_name'] + Var['units']) # r'(m s$^{-1}$)') +## cb.set_label(Var['long_name'] + Var['units']) # r'(m s$^{-1}$)') # Plot the horizontal dBZ contour field if plot_contour: if (con_field is not None): - conVar = self._get_variable_dict(con_field) + conVar = _get_variable_dict(con_field) # Find the closest vertical point Zind = find_nearest_indices(self.height['data'][:], ppi_height) @@ -145,7 +146,7 @@ def DPJgrid_3d(self, surf_field, vmin=vmin, vmax=vmax, cmap=cmap, zdir='z') # ,offset=surf_min) else: - print("Need to set con_field and ppi_height") + print("--> Check `con_field` and `ppi_height` keywords") if plot_track: ax.plot(self.latitude['data'][:], self.longitude['data'][:], diff --git a/awot/graph/radar_horizontal.py b/awot/graph/radar_horizontal.py index dba0296..2fc5b82 100755 --- a/awot/graph/radar_horizontal.py +++ b/awot/graph/radar_horizontal.py @@ -9,6 +9,7 @@ from mpl_toolkits.basemap import Basemap, cm import matplotlib.pyplot as plt +from matplotlib.colors import from_levels_and_colors from mpl_toolkits.mplot3d import axes3d from matplotlib import ticker import numpy as np @@ -163,7 +164,7 @@ def plot_cappi(self, field, cappi_height, plot_km=False, levelht = self.height['data'][Zind] / 1000. else: levelht = self.height['data'][Zind] - print("Closest level: %4.1f" % levelht) + print(f"--> Closest level: {levelht:4.1f}") # Convert lats/lons to 2D grid Lon2D, Lat2D = np.meshgrid(self.longitude['data'][:], @@ -183,7 +184,7 @@ def plot_cappi(self, field, cappi_height, plot_km=False, cmap, norm = from_levels_and_colors( discrete_cmap_levels, cm(levpos), extend='max') except: - print("Keyword error: 'discrete_cmap_levels' must " + print("--> Keyword error: 'discrete_cmap_levels' must " "be a list of float or integer") # Plot the data @@ -416,7 +417,7 @@ def plot_lf(self, field=None, mask_procedure=None, mask_tuple=None, cmap, norm = from_levels_and_colors( discrete_cmap_levels, cm(levpos), extend='max') except: - print("Keyword error: 'discrete_cmap_levels' must " + print("--> Keyword error: 'discrete_cmap_levels' must " "be a list of float or integer") p = self.basemap.pcolormesh(x, y, Data, vmin=vmin, vmax=vmax, @@ -534,7 +535,7 @@ def DPJgrid_3d(self, surf_field, surf_min=-5., surf_max=5., """ r3d = Radar3DPlot(self.radar, basemap=self.basemap) - r3d.DPJgrid_3d(self.airborne, surf_field, surf_min=surf_min, + r3d.DPJgrid_3d(self.radar, surf_field, surf_min=surf_min, surf_max=surf_max, surf_cmap=surf_cmap, rstride=rstride, cstride=cstride, plot_contour=plot_contour, cont_field=cont_field, diff --git a/awot/graph/radar_swath.py b/awot/graph/radar_swath.py new file mode 100755 index 0000000..2865d29 --- /dev/null +++ b/awot/graph/radar_swath.py @@ -0,0 +1,519 @@ +""" +awot.graph.radar_swath +=========================== + +A group of scripts create plots of horizontal swaths from airborne radar. + +""" + +from mpl_toolkits.basemap import Basemap, cm +import matplotlib.pyplot as plt +from mpl_toolkits.mplot3d import axes3d +from matplotlib import ticker +import numpy as np +import scipy.ndimage as scim + +from .common import (find_nearest_indices, get_masked_data, + _check_basemap, create_basemap, + _get_start_datetime, _get_end_datetime, + _parse_ax_fig, _parse_ax) + + +class RadarSwathPlot(object): + """Class to plot a horizontal radar image.""" + + def __init__(self, radar, basemap=None, + lon_name=None, lat_name=None, height_name=None, + range_name=None, time_name=None, + resolution='l', projection='lcc', area_thresh=None, + ax=None): + ''' + Parameters + ---------- + radar : dict + AWOT radar object. + basemap : basemap instance + lon_name : str + Key in radar instance for longitude variable. + None uses AWOT default. + lat_name : str + Key in radar instance for latitude variable. + None uses AWOT default. + height_name : str + Key in radar instance for height variable. + None uses AWOT default. + time_name : str + Key in radar instance for time variable. + None uses AWOT default. + + Initialize the class to create plots. + ''' + + # Save the airborne class to this class in case cross-section is passed + self.radar = radar + + self.fields = self.radar['fields'] + + if lon_name is None: + self.longitude = self.radar['longitude'] + else: + self.longitude = self.radar[lon_name] + if lat_name is None: + self.latitude = self.radar['latitude'] + else: + self.latitude = self.radar[lat_name] + if range_name is None: + self.range = self.radar['range'] + else: + self.range = self.radar[range_name] + if time_name is None: + self.time = self.radar['time'] + else: + self.time = self.radar[time_name] + + if height_name is None: + alt2D, range2D = np.meshgrid( + self.radar['altitude']['data'], + self.range['data']) + self.height = { + 'data': alt2D - range2D, + 'units': self.radar['altitude']['units'] + } + else: + self.height = self.radar[height_name] + + if basemap is not None: + self.basemap = basemap + else: + # Create Basemap instance + lon_0 = np.median(self.longitude['data'][:]) + lat_0 = np.median(self.latitude['data'][:]) + corners = [np.min(self.longitude['data'][:]), + np.min(self.latitude['data'][:]), + np.max(self.longitude['data'][:]), + np.max(self.latitude['data'][:])] + self.basemap = create_basemap( + proj=projection, + lon_0=lon_0, lat_0=lat_0, + corners=corners, ax=ax, + resolution=resolution, + area_thresh=area_thresh) + +#################### +# Plot modules ## +#################### + + def plot_at_range_height(self, field, range_height, mask_procedure=None, + mask_tuple=None, cminmax=(0., 60.), + clevs=25, vmin=15., vmax=60., + clabel='dBZ', title=" ", title_size=20, + cmap='gist_ncar', color_bar=True, cb_pad="5%", + cb_loc='right', cb_tick_int=2, + start_time=None, end_time=None, + ax=None, fig=None): + """ + Produce a plot of radar swath at the give range height. + + Parameters + ---------- + field : str + 3-D variable (e.g. Reflectivity [dBZ]) to use in plot. + range_height : float + Height in kilometers at which to plot the horizontal field. + mask_procedure : str + String indicating how to apply mask via numpy, possibilities are: + 'less', 'less_equal', 'greater', 'greater_equal', + 'equal', 'inside', 'outside'. + mask_tuple : (str, float[, float]) + Tuple containing the field name and value(s) below which to mask + field prior to plotting, for example to mask all data where. + cminmax : tuple + (min,max) values for controur levels. + clevs : int + Number of contour levels. + vmin : float + Minimum contour value to display. + vmax : float + Maximum contour value to display. + clabel : str + Label for colorbar (e.g. units 'dBZ'). + title : str + Plot title. + title_size : int + Font size of title to display. + cmap : str + Matplotlib color map to use. + color_bar : boolean + True to add colorbar, False does not. + cb_pad : str + Pad to move colorbar, in the form "5%", pos is to + right for righthand location. + cb_loc : str + Location of colorbar, default is 'right', also available: + 'bottom', 'top', 'left'. + cb_tick_int : int + Interval to use for colorbar tick labels, + higher number "thins" labels. + start_time : str + UTC time to use as start time for subsetting in datetime format. + (e.g. 2014-08-20 12:30:00) + end_time : str + UTC time to use as an end time for subsetting in datetime format. + (e.g. 2014-08-20 16:30:00) + ax : Matplotlib axis instance + Axis to plot. + None will use the current axis. + fig : Matplotlib figure instance + Figure on which to add the plot. + None will use the current figure. + + Notes + ----- + Defaults are established during DYNAMO project analysis. + """ + # parse parameters + ax, fig = _parse_ax_fig(ax, fig) + + # Grab the variable dictionary of interest to plot +# Var = self.fields[field] + + # Return masked or unmasked variable +# Var, Data = self._get_variable_dict_data(field) +# if mask_procedure is not None: +# Data = get_masked_data(Data, mask_procedure, mask_tuple) + + # Return masked or unmasked variable + # Subsetted if desired + Var, tsub, Data = self._get_variable_dict_data_time_subset( + field, start_time, end_time) + if mask_procedure is not None: + Data = get_masked_data(Data, mask_procedure, mask_tuple) + + # Find the closest vertical point + Zind = find_nearest_indices(self.range['data'][:], range_height) + print(f"Closest level:{self.range['data'][Zind]:4.1f}") + + # Convert lats/lons to 2D grid + Ht2D, Lon2D = np.meshgrid(self.range['data'][:], + self.longitude['data'][:]) + Ht2D, Lat2D = np.meshgrid(self.range['data'][:], + self.longitude['data'][:]) + + # Convert lats/lons to map projection coordinates + + x, y = self.basemap(Lon2D[:, Zind], Lat2D[:, Zind]) + + # Create plots + cs = self.basemap.pcolormesh(x, y, Data[:, Zind], + vmin=vmin, vmax=vmax, cmap=cmap) + +# plt.colors.colormap.set_under('white') + # Add Colorbar + if color_bar: + cbStr = "%s at %4.1f %s" % (Var['long_name'], + self.height['data'][Zind], + self.height['units']) + cb = self.basemap.colorbar( + cs, location=cb_loc, pad=cb_pad) + cb.set_label(cbStr) + # Set the number of ticks in the colorbar based upon + # number of contours and the tick interval selected + tick_locator = ticker.MaxNLocator(nbins=int(clevs / cb_tick_int)) + cb.locator = tick_locator + cb.update_ticks() + + # Add title + ax.set_title(title, fontsize=title_size) + + return + +# def overlay_wind_vector(self, height_level=2., vtrim=4, vlw=1.3, vhw=2.5, +# vscale=400, refVec=True, refU=10., refUposX=1.05, +# refUposY=1.015, qcolor='k', ax=None, fig=None): +# """ +# Overlays a 2-D wind field at specified height onto map +# +# Parameters +# ---------- +# height_level : float +# Height level for winds in horizontal plot. +# vtrim : float +# The number of vector arrows will be thinned by this factor. +# vlw : float +# Vector arrow linewidth. +# vhw : float +# Vector arrow headwidth. +# vscale : int +# Vector arrow scale (smaller = longer arrow). +# refVec : boolean +# True to attach a reference vector, False to not. +# refU : float +# Magnitude of reference vector [m/s]. +# refU_Xpos : float +# X position of reference vector in axes-relative coordinates. +# refU_Ypos : float +# Y position of reference vector in axes-relative coordinates. +# ax : Matplotlib axis instance +# Axis to plot. None will use the current axis. +# fig : Matplotlib figure instance +# Figure to add the plot. None will use the current figure. +# +# Notes +# ----- +# U is Along aircraft longitudinal axis wind. +# V is Perpendicular aircraft longitudinal axis wind. +# """ +# # parse parameters +# ax, fig = _parse_ax_fig(ax, fig) +# +# # Find the closest vertical point for desired wind field +# Htind = find_nearest_indices(self.height['data'][:], height_level) +# +# # transform to porjection grid +# U = self.fields['Uwind']['data'][Htind, :, :] +# V = self.fields['Vwind']['data'][Htind, :, :] +# lon = self.longitude['data'][:] +# lat = self.latitude['data'][:] +# uproj, vproj, xx, yy = self.basemap.transform_vector( +# U, V, lon, lat, len(lon) / vtrim, len(lat) / vtrim, +# returnxy=True, masked=True) +# +# # Overplot the vectors +# Q = self.basemap.quiver(xx, yy, uproj, vproj, scale=vscale, +# headwidth=vhw, linewidths=vlw, color=qcolor) +# +# # Make a quiver key to attach to figure. +# qkLab = str("%4.1f" % refU) + 'm/s at ' + \ +# str("%4.1f" % height_level) + ' km' +# # , fontproperties={'weight': 'bold'}) +# qk = ax.quiverkey(Q, refUposX, refUposY, refU, qkLab) +# +# return +# +# def plot_point(self, lon, lat, symbol='ro', label_text=None, +# label_offset=(None, None), **kwargs): +# """ +# Plot a point on the current map. +# +# Additional arguments are passed to basemap.plot. +# +# Parameters +# ---------- +# lon : float +# Longitude of point to plot. +# lat : float +# Latitude of point to plot. +# symbol : str +# Matplotlib compatible string which specified the symbol of the +# point. +# label_text : str, optional. +# Text to label symbol with. If None no label will be added. +# label_offset : [float, float] +# Offset in lon, lat degrees bottom left corner of the label +# text relative to the point. None will use 0.01 default. +# """ +# lon_offset, lat_offset = label_offset +# if lon_offset is None: +# lon_offset = 0.01 +# if lat_offset is None: +# lat_offset = 0.01 +# +# # Plot the symbol +# self.basemap.plot(lon, lat, symbol, latlon=True, **kwargs) +# # Attach the text +# if label_text is not None: +# # basemap does not have a text method so we must determine +# # the x and y points and plot them on the basemap's axis. +# x_text, y_text = self.basemap(lon + lon_offset, +# lat + lat_offset) +# self.basemap.ax.text(x_text, y_text, label_text) +# +# def plot_line_geo(self, line_lons, line_lats, +# line_style='r-', lw=3, alpha=0.2, +# label0=True, label_offset=(0.01, 0.01), +# ax=None, fig=None, **kwargs): +# """ +# Plot a line segments on the current map given values in lat and lon. +# +# Additional arguments are passed to basemap.plot. +# +# Parameters +# ---------- +# line_lons : array +# Longitude of line segment to plot. +# line_lats : array +# Latitude of line segment to plot. +# line_style : str +# Matplotlib compatible string which specifies the line style. +# lw : int +# LInewidth. +# alpha : float +# Transparency, 0: transparent, 1: opaque. +# label0 : bool +# True to label the starting point of line, False is no label. +# label_offset : [float, float] +# Offset in lon, lat degrees for bottom left corner of the label +# text relative to the point. None will use 0.01 default. +# ax : Matplotlib axis instance +# Axis to plot. None will use the current axis. +# fig : Matplotlib figure instance +# Matplotlibl figure instance to add plot. +# None will use the current figure. +# """ +# # parse parameters +# if ax is None: +# ax = _parse_ax(ax) +# +# x, y = self.basemap(line_lons, line_lats) +# self.basemap.plot(x, y, line_style, lw=lw, alpha=alpha, ax=ax) +# +# # Overplot the 0 point +# if label0: +# self.plot_point(line_lons[0], line_lats[0], 'ko', +# label_text='0', label_offset=label_offset, +# markersize=0.5) + +######################## +# 3-D plot methods ## +######################## + +# def grid_3d(self, surf_field, surf_min=-5., surf_max=5., +# surf_cmap='RdBu_r', rstride=5, cstride=5, +# plot_contour=False, cont_field=None, range_height=2., +# cminmax=(0., 60.), clevs=25, vmin=15., vmax=60., +# cmap='gist_ncar', alpha=0., zlims=(-5., 5.), dlat=1., +# dlon=1., plot_track=True, title=" ", fig=None, ax=None): +# """ +# Wrapper to call the 3D plotting function for backwards compatability. +# """ +# r3d = Radar3DPlot(self.radar, basemap=self.basemap) +# +# r3d.DPJgrid_3d(self.airborne, surf_field, surf_min=surf_min, +# surf_max=surf_max, surf_cmap=surf_cmap, +# rstride=rstride, cstride=cstride, +# plot_contour=plot_contour, cont_field=cont_field, +# range_height=range_height, +# cminmax=cminmax, clevs=clevs, vmin=vmin, vmax=vmax, +# cmap=cmap, alpha=alpha, +# zlims=zlims, dlat=dlat, dlon=dlon, +# plot_track=plot_track, +# title=title, fig=fig, ax=ax) + +############################# +# Vertical plot methods ## +############################# + + def plot_cross_section(self, field, start_pt, end_pt, xs_length=500, + mask_procedure=None, mask_tuple=None, + title=" ", title_size=20, cminmax=(0., 60.), + clevs=25, vmin=15., vmax=60., cmap='gist_ncar', + clabel='dBZ', color_bar=True, cb_pad=.05, + cb_orient='vertical', cb_tick_int=2, + ax=None, fig=None): + ''' + Plot a cross-section between two points. + + Parameters + ---------- + field : str + 3-D variable (e.g. Reflectivity [dBZ]) to use in plot. + start_pt, end_pt : tuple + (lat, lon) Tuple of start, end points for cross-section. + xs_length : int + Number of to use for the cross section. + mask_procedure : str + String indicating how to apply mask via numpy, possibilities are: + 'less', 'less_equal', 'greater', 'greater_equal', + 'equal', 'inside', 'outside'. + mask_tuple : (str, float[, float]) + Tuple containing the field name and value(s) below which to mask + field prior to plotting, for example to mask all data where. + cminmax : tuple + (min,max) values for controur levels. + clevs : integer + Number of contour levels. + vmin : float + Minimum contour value to display. + vmax : float + Maximum contour value to display. + clabel : str + Label for colorbar (e.g. units 'dBZ'). + title : str + Plot title. + title_size : int + Font size of title to display. + cmap : string + Matplotlib color map to use + color_bar : bool + True to add colorbar, False does not. + cb_pad : str + Pad to move colorbar, in the form "5%", pos is to + right for righthand location. + cb_loc : str + Location of colorbar, default is 'right', also available: + 'bottom', 'top', 'left'. + cb_tick_int : int + Interval to use for colorbar tick labels, + higher number "thins" labels. + ax : Matplotlib axis instance + Axis to plot. None will use the current axis. + fig : Matplotlib figure instance + Figure on which to add the plot. None will use the current figure. + ''' + rvp = RadarVerticalPlot(self.radar, basemap=self.basemap) + + rvp.plot_cross_section( + field, start_pt, end_pt, xs_length=xs_length, + mask_procedure=mask_procedure, mask_tuple=mask_tuple, + title=title, title_size=title_size, + cminmax=cminmax, clevs=clevs, vmin=vmin, vmax=vmax, + cmap=cmap, clabel=clabel, color_bar=color_bar, cb_pad=cb_pad, + cb_orient=cb_orient, cb_tick_int=cb_tick_int, ax=ax, fig=fig) + +################### +# Get methods ## +################### + + def _get_variable_dict(self, field): + '''Get the variable from the fields dictionary''' + Var = self.fields[field] + return Var + + def _get_variable_dict_data(self, field): + '''Get the variable from the fields dictionary.''' + Var, data = self.fields[field], self.fields[field]['data'][:] + return Var, data + + def _get_lat_index(self, value): + '''Calculate the exact index position within latitude array.''' + # Find the spacing + dp = self.latitude['data'][1] - self.latitude['data'][0] + + # Calculate the relative position + pos = (value - self.latitude['data'][0]) / dp + return pos + + def _get_lon_index(self, value): + '''Calculate the exact index position within latitude array.''' + # Find the spacing + dp = self.longitude['data'][1] - self.longitude['data'][0] + + # Calculate the relative position + pos = (value - self.longitude['data'][0]) / dp + return pos + + def _get_variable_dict_data_time_subset(self, field, start_time, end_time): + ''' + Get the variable from the fields dictionary. + Subset the time when in time series format. + ''' + Var, data = self.fields[field], self.fields[field]['data'][:] + + # Check to see if time is subsetted + dt_start = _get_start_datetime(self.time, start_time) + dt_end = _get_end_datetime(self.time, end_time) + tsub = self.time['data'][(self.time['data'] >= dt_start) & + (self.time['data'] <= dt_end)] + datasub = data[(self.time['data'] >= dt_start) & + (self.time['data'] <= dt_end)] + return Var, tsub, datasub diff --git a/awot/graph/radar_sweep.py b/awot/graph/radar_sweep.py index ba4ca65..c28110d 100755 --- a/awot/graph/radar_sweep.py +++ b/awot/graph/radar_sweep.py @@ -7,7 +7,7 @@ """ # Load the needed packages -from __future__ import print_function + import numpy as np import matplotlib.pyplot as plt from matplotlib.dates import DateFormatter, date2num @@ -42,7 +42,7 @@ def __init__(self, radar, map_type=None, basemap=None): ''' # Check the instrument to see how to import airborne class if radar['data_format'] is not 'tdr_sweep': - print("Check file type, procedure may not work!") + print("--> Check file type, procedure may not work!") # Now initialize the RadarHorizontalPlot Class self.radar = radar @@ -173,7 +173,6 @@ def plot_to_grid(self, Xcoord, Ycoord, Values, ylims = (-10., 30.) ax.set_ylim(ylims) - print(xlims, ylims) # Set the axes labels if xlab is None: pass diff --git a/awot/graph/radar_utility.py b/awot/graph/radar_utility.py index 2ac7019..ac12d88 100755 --- a/awot/graph/radar_utility.py +++ b/awot/graph/radar_utility.py @@ -6,7 +6,7 @@ """ # Load the needed packages -from __future__ import print_function + import numpy as np import matplotlib.pyplot as plt from netCDF4 import date2num, num2date @@ -52,7 +52,7 @@ def __init__(self, radar, basemap=None, # See what the field shape looks like for inspection # of time and height arrays later - fieldshape = self.radar['fields'][self.radar['fields'].keys()[0]]['data'].shape + fieldshape = self.radar['fields'][list(self.radar['fields'].keys())[0]]['data'].shape if lon_name is None: self.longitude = self.radar['longitude'] @@ -448,7 +448,7 @@ def plot_cfad(self, field, height_axis=1, cmap, norm = from_levels_and_colors( discrete_cmap_levels, cm(levpos), extend='max') except: - print("Keyword error: 'discrete_cmap_levels' must " + print("--> Keyword error: 'discrete_cmap_levels' must " "be a list of float or integer") p = ax.pcolormesh(cfad_dict['xaxis'], yarr, CFAD, @@ -1029,7 +1029,7 @@ def plot_cfad_diff(self, cfad_dict1, cfad_dict2, cmap, norm = from_levels_and_colors( discrete_cmap_levels, cm(levpos), extend='max') except: - print("Keyword error: 'discrete_cmap_levels' must " + print("--> Keyword error: 'discrete_cmap_levels' must " "be a list of float or integer") p = ax.pcolormesh(cfad_dict1['xaxis'], cfad_dict1['yaxis'], (CFAD1 - CFAD2), @@ -1303,7 +1303,7 @@ def _get_fields_variable_dict_data_time_subset(self, field, else: datasub = data if ~np.any(np.isfinite(datasub)): - print("WARNING: No data found in time subset!") + print("--> WARNING: No data found in time subset!") return datasub def _get_variable_dict_data_time_subset(self, field, start_time, end_time): diff --git a/awot/graph/radar_vertical.py b/awot/graph/radar_vertical.py index 80e9005..9995f85 100755 --- a/awot/graph/radar_vertical.py +++ b/awot/graph/radar_vertical.py @@ -14,7 +14,7 @@ # sweep_earth_relative - Polar coord data transformed to earth-relative frame # Load the needed packages -from __future__ import print_function + import numpy as np import matplotlib.pyplot as plt from matplotlib.dates import DateFormatter, date2num @@ -238,7 +238,7 @@ def plot_cross_section(self, field, start_pt, end_pt, xs_length=500, cmap, norm = from_levels_and_colors( discrete_cmap_levels, cm(levpos), extend='max') except: - print("Keyword error: 'discrete_cmap_levels' must " + print("--> Keyword error: 'discrete_cmap_levels' must " "be a list of float or integer") p = ax.pcolormesh(Dist2D, Ht2D, @@ -418,7 +418,7 @@ def time_height_image(self, field, plot_km=False, cmap, norm = from_levels_and_colors( discrete_cmap_levels, cm(levpos), extend='max') except: - print("Keyword error: 'discrete_cmap_levels' must " + print("--> Keyword error: 'discrete_cmap_levels' must " "be a list of float or integer") # Plot the time series @@ -449,7 +449,7 @@ def time_height_image(self, field, plot_km=False, ft = common.plot_fill_surface(tsub, sfc, ymin=fill_min, color=fill_color, ax=ax) else: - print("No surface height information, cannot fill...") + print("--> No surface height information, cannot fill...") return def track_height_image(self, field, track_key=None, @@ -596,8 +596,7 @@ def track_height_image(self, field, track_key=None, cb_label = r'log$_{10}$[' + cb_label + ']' # Print out min and max values to screen - print("Minimum value of %s = %g" % (field, np.ma.min(Data))) - print("Maximum value of %s = %g" % (field, np.ma.max(Data))) + print(f"--> Minimum/Maximum value of {field} = {np.ma.min(Data)}/{np.ma.max(Data)}") # Get vmin/vmax if not given if vmin is None: @@ -609,9 +608,8 @@ def track_height_image(self, field, track_key=None, clevels = np.linspace(cminmax[0], cminmax[1], clevs) if len(self.height['data'].shape) == 2: - track2D, junk = np.meshgrid(trackd, self.height['data'][0, :]) - Ht2D = Height.T - del junk + track2D, _ = np.meshgrid(trackd, self.height['data'][0, :]) + Ht2D = self.height['data'][:].T else: track2D, Ht2D = np.meshgrid(trackd, self.height['data'][:]) @@ -630,7 +628,7 @@ def track_height_image(self, field, track_key=None, cmap, norm = from_levels_and_colors( discrete_cmap_levels, cm(levpos), extend='max') except: - print("Keyword error: 'discrete_cmap_levels' must " + print("--> Keyword error: 'discrete_cmap_levels' must " "be a list of float or integer") # Plot the time series @@ -661,7 +659,7 @@ def track_height_image(self, field, track_key=None, ft = common.plot_fill_surface(trackd, sfc, ymin=fill_min, color=fill_color, ax=ax) else: - print("No surface height information, cannot fill...") + print("--> No surface height information, cannot fill...") return ################### diff --git a/awot/graph/sonde.py b/awot/graph/sonde.py index 0b9d352..e3a8004 100644 --- a/awot/graph/sonde.py +++ b/awot/graph/sonde.py @@ -176,7 +176,7 @@ def plot_hodograph(data, instance): if data['Type'] == 'radioSonde': - for unew, vnew, hnew in zip(U, V, H): + for unew, vnew, hnew in zip(Uwind, Vwind, Height): if hnew <= 3001.0: u_3km.append(unew) v_3km.append(vnew) @@ -190,8 +190,6 @@ def plot_hodograph(data, instance): u_12km.append(unew) v_12km.append(vnew) - # print(len(u_3km)) - # print(len(v_3km)) # =============================================================# # take the first point of the next height level and attach it # to the last point of the previous line segment to @@ -429,7 +427,7 @@ def plot_wind_barbs(data, instance, **kwargs): if data['Type'] == 'radioSonde': - mask = U.mask + mask = Uwind.mask Uwind = Uwind[~mask] Vwind = Vwind[~mask] P = P[~mask] diff --git a/awot/io/__init__.py b/awot/io/__init__.py index 340bd86..c075d43 100755 --- a/awot/io/__init__.py +++ b/awot/io/__init__.py @@ -7,7 +7,7 @@ .. currentmodule:: awot.io """ -from __future__ import absolute_import + from .flight import read_netcdf, read_netcdf_variable, read_nasa_ames from .read_ground_radar import read_ground_radar diff --git a/awot/io/common.py b/awot/io/common.py index 2215e65..6fa6c24 100644 --- a/awot/io/common.py +++ b/awot/io/common.py @@ -6,7 +6,7 @@ """ -from __future__ import print_function +import matplotlib.pyplot as plt import numpy as np from netCDF4 import num2date, date2num @@ -107,14 +107,16 @@ def _h5var_to_dict(dataset, units=None, long_name=None, standard_name=None): return d -def _var_found(var): - '''Print variable found message.''' - print("Found %s" % var) - - -def _var_not_found(var): - '''Print variable not found message.''' - print("%s does not exist in file..." % var) +def _print_var_status(var, found): + """Print variable found message. + Args: + var (str): Variable for message status + found (bool): Whether that variable is found + """ + if found: + print(f"--> Found {var}") + else: + print(f"--> {var} cannot be found...") ################## # time methods # @@ -181,7 +183,7 @@ def save_figure(self, figName='awot_plot', figType='png', **kwargs): plt.gca() plt.gcf() plt.savefig(figName+'.'+figType, format=figType) - print("Saved figure: %s.%s" % (figName, figType)) + print(f"--> Saved figure: {figName}.{figType}") # Now close the plot to make sure matplotlib is happy plt.close() diff --git a/awot/io/flight.py b/awot/io/flight.py index 127bf51..bd0e501 100644 --- a/awot/io/flight.py +++ b/awot/io/flight.py @@ -4,7 +4,7 @@ Routines for reading flight data from file """ -from __future__ import absolute_import, print_function + import os import numpy as np @@ -12,7 +12,7 @@ from netCDF4 import Dataset, num2date, date2num from . import common -from ..io.name_maps_flight import get_name_map +from .io.name_maps_flight import get_name_map ######################### # NetCDF Methods # @@ -67,7 +67,7 @@ def read_netcdf(fname, time_var=None, mapping_dict=None, platform=None, if platform is not None: name_map = get_name_map(platform) else: - name_map = _make_name_map_from_varlist(ncFile.variables.keys()) + name_map = _make_name_map_from_varlist(list(ncFile.variables.keys())) # Cycle to through variables in file data = _make_data_dictionary(ncFile, name_map, isRAF, RAFdim=RAFdim, @@ -87,7 +87,7 @@ def read_netcdf(fname, time_var=None, mapping_dict=None, platform=None, data['time'] = _get_time( ncFile, isRAF, RAFrate=RAFrate, timevar=time_var) else: - if 'time' in data.keys(): + if 'time' in list(data.keys()): data['time'] = _get_time( ncFile, isRAF, RAFrate=RAFrate, timevar=name_map['time']) else: @@ -153,11 +153,11 @@ def _get_time(ncFile, isRAF, RAFrate=None, timevar=None): # Pull out the start time if timevar is not None: varname = timevar - elif (timevar is None) & ('base_time' in ncFile.variables.keys()): + elif (timevar is None) & ('base_time' in list(ncFile.variables.keys())): varname = 'base_time' - elif (timevar is None) & ('time' in ncFile.variables.keys()): + elif (timevar is None) & ('time' in list(ncFile.variables.keys())): varname = 'time' - elif (timevar is None) & ('Time' in ncFile.variables.keys()): + elif (timevar is None) & ('Time' in list(ncFile.variables.keys())): varname = 'Time' # Sanity check on the time variable in case a dummy time variable @@ -167,7 +167,7 @@ def _get_time(ncFile, isRAF, RAFrate=None, timevar=None): varname = None if varname is not None: - print("Using '%s' to make AWOT time variable" % varname) + print(f"--> Using '{varname}' to make AWOT time variable") TimeSec = np.array(ncFile.variables[varname][:]).ravel() # Check if it is a high rate file and 2D - yep instances of this @@ -178,8 +178,7 @@ def _get_time(ncFile, isRAF, RAFrate=None, timevar=None): TimeSec[0], TimeSec[-1], len(TimeSec) * RAFrate) TimeSec = Timehirate else: - print("No time variable found, using StarTime " - "to make AWOT time variable") + print("--> No time variable found, using StartTime to make AWOT time variable") StartTime = ncFile.StartTime length = len(ncFile.dimensions['Time']) # Create a time array @@ -206,11 +205,11 @@ def _make_data_dictionary(ncFile, name_map, isRAF, RAFdim=None, verbose=False): for var in name_map: if verbose: - print(var) + print(f"--> Processing {var}") # matching = [s.lower() for s in ncFile.variables.keys() # if var[0:13].lower() in s] # if (len(matching) > 0): - if name_map[var] in ncFile.variables.keys(): + if name_map[var] in list(ncFile.variables.keys()): dd[var] = common._ncvar_to_dict(ncFile.variables[name_map[var]]) data = ncFile.variables[name_map[var]] if (isRAF) & (RAFdim in data.dimensions): @@ -219,7 +218,7 @@ def _make_data_dictionary(ncFile, name_map, isRAF, RAFdim=None, verbose=False): # Find the dimension to remove popdim = data.dimensions.index(RAFdim) # Create new indices and remove RAFdim - reindex = range(ndim) + reindex = list(range(ndim)) reindex.pop(popdim) # Get the original shape origshape = ncFile.variables[name_map[var]].shape @@ -290,9 +289,8 @@ def read_nasa_ames(filename, mapping_dict=None, platform=None, verbose=False): readfile = {} if len(hdr['VNAME']) != len(hdr['VSCAL']): - print("ALL variables must be read in this type of file, " - "please check name_map to make sure it is the " - "correct length.") + print("--> ALL variables must be read in this type of file, " + "please check name_map to make sure it is the correct length.") return for jj, name in enumerate(hdr['VNAME']): @@ -316,7 +314,7 @@ def read_nasa_ames(filename, mapping_dict=None, platform=None, verbose=False): readfile['time'] = Time data = {} - sht_var = [elem[:37].lower() for elem in readfile.keys()] + sht_var = [elem[:37].lower() for elem in list(readfile.keys())] for varname in name_map: try: matching = sht_var.index(name_map[varname][:37].lower()) @@ -324,13 +322,12 @@ def read_nasa_ames(filename, mapping_dict=None, platform=None, verbose=False): readfile[list(readfile.keys())[matching]], varname, name_map[varname]) if verbose: - print('{} -> {}'.format(varname, - list(readfile.keys())[matching])) + print(f"--> {varname} -> {list(readfile.keys())[matching]}") except: data[varname] = None if verbose: - print('{} -> NO MATCH'.format(varname)) + print(f"--> {varname} -> NO MATCH") # Calculate U,V wind if not present if 'Uwind' not in name_map: @@ -384,7 +381,7 @@ def _get_ames_header(f): # Check that the file is indeed NASA AMES 1001 if hdr['FFI'] != '1001': - print("Check file type, looks like it's not FFI 1001") + print("--> FATAL: Check file type, looks like it's not FFI 1001") return hdr['ONAME'] = f.readline().rstrip('\n') diff --git a/awot/io/name_maps_flight.py b/awot/io/name_maps_flight.py index fa53dd2..e0ba21b 100644 --- a/awot/io/name_maps_flight.py +++ b/awot/io/name_maps_flight.py @@ -13,7 +13,7 @@ custom name_map. """ -from __future__ import print_function + import warnings diff --git a/awot/io/read_hiaper_radar.py b/awot/io/read_hiaper_radar.py index ca6d146..58c081e 100644 --- a/awot/io/read_hiaper_radar.py +++ b/awot/io/read_hiaper_radar.py @@ -109,7 +109,7 @@ def read_hcr(fname, field_mapping=None, file_mapping=None): ncvars[name_map_data[varname]]) else: data[varname] = None - common._var_not_found(varname) + common._print_var_status(varname, False) # Add fields to their own dictionary fields = {} @@ -123,7 +123,7 @@ def read_hcr(fname, field_mapping=None, file_mapping=None): ncvars[name_map_fields[varname]], Good) else: fields[varname] = None - common._var_not_found(varname) + common._print_var_status(varname, False) #cant remember do we need this SURFACE DATA? diff --git a/awot/io/read_latmos_falcon.py b/awot/io/read_latmos_falcon.py index a4f7f76..67305d5 100755 --- a/awot/io/read_latmos_falcon.py +++ b/awot/io/read_latmos_falcon.py @@ -122,7 +122,7 @@ def read_rasta_dynamic(fname, field_mapping=None): data[varname] = data[varname] * 1000. else: data[varname] = None - common._var_not_found(varname) + common._print_var_status(varname, False) try: data['height'] = common._ncvar_to_dict(ncvars['height']) @@ -148,11 +148,11 @@ def read_rasta_dynamic(fname, field_mapping=None): # Loop through the variables and pull data for varname in name_map_fields: try: - fields[varname] = _ncvar_radar_to_dict( + fields[varname] = common._ncvar_to_dict_masked( ncvars[name_map_fields[varname]], Good) except: fields[varname] = None - common._var_not_found(varname) + common._print_var_status(varname, False) # Save to output dictionary data['fields'] = fields @@ -281,11 +281,11 @@ def read_rasta_microphysics(fname, field_mapping=None): # Loop through the variables and pull data for varname in name_map: try: - fields[varname] = _ncvar_radar_to_dict( + fields[varname] = common._ncvar_to_dict_masked( ncvars[name_map[varname]], Good) except: fields[varname] = None - common._var_not_found(varname) + common._print_var_status(varname, False) if fields['Dm'] is not None: fields['Dm']['data'][:] = fields['Dm']['data'][:] * 1000. diff --git a/awot/io/read_nasa_hiwrap.py b/awot/io/read_nasa_hiwrap.py index 86ed5b6..6ff9ad2 100755 --- a/awot/io/read_nasa_hiwrap.py +++ b/awot/io/read_nasa_hiwrap.py @@ -128,7 +128,7 @@ def read_hiwrap_netcdf(fname, mapping_dict=None, field_mapping=None): # data[varname]['units'] = 'meters' else: data[varname] = None - common._var_not_found(varname) + common._print_var_status(varname, False) # Replace negative range gates - used for calibration purposes # This likely has no affect given data tested from GRIP campaign @@ -174,7 +174,7 @@ def read_hiwrap_netcdf(fname, mapping_dict=None, field_mapping=None): fields[varname]['data'][:, gate_mask] = np.nan except: fields[varname] = None - common._var_not_found(varname) + common._print_var_status(varname, False) # Save to output dictionary data['fields'] = fields @@ -292,8 +292,8 @@ def read_hiwrap_h5(fname, mapping_dict=None, field_mapping=None): # check that h5py is available if not _H5PY_AVAILABLE: - raise MissingOptionalDependency( - "h5py is required to use read_hiwrap_h5 but is not installed") + raise ModuleNotFoundError("h5py is required to use read_hiwrap_h5 but is not installed") + # Read the NetCDF h5File = h5py.File(fname, 'r') @@ -323,12 +323,12 @@ def read_hiwrap_h5(fname, mapping_dict=None, field_mapping=None): # Loop through the variables and pull data # Adjust altitude to meters from kilometers for varname in name_map_flightdata: - if name_map_flightdata[varname] in h5File.keys(): + if name_map_flightdata[varname] in list(h5File.keys()): data[varname] = common._h5var_to_dict( h5File[name_map_flightdata[varname]]) else: data[varname] = None - common._var_not_found(varname) + common._print_var_status(varname, False) # Replace negative range gates - used for calibration purposes gate_mask = np.ma.less(h5File['rangevec'][:], 0.) data['range']['data'][gate_mask] = np.nan @@ -372,7 +372,7 @@ def read_hiwrap_h5(fname, mapping_dict=None, field_mapping=None): fields[varname]['data'][:, gate_mask] = np.nan except: fields[varname] = None - common._var_not_found(varname) + common._print_var_status(varname, False) # Save to output dictionary data['fields'] = fields @@ -531,7 +531,7 @@ def _get_old_hiwrap_time(fname, ncFile, Good_Indices): ncFile.variables['time'][0], 24 * 3600)[0]) if datetime.date(ncFile.variables['year'][:], int(yyyymmdd[4:6]), startday).weekday() != 6: - print("Time could be incorrect, check file to see if time units " + print("--> Time could be incorrect, check file to see if time units " "are 'computer time (sec from last Sunday at 12 am)'") StartDate = yyyymmdd[0:4] + '-' + yyyymmdd[4:6] + '-' + str(startday) diff --git a/awot/io/read_p3_radar.py b/awot/io/read_p3_radar.py index d5bde21..0563ae0 100755 --- a/awot/io/read_p3_radar.py +++ b/awot/io/read_p3_radar.py @@ -7,7 +7,7 @@ """ # Load the needed packages -from __future__ import print_function + from netCDF4 import Dataset import numpy as np from datetime import datetime @@ -124,7 +124,7 @@ def read_windsyn_tdr_netcdf(fname, field_mapping=None): fields[varname] = common._ncvar_to_dict(ncvars[name_map[varname]]) except: fields[varname] = None - common._var_not_found(varname) + common._print_var_status(varname, False) # Mask bad data values # badval = float(ncFile.MissingValue) @@ -153,7 +153,7 @@ def read_windsyn_tdr_netcdf(fname, field_mapping=None): return radar -def read_tdr_grid_variable(fname, Rec): +def read_tdr_grid_variable(fname, var_name): """ Read in a variable from a gridded NetCDF file containing NOAA P-3 tail Doppler radar fields. @@ -162,26 +162,26 @@ def read_tdr_grid_variable(fname, Rec): ---------- fname : str Long path filename. - VarName : str + var_name : str Variable name to access. Output ------ - VarOut : Array + var_out : Array Numpy array object. Useage ------ - VarOut = tdr_grid_variable(fname, Rec) + var_out = tdr_grid_variable(fname, Rec) """ # Read the NetCDF ncFile = Dataset(fname, 'r') # Get the variable of interest - VarOut = ncFile.variables[VarName][:] + var_out = ncFile.variables[var_name][:] ncFile.close() - return VarOut + return var_out def read_windsyn_binary(fname, platform=None, instrument=None, radar_num=None): @@ -253,7 +253,7 @@ def read_windsyn_binary(fname, platform=None, instrument=None, radar_num=None): count=(Imax * Jmax * Kmax)).reshape(Kmax, Jmax, Imax) Vt = np.fromfile(fi, dtype='>f', count=(Imax * Jmax * Kmax)).reshape(Kmax, Jmax, Imax) - Tdif = np.fromfile(fi, dtype='>f', + Tdiff = np.fromfile(fi, dtype='>f', count=(Imax * Jmax * Kmax)).reshape(Kmax, Jmax, Imax) Tave = np.fromfile(fi, dtype='>f', count=(Imax * Jmax * Kmax)).reshape(Kmax, Jmax, Imax) @@ -608,7 +608,7 @@ def _construct_windsyn_hdr_dict(filename): Iw_at_top, I_w_0, Top_Hgt, ] - hdr_dict = dict(zip(names, values)) + hdr_dict = dict(list(zip(names, values))) # Loop through the multiple radars if there are more than one # Adds up to 319 bytes = ( 4 byte pad front + 311 byte vars + 4 byte pad diff --git a/awot/io/read_radar_sweep.py b/awot/io/read_radar_sweep.py index 318f084..cab8800 100755 --- a/awot/io/read_radar_sweep.py +++ b/awot/io/read_radar_sweep.py @@ -35,14 +35,13 @@ def read_tdr_sweep(fname, map_to_awot=True, If set this supersedes the platform key in AWOT dictionary. """ if not _PYART_AVAILABLE: - raise MissingOptionalDependency( - "pyart is required to use read_tdr_sweep but is not installed") + raise ModuleNotFoundError("Py-ART is required to use read_tdr_sweep but is not installed") rad = pyart.io.read(fname) if map_to_awot: # build the fields dictionary fields = {} - for fldName in rad.fields.keys(): + for fldName in list(rad.fields.keys()): fields[fldName] = rad.fields[fldName] # Create a dictionary to transfer the data @@ -95,14 +94,13 @@ def read_lf_sweep(fname, map_to_awot=True, If set this supersedes the platform key in AWOT dictionary. """ if not _PYART_AVAILABLE: - raise MissingOptionalDependency( - "pyart is required to use read_lf_sweep but is not installed") + raise ModuleNotFoundError("Py-ART is required to use read_tdr_sweep but is not installed") rad = pyart.io.read(fname) if map_to_awot: # build the fields dictionary fields = {} - for fldName in rad.fields.keys(): + for fldName in list(rad.fields.keys()): fields[fldName] = rad.fields[fldName] # Create a dictionary to transfer the data diff --git a/awot/io/read_sonde.py b/awot/io/read_sonde.py index 3cb85b9..79f618c 100644 --- a/awot/io/read_sonde.py +++ b/awot/io/read_sonde.py @@ -45,34 +45,34 @@ def read_sounding_data(filePath): fp = open(data, 'r') sounding_data = fp p, h, T, Td, RH, MIXR, wd, ws = np.genfromtxt( - sounding_data, skip_header=8, usecols=range(0, 8), + sounding_data, skip_header=8, usecols=list(range(0, 8)), dtype=float, delimiter=None, autostrip=True, missing_values='-9999.00', unpack=True, usemask=True) - u = -ws * np.sin(np.radians(wd)) - v = -ws * np.cos(np.radians(wd)) + u_wind = -ws * np.sin(np.radians(wd)) + v_wind = -ws * np.cos(np.radians(wd)) data = dict() data['metadata'] = header data['temperature'] = _build_dict(T, 'c', 'Temperature of ambient air', 'Temperature') - data['dewpoint'] = _build_dict(TD, + data['dewpoint'] = _build_dict(Td, 'c', 'Dewpoint temperature of ambient air', 'Dewpoint_Temperature') - data['presssure'] = _build_dict(P, + data['presssure'] = _build_dict(p, 'hPa', 'Pressure of ambient air', 'Pressure') data['relative_humidity'] = _build_dict(RH, '%', 'Relative Humidity of ambient air', 'Relative_Humidity') - data['u_component'] = _build_dict(Uwind, + data['u_component'] = _build_dict(u_wind, 'm/s', 'u component of wind', 'U_component') - data['v_component'] = _build_dict(Vwind, + data['v_component'] = _build_dict(v_wind, 'm/s', 'v component of wind', 'V_component') - data['height'] = _build_dict(H, + data['height'] = _build_dict(h, 'm', 'Geometric Height in meters', 'Height') data['data_format'] = 'radioSonde' @@ -225,10 +225,9 @@ def read_cls_dropsonde(filename, hdr_num=15): data_end.append(nfilelines) # Check to see if Header ID string if found if nhdr == 0: - raise ValueError('No headers found in file') - return + raise ValueError('No headers found in file')\ - print("%d sonde profiles in file, working to split..." % nhdr) + print(f"--> {nhdr} sonde profiles in file, working to split...") # Create a dictionary to hold the cls data cls = {'time': [], } @@ -241,7 +240,6 @@ def read_cls_dropsonde(filename, hdr_num=15): # Open and move to record location f = open(filename, 'r') f.seek(hdrpos) -# print(ii) hdrdict = _get_header(f) # Reset the header position for next loop # Save some data to the profile dictionary diff --git a/awot/io/read_t28.py b/awot/io/read_t28.py index ec2d334..7232a6d 100644 --- a/awot/io/read_t28.py +++ b/awot/io/read_t28.py @@ -7,9 +7,9 @@ """ -from __future__ import absolute_import, print_function + import numpy as np -from netCDF4 import num2date +from netCDF4 import (num2date, date2num) from .flight import _winduv from . import common @@ -26,7 +26,7 @@ def read_t28_netcdf(ncFile): name_map = _t28_flight_namemap() data = _make_data_dictionary(ncFile, name_map) data['time'] = _get_time(ncFile) - if 'turb' in data.keys(): + if 'turb' in list(data.keys()): CM2M_TWOTHIRDS = 100**0.6666667 data['turb'] /= CM2M_TWOTHIRDS diff --git a/awot/io/read_uwka_lidar.py b/awot/io/read_uwka_lidar.py index d24a9af..9c78f55 100755 --- a/awot/io/read_uwka_lidar.py +++ b/awot/io/read_uwka_lidar.py @@ -9,7 +9,7 @@ """ # Load the needed packages -from __future__ import print_function + from netCDF4 import Dataset, num2date, date2num import datetime import numpy as np @@ -83,7 +83,7 @@ def read_wcl(fname): ncFile, name_map_data[varname], Good) else: data[varname] = None - common._var_not_found(name_map_data[varname]) + common._print_var_status(name_map_data[varname], False) # Add fields to their own dictionary fields = {} @@ -96,10 +96,10 @@ def read_wcl(fname): if name_map_fields[varname] in ncvars: fields[varname] = common._ncvar_subset_to_dict( ncvars[name_map_fields[varname]], Good) - common._var_found(name_map_fields[varname]) + common._print_var_status(name_map_fields[varname], True) else: fields[varname] = None - common._var_not_found(name_map_fields[varname]) + common._print_var_status(name_map_fields[varname], False) # Save to output dictionary data['fields'] = fields diff --git a/awot/io/read_uwka_radar.py b/awot/io/read_uwka_radar.py index a599b6e..72189bc 100755 --- a/awot/io/read_uwka_radar.py +++ b/awot/io/read_uwka_radar.py @@ -114,7 +114,7 @@ def read_wcr2(fname, field_mapping=None, file_mapping=None): ncvars[name_map_data[varname]]) else: data[varname] = None - common._var_not_found(varname) + common._print_var_status(varname, False) # Add fields to their own dictionary fields = {} @@ -128,7 +128,7 @@ def read_wcr2(fname, field_mapping=None, file_mapping=None): ncvars[name_map_fields[varname]], Good) else: fields[varname] = None - common._var_not_found(varname) + common._print_var_status(varname, False) # Find the surface variable # At http://flights.uwyo.edu/uwka/wcr/projects/owles13/ diff --git a/awot/io/write_radar_netcdf.py b/awot/io/write_radar_netcdf.py index 9593349..da4ad5c 100755 --- a/awot/io/write_radar_netcdf.py +++ b/awot/io/write_radar_netcdf.py @@ -77,7 +77,7 @@ def radar2nc(radar, Outfile=None): htid.long_name = radar['height']['long_name'] # Loop through the fields to create variables for each - for variable in radar['fields'].keys(): + for variable in list(radar['fields'].keys()): if radar['fields'][variable] is not None: vid = nc_fid.createVariable(variable, np.float32, ('height', 'lat', 'lon')) diff --git a/awot/util/__init__.py b/awot/util/__init__.py index 1f6ea20..9e98a94 100755 --- a/awot/util/__init__.py +++ b/awot/util/__init__.py @@ -7,7 +7,7 @@ .. currentmodule:: awot.io """ -from __future__ import absolute_import + from .matcher import (TrackMatch, FlightLevelMatch, RadarMatch) from .convert import (pyart_radar_to_awot, to_awot_flight, build_vardict) diff --git a/awot/util/convert.py b/awot/util/convert.py index e24d3da..b7032f5 100644 --- a/awot/util/convert.py +++ b/awot/util/convert.py @@ -136,7 +136,7 @@ def to_awot_flight(lon_dict=None, lat_dict=None, alt_dict=None, if lon_dict is not None: awot_flight['longitude'] = lon_dict - for d in other_dict.keys(): + for d in list(other_dict.keys()): awot_flight[d] = other_dict[d] # Attempt to build AWOT dictionaries if arrays are provided diff --git a/awot/util/google_earth_tools.py b/awot/util/google_earth_tools.py index 1a65358..3c6c2e6 100644 --- a/awot/util/google_earth_tools.py +++ b/awot/util/google_earth_tools.py @@ -21,8 +21,8 @@ an argument to make_kml(). """ -from __future__ import absolute_import -from __future__ import print_function + + import numpy as np import matplotlib.pyplot as plt try: @@ -70,9 +70,7 @@ def make_kml(llcrnrlon, llcrnrlat, urcrnrlon, urcrnrlat, """ if not SIMPLEKML_FLAG: - print('***ERROR!***') - print('simplekml not installed, download from', - 'https://pypi.python.org/pypi/simplekml/') + raise ModuleNotFoundError("simplekml is required: https://pypi.python.org/pypi/simplekml/") return kml = Kml() altitude = kw.pop('altitude', 2e7) diff --git a/awot/util/matcher.py b/awot/util/matcher.py index cddd8ec..919fff9 100644 --- a/awot/util/matcher.py +++ b/awot/util/matcher.py @@ -26,7 +26,7 @@ class TrackMatch(object): def __init__(self, flight, datavolume, start_time=None, end_time=None, data_lon=None, data_lat=None, - data_time=None, + data_time=None, data_height=None, flight_lon_name=None, flight_lat_name=None, flight_alt_name=None, flight_time_name=None, flight_uwind_name=None, flight_vwind_name=None, @@ -88,27 +88,27 @@ def __init__(self, flight, datavolume, if flight_lon_name is None: lonkey = 'longitude' else: - lonkey = lon_name + lonkey = flight_lon_name if flight_lat_name is None: latkey = 'latitude' else: - latkey = lat_name + latkey = flight_lat_name if flight_alt_name is None: altkey = 'altitude' else: - altkey = alt_name + altkey = flight_alt_name if flight_time_name is None: timekey = 'time' else: - timekey = time_name + timekey = flight_time_name if flight_uwind_name is None: ukey = 'Uwind' else: - ukey = uwind_name + ukey = flight_uwind_name if flight_vwind_name is None: vkey = 'Vwind' else: - vkey = vwind_name + vkey = flight_vwind_name # Set the required flight fields self.flight_lon = self._get_var_time_subset( @@ -149,7 +149,7 @@ def __init__(self, flight, datavolume, self.fldata['altitude'] = self.flight_alt self.fldata['time'] = self.flight_time - for field in flight.keys(): + for field in list(flight.keys()): try: self.fldata[field] = self._get_var_time_subset( flight[field].copy(), start_time, end_time) @@ -159,13 +159,13 @@ def __init__(self, flight, datavolume, # Create a field to store data field to matching data self.data_fields = {} if field_match_dict is None: - for field in self.datavol['fields'].keys(): + for field in list(self.datavol['fields'].keys()): self.data_fields[field] = self.datavol['fields'][field].copy() else: for field in field_match_dict: self.data_fields[field] = self.datavol['fields'][field].copy() self.data_shape = self.data_fields[ - self.data_fields.keys()[0]]['data'].shape + list(self.data_fields.keys())[0]]['data'].shape # Run checks to make sure array lengths are the same warntxt = "Check that data is an AWOT style object or specify keyword!" @@ -173,7 +173,7 @@ def __init__(self, flight, datavolume, try: self.data_lon = self.datavol['longitude']['data'] except: - print("Cannot find longitude, %s" % warntxt) + print(f"--> Cannot find longitude, {warntxt}") else: self.data_lon = np.array(data_lon) @@ -181,14 +181,22 @@ def __init__(self, flight, datavolume, try: self.data_lat = self.datavol['latitude']['data'] except: - print("Cannot find latitude, %s" % warntxt) + print(f"--> Cannot find latitude, {warntxt}") else: self.data_lat = np.array(data_lat) + if data_height is None: + try: + self.data_height = self.datavol['height']['data'] + except: + print(f"--> Cannot find height, {warntxt}") + else: + self.data_height = np.array(data_height) + if data_time is None: if self.datavol['time'] is None: self.data_time = None - print("Cannot find time, %s" % warntxt) + print(f"--> Cannot find time, {warntxt}") else: try: self.data_time = common.convert_to_epoch_dict( @@ -199,7 +207,7 @@ def __init__(self, flight, datavolume, self.datavol['time']['units']) else: self.data_time = None - print("Cannot find time, %s" % warntxt) + print(f"--> Cannot find time, {warntxt}") else: try: self.data_time = common.convert_to_epoch_dict(data_time) @@ -279,7 +287,7 @@ def kdtree(self, leafsize=16, (dlat <= np.max(self.flight_lat['data']))) indices = np.where(cond) - print(len(indices[0])) + print(f"--> Found {len(indices[0])} points for conditional") if len(indices[0]) > 1: dlon = dlon[indices[0]] @@ -287,23 +295,23 @@ def kdtree(self, leafsize=16, if use_time: dti = dti[indices[0]] else: - print("No points found for condition") + print("--> No points found for condition") # Build and query kd-tree if use_time: - kdt = cKDTree(zip(dlon, dlat, dti), leafsize=leafsize) + kdt = cKDTree(list(zip(dlon, dlat, dti)), leafsize=leafsize) else: - kdt = cKDTree(zip(dlon, dlat), leafsize=leafsize) + kdt = cKDTree(list(zip(dlon, dlat)), leafsize=leafsize) if use_time: distance, ind1d = kdt.query( - zip(self.flight_lon['data'], self.flight_lat['data'], - self.flight_numtime), + list(zip(self.flight_lon['data'], self.flight_lat['data'], + self.flight_numtime)), k=query_k, eps=query_eps, p=query_p, distance_upper_bound=query_distance_upper_bound, n_jobs=query_n_jobs) else: distance, ind1d = kdt.query( - zip(self.flight_lon['data'], self.flight_lat['data']), + list(zip(self.flight_lon['data'], self.flight_lat['data'])), k=query_k, eps=query_eps, p=query_p, distance_upper_bound=query_distance_upper_bound, n_jobs=query_n_jobs) @@ -314,7 +322,7 @@ def kdtree(self, leafsize=16, matchdata = self._get_data_by_index(ind1d) if timeit: - print("--- Elapsed time: %s sec ---" % (timer.time() - opbegin)) + print(("--- Elapsed time: %s sec ---" % (timer.time() - opbegin))) return MatchData(self.fldata, matchdata, distance_to_point=distance, indices_1d=ind1d[0], #indices_nd=indnd, start_time=self.start_time, end_time=self.end_time) @@ -367,7 +375,7 @@ def near_neighbor_tunnel(self, use_time=False, verbose=False, timeit=True): matchdata = self._get_data_by_index(ind1d, ) if timeit: - print("--- Elapsed time: %s sec ---" % (timer.time() - opbegin)) + print(("--- Elapsed time: %s sec ---" % (timer.time() - opbegin))) return MatchData(self.fldata, matchdata, distance_to_point=distance, indices_1d=ind1d[0], indices_nd=indnd, start_time=self.start_time, end_time=self.end_time) @@ -393,8 +401,7 @@ def _get_datetime(self, time_string, get_start=False, get_end=False): dt = datetime.datetime(tInt[0], tInt[1], tInt[2], tInt[3], tInt[4], tInt[5], tInt[6]) except: - print("Check the format of date string " - "(e.g. '2014-08-20 12:30:00')") + print("--> Check the format of date string (e.g. '2014-08-20 12:30:00')") return return dt @@ -411,7 +418,7 @@ def _get_var_time_subset(self, var, start_time, end_time): def _get_data_by_index(self, indices_1d, orig_indices): '''Retrieve data fields using calculated indices.''' data_by_ind = {} - for field in self.data_fields.keys(): + for field in list(self.data_fields.keys()): dfield = self.data_fields[field] indices_nd = np.unravel_index(orig_indices[indices_1d], dfield['data'].shape) data_by_ind[field] = dfield.copy() @@ -421,16 +428,9 @@ def _get_data_by_index(self, indices_1d, orig_indices): def _print_pair_by_index(self, ind1d): for ii, ind in enumerate(ind1d): - print(("AC Lat: %g, Lon: %g, Alt: %g | " - "Rad Lat: %g, Lon: %g, Alt: %g") % ( - self.flight_lon['data'][ii], - self.flight_lat['data'][ii], - np.ravel(self.data_lon)[ind], - np.ravel(self.data_lat)[ind], - )) - print("AC/Rad Turb: %g / %g" % ( - self.fldata['turb']['data'][ii], - np.ravel(self.data_fields['turbulence']['data'])[ind])) + print((f"--> AC Lat: {self.flight_lon['data'][ii]}, Lon: {self.flight_lat['data'][ii]}, Alt: {self.flight_alt['data'][ii]} \n" + f"--> Rad Lat: {np.ravel(self.data_lon)[ind]}, Lon: {np.ravel(self.data_lat)[ind]}, Alt: {np.ravel(self.data_height)[ind]}")) + print(f"--> AC/Rad Turb: {self.fldata['turb']['data'][ii]} / {np.ravel(self.data_fields['turbulence']['data'])[ind]}") class FlightLevelMatch(object): @@ -502,27 +502,27 @@ def __init__(self, flight, datavolume, if flight_lon_name is None: lonkey = 'longitude' else: - lonkey = lon_name + lonkey = flight_lon_name if flight_lat_name is None: latkey = 'latitude' else: - latkey = lat_name + latkey = flight_lat_name if flight_alt_name is None: altkey = 'altitude' else: - altkey = alt_name + altkey = flight_alt_name if flight_time_name is None: timekey = 'time' else: - timekey = time_name + timekey = flight_time_name if flight_uwind_name is None: ukey = 'Uwind' else: - ukey = uwind_name + ukey = flight_uwind_name if flight_vwind_name is None: vkey = 'Vwind' else: - vkey = vwind_name + vkey = flight_vwind_name # Set the required flight fields self.flight_lon = self._get_var_time_subset( @@ -559,7 +559,7 @@ def __init__(self, flight, datavolume, self.fldata['altitude'] = self.flight_alt self.fldata['time'] = self.flight_time - for field in flight.keys(): + for field in list(flight.keys()): try: self.fldata[field] = self._get_var_time_subset( flight[field].copy(), start_time, end_time) @@ -569,13 +569,13 @@ def __init__(self, flight, datavolume, # Create a field to store data field to matching data self.data_fields = {} if field_match_dict is None: - for field in self.datavol['fields'].keys(): + for field in list(self.datavol['fields'].keys()): self.data_fields[field] = self.datavol['fields'][field].copy() else: for field in field_match_dict: self.data_fields[field] = self.datavol['fields'][field].copy() self.data_shape = self.data_fields[ - self.data_fields.keys()[0]]['data'].shape + list(self.data_fields.keys())[0]]['data'].shape # Run checks to make sure array lengths are the same warntxt = "Check that data is an AWOT style object or specify keyword!" @@ -583,7 +583,7 @@ def __init__(self, flight, datavolume, try: self.data_lon = self.datavol['longitude']['data'] except: - print("Cannot find longitude, %s" % warntxt) + print(f"--> Cannot find longitude, {warntxt}") else: self.data_lon = np.array(data_lon) @@ -591,7 +591,7 @@ def __init__(self, flight, datavolume, try: self.data_lat = self.datavol['latitude']['data'] except: - print("Cannot find latitude, %s" % warntxt) + print(f"--> Cannot find latitude, {warntxt}") else: self.data_lat = np.array(data_lat) @@ -599,7 +599,7 @@ def __init__(self, flight, datavolume, try: self.data_height = self.datavol['height']['data'] except: - print("Cannot find height, %s" % warntxt) + print(f"--> Cannot find height, {warntxt}") else: self.data_height = np.array(data_height) @@ -612,7 +612,7 @@ def __init__(self, flight, datavolume, self.datavol['time']['data'], self.datavol['time']['units']) else: - print("Cannot find time, %s" % warntxt) + print(f"--> Cannot find time, {warntxt}") else: try: self.data_time = common.convert_to_epoch_dict(data_time) @@ -686,7 +686,7 @@ def kdtree(self, leafsize=16, (dti >= np.min(self.flight_numtime)) & (dti <= np.max(self.flight_numtime))) indices = np.where(cond) - print(len(indices[0])) + print((len(indices[0]))) if len(indices[0]) > 1: dlon = dlon[indices[0]] @@ -694,24 +694,24 @@ def kdtree(self, leafsize=16, dht = dht[indices[0]] dti = dti[indices[0]] else: - print("No points found for condition") + print("--> No points found for condition") # Build and query kd-tree if use_time: - kdt = cKDTree(zip(dlon, dlat, dht, dti), leafsize=leafsize) + kdt = cKDTree(list(zip(dlon, dlat, dht, dti)), leafsize=leafsize) else: - kdt = cKDTree(zip(dlon, dlat, dht), leafsize=leafsize) + kdt = cKDTree(list(zip(dlon, dlat, dht)), leafsize=leafsize) if use_time: distance, ind1d = kdt.query( - zip(self.flight_lon['data'], self.flight_lat['data'], - self.flight_alt['data'], self.flight_numtime), + list(zip(self.flight_lon['data'], self.flight_lat['data'], + self.flight_alt['data'], self.flight_numtime)), k=query_k, eps=query_eps, p=query_p, distance_upper_bound=query_distance_upper_bound, n_jobs=query_n_jobs) else: distance, ind1d = kdt.query( - zip(self.flight_lon['data'], self.flight_lat['data'], - self.flight_alt['data']), + list(zip(self.flight_lon['data'], self.flight_lat['data'], + self.flight_alt['data'])), k=query_k, eps=query_eps, p=query_p, distance_upper_bound=query_distance_upper_bound, n_jobs=query_n_jobs) @@ -722,7 +722,7 @@ def kdtree(self, leafsize=16, matchdata = self._get_data_by_index(ind1d) if timeit: - print("--- Elapsed time: %s sec ---" % (timer.time() - opbegin)) + print(("--- Elapsed time: %s sec ---" % (timer.time() - opbegin))) return MatchData(self.fldata, matchdata, distance_to_point=distance, indices_1d=ind1d[0], indices_nd=indnd, start_time=self.start_time, end_time=self.end_time) @@ -775,7 +775,7 @@ def near_neighbor_tunnel(self, use_time=False, verbose=False, timeit=True): matchdata = self._get_data_by_index(ind1d) if timeit: - print("--- Elapsed time: %s sec ---" % (timer.time() - opbegin)) + print(("--- Elapsed time: %s sec ---" % (timer.time() - opbegin))) return MatchData(self.fldata, matchdata, distance_to_point=distance, indices_1d=ind1d[0], indices_nd=indnd, start_time=self.start_time, end_time=self.end_time) @@ -801,8 +801,7 @@ def _get_datetime(self, time_string, get_start=False, get_end=False): dt = datetime.datetime(tInt[0], tInt[1], tInt[2], tInt[3], tInt[4], tInt[5], tInt[6]) except: - print("Check the format of date string " - "(e.g. '2014-08-20 12:30:00')") + print("--> Check the format of date string (e.g. '2014-08-20 12:30:00')") return return dt @@ -819,7 +818,7 @@ def _get_var_time_subset(self, var, start_time, end_time): def _get_data_by_index(self, indices_1d): '''Retrieve data fields using calculated indices.''' data_by_ind = {} - for field in self.data_fields.keys(): + for field in list(self.data_fields.keys()): dfield = self.data_fields[field] data_by_ind[field] = dfield.copy() data_by_ind[field]['data'] = np.ravel(dfield['data'])[indices_1d] @@ -827,17 +826,9 @@ def _get_data_by_index(self, indices_1d): def _print_pair_by_index(self, ind1d): for ii, ind in enumerate(ind1d): - print(("AC Lat: %g, Lon: %g, Alt: %g | " - "Rad Lat: %g, Lon: %g, Alt: %g") % ( - self.flight_lon['data'][ii], - self.flight_lat['data'][ii], - self.flight_alt['data'][ii], - np.ravel(self.data_lon)[ind], - np.ravel(self.data_lat)[ind], - np.ravel(self.data_height)[ind])) - print("AC/Rad Turb: %g / %g" % ( - self.fldata['turb']['data'][ii], - np.ravel(self.data_fields['turbulence']['data'])[ind])) + print((f"--> AC Lat: {self.flight_lon['data'][ii]}, Lon: {self.flight_lat['data'][ii]}, Alt: {self.flight_alt['data'][ii]}\n" + f"Rad Lat: {np.ravel(self.data_lon)[ind]}, Lon: {np.ravel(self.data_lat)[ind]}, Alt: {np.ravel(self.data_height)[ind]}")) + print(f"--> AC/Rad Turb: {self.fldata['turb']['data'][ii]} / {np.ravel(self.data_fields['turbulence']['data'])[ind]}") class RadarMatch(object): @@ -912,27 +903,27 @@ def __init__(self, flight, radar, volume_update_time=0, if flight_lon_name is None: lonkey = 'longitude' else: - lonkey = lon_name + lonkey = flight_lon_name if flight_lat_name is None: latkey = 'latitude' else: - latkey = lat_name + latkey = flight_lat_name if flight_alt_name is None: altkey = 'altitude' else: - altkey = alt_name + altkey = flight_alt_name if flight_time_name is None: timekey = 'time' else: - timekey = time_name + timekey = flight_time_name if flight_uwind_name is None: ukey = 'Uwind' else: - ukey = uwind_name + ukey = flight_uwind_name if flight_vwind_name is None: vkey = 'Vwind' else: - vkey = vwind_name + vkey = flight_vwind_name # Set the required flight fields self.flight_lon = self._get_var_time_subset( @@ -969,7 +960,7 @@ def __init__(self, flight, radar, volume_update_time=0, self.fldata['altitude'] = self.flight_alt self.fldata['time'] = self.flight_time - for field in flight.keys(): + for field in list(flight.keys()): try: self.fldata[field] = self._get_var_time_subset( flight[field].copy(), start_time, end_time) @@ -1019,7 +1010,7 @@ def kdtree(self, leafsize=16, distance = [] prdata = {} # We assume that all files have the same data field dimensions - for field in self.rlist[0].fields.keys(): + for field in list(self.rlist[0].fields.keys()): prdata[field] = self.rlist[0].fields[field].copy() prdata[field]['data'] = np.full_like(self.flight_numtime, np.nan) @@ -1030,7 +1021,7 @@ def kdtree(self, leafsize=16, dlon = np.ravel(pr.gate_longitude['data'].copy()) dlat = np.ravel(pr.gate_latitude['data'].copy()) dht = np.ravel(pr.gate_altitude['data'].copy()) - for field in pr.fields.keys(): + for field in list(pr.fields.keys()): dfield[field] = np.ravel(pr.fields[field]['data']) dshape = pr.fields[field]['data'].shape @@ -1060,10 +1051,10 @@ def kdtree(self, leafsize=16, dlat = dlat[indices[0]] dht = dht[indices[0]] dti = dti[indices[0]] - for field in pr.fields.keys(): + for field in list(pr.fields.keys()): dfield[field] = dfield[field][indices[0]] else: - print("No points found for condition on file %d" % num) + print(f"--> No points found for condition on file {num}") # Subset for matching times tcond = np.logical_and(self.flight_numtime >= np.min(dti), @@ -1071,35 +1062,35 @@ def kdtree(self, leafsize=16, indt = np.where(tcond) # Build and query kd-tree - kdt = cKDTree(zip(dlon, dlat, dht, dti), leafsize=leafsize) + kdt = cKDTree(list(zip(dlon, dlat, dht, dti)), leafsize=leafsize) if np.size(indt[0]) > 0: prdistance, prind1d = kdt.query( - zip(self.flight_lon['data'][indt[0]], + list(zip(self.flight_lon['data'][indt[0]], self.flight_lat['data'][indt[0]], self.flight_alt['data'][indt[0]], - self.flight_numtime[indt[0]]), + self.flight_numtime[indt[0]])), k=query_k, eps=query_eps, p=query_p, distance_upper_bound=query_distance_upper_bound, n_jobs=query_n_jobs) distance.append(prdistance) ind1d.append(prind1d) - for field in pr.fields.keys(): + for field in list(pr.fields.keys()): prdata[field]['data'][indt[0]] = dfield[field][prind1d] if verbose: for ii, ind in enumerate(ind1d[0]): - print(("AC Lat: %g, Lon: %g, Alt: %g | " + print((("AC Lat: %g, Lon: %g, Alt: %g | " "Rad Lat: %g, Lon: %g, Alt: %g") % ( self.flight_lon['data'][ii], self.flight_lat['data'][ii], self.flight_alt['data'][ii], dlon[ind], dlat[ind], - dht[ind])) + dht[ind]))) # Apply masks to data - for field in prdata.keys(): + for field in list(prdata.keys()): prdata[field]['data'] = np.ma.masked_invalid(prdata[field]['data']) if self.masklo is not None: prdata[field]['data'] = np.ma.masked_less( @@ -1108,7 +1099,7 @@ def kdtree(self, leafsize=16, prdata[field]['data'] = np.ma.masked_greater( prdata[field]['data'], self.maskhi) if timeit: - print("--- Elapsed time: %s sec ---" % (timer.time() - opbegin)) + print(("--- Elapsed time: %s sec ---" % (timer.time() - opbegin))) return MatchData(self.fldata, prdata, distance_to_point=distance, indices_1d=ind1d[0], start_time=self.start_time, end_time=self.end_time) @@ -1137,7 +1128,7 @@ def near_neighbor_tunnel(self, verbose=False, timeit=True): distance = [] prdata = {} # We assume that all files have the same data field dimensions - for field in self.rlist[0].fields.keys(): + for field in list(self.rlist[0].fields.keys()): prdata[field] = self.rlist[0].fields[field].copy() prdata[field]['data'] = np.full_like(self.flight_numtime, np.nan) @@ -1152,7 +1143,7 @@ def near_neighbor_tunnel(self, verbose=False, timeit=True): dlon = np.ravel(pr.gate_longitude['data'].copy()) dlat = np.ravel(pr.gate_latitude['data'].copy()) dht = np.ravel(pr.gate_altitude['data'].copy()) - for field in pr.fields.keys(): + for field in list(pr.fields.keys()): dfield[field] = np.ravel(pr.fields[field]['data']) dshape = pr.fields[field]['data'].shape @@ -1182,10 +1173,10 @@ def near_neighbor_tunnel(self, verbose=False, timeit=True): dlat = dlat[indices[0]] dht = dht[indices[0]] dti = dti[indices[0]] - for field in pr.fields.keys(): + for field in list(pr.fields.keys()): dfield[field] = dfield[field][indices[0]] else: - print("No points found for condition on file %d" % num) + print(("No points found for condition on file %d" % num)) clat, clon = np.cos(np.radians(dlat)), np.cos(np.radians(dlon)) slon, slat = np.sin(np.radians(dlat)), np.sin(np.radians(dlon)) @@ -1209,23 +1200,23 @@ def near_neighbor_tunnel(self, verbose=False, timeit=True): distance.append(np.ma.min(dist_sq)) ind1d.append(prind1d) - for field in pr.fields.keys(): + for field in list(pr.fields.keys()): prdata[field]['data'].flat[[indt[0]]] = \ dfield[field][prind1d] if verbose: for ii, ind in enumerate(ind1d[0]): - print(("AC Lat: %g, Lon: %g, Alt: %g | " + print((("AC Lat: %g, Lon: %g, Alt: %g | " "Rad Lat: %g, Lon: %g, Alt: %g") % ( self.flight_lon['data'][ii], self.flight_lat['data'][ii], self.flight_alt['data'][ii], dlon[ind], dlat[ind], - dht[ind])) + dht[ind]))) # Apply masks to data - for field in prdata.keys(): + for field in list(prdata.keys()): prdata[field]['data'] = np.ma.masked_invalid(prdata[field]['data']) if self.masklo is not None: prdata[field]['data'] = np.ma.masked_less( @@ -1234,7 +1225,7 @@ def near_neighbor_tunnel(self, verbose=False, timeit=True): prdata[field]['data'] = np.ma.masked_greater( prdata[field]['data'], self.maskhi) if timeit: - print("--- Elapsed time: %s sec ---" % (timer.time() - opbegin)) + print(("--- Elapsed time: %s sec ---" % (timer.time() - opbegin))) return MatchData(self.fldata, prdata, distance_to_point=distance, indices_1d=ind1d[0], start_time=self.start_time, end_time=self.end_time) @@ -1258,7 +1249,7 @@ def near_neighbor(self, verbose=False, timeit=True): indel = [] prdata = {} # We assume that all files have the same data field dimensions - for field in self.rlist[0].fields.keys(): + for field in list(self.rlist[0].fields.keys()): prdata[field] = self.rlist[0].fields[field].copy() prdata[field]['data'] = np.full_like(self.flight_numtime, np.nan) @@ -1321,19 +1312,19 @@ def near_neighbor(self, verbose=False, timeit=True): np.abs(ac_dist_hav[index] - pr_sweep2.range['data'])) if verbose: - print("AC/Radar lat/lon/alt: %g/%g, %g/%g, %g/%g" % ( + print(("AC/Radar lat/lon/alt: %g/%g, %g/%g, %g/%g" % ( self.flight_lat['data'][index], pr_sweep.gate_latitude['data'][azin, rgin], self.flight_lon['data'][index], pr_sweep.gate_longitude['data'][azin, rgin], self.flight_alt['data'][index], - pr_sweep.gate_altitude['data'][azin, rgin])) + pr_sweep.gate_altitude['data'][azin, rgin]))) - for field in pr_sweep.fields.keys(): + for field in list(pr_sweep.fields.keys()): prdata[field]['data'][index] = pr_sweep.fields[field]['data'][azin, rgin] # Apply masks to data - for field in prdata.keys(): + for field in list(prdata.keys()): prdata[field]['data'] = np.ma.masked_invalid(prdata[field]['data']) if self.masklo is not None: prdata[field]['data'] = np.ma.masked_less( @@ -1342,7 +1333,7 @@ def near_neighbor(self, verbose=False, timeit=True): prdata[field]['data'] = np.ma.masked_greater( prdata[field]['data'], self.maskhi) if timeit: - print("--- Elapsed time: %s sec ---" % (timer.time() - opbegin)) + print(("--- Elapsed time: %s sec ---" % (timer.time() - opbegin))) return MatchData(self.fldata, prdata, start_time=self.start_time, end_time=self.end_time) @@ -1525,10 +1516,10 @@ def _get_var_time_subset(self, var, start_time, end_time): def _get_data_by_pyart_index(self, radar, indel, indaz, indrng): '''Retrieve data fields using calculated Py-ART indices.''' data_by_ind = {} - for field in radar.fields.keys(): + for field in list(radar.fields.keys()): rfield = radar.fields[field] data_by_ind[field] = rfield.copy() - data_by_ind[field]['data'] = rfield['data'][azin, rgin] + data_by_ind[field]['data'] = rfield['data'][indaz, indrng] return data_by_ind diff --git a/awot/util/shearcalcs.py b/awot/util/shearcalcs.py index fbc79dc..c5da734 100644 --- a/awot/util/shearcalcs.py +++ b/awot/util/shearcalcs.py @@ -1,4 +1,4 @@ -from __future__ import print_function + import numpy as np @@ -86,8 +86,6 @@ def _VertShear_Sfc_to_1km(self, Height, U, V): U1km = np.interp(index1km, Height, U) V1km = np.interp(index1km, Height, V) - print('test at 800 mb recorded 30 knot') - u800 = np.interp(index1km, Height, U) v800 = np.interp(index1km, Height, U) @@ -99,13 +97,6 @@ def _VertShear_Sfc_to_1km(self, Height, U, V): Usfc = U[sfcindex] Vsfc = V[sfcindex] - ''' - print(U) - print(V) - print(U1km) - print(V1km) - ''' - shear = np.sqrt(((U1km - Usfc)**2 + (V1km - Vsfc)**2)/(index1km)) return shear @@ -126,7 +117,6 @@ def _bulkshear_sfc_1km(self, Height, U, V): bulk_shear = np.sqrt((U1km-USFC)**2 + (V1km-VSFC)**2) - print(np.sqrt((U1km)**2 + (V1km)**2)) return bulk_shear def _bulkshear_sfc_3km(self, Height, U, V): @@ -162,7 +152,5 @@ def _bulkshear_sfc_6km(self, Height, U, V): USFC = U[sfcindex] VSFC = V[sfcindex] - print(np.sqrt((U6km)**2 + (V6km)**2)) - bulk_shear = np.sqrt((U6km - USFC)**2+(V6km - VSFC)**2) return bulk_shear diff --git a/awot/util/sonde_calcs.py b/awot/util/sonde_calcs.py deleted file mode 100644 index 3591b9d..0000000 --- a/awot/util/sonde_calcs.py +++ /dev/null @@ -1,131 +0,0 @@ -import numpy as np -from .thermocalcs import ThermoCalcs -from .shearcalcs import ShearCalcs - - -def add_thermo_calcs(data): - - ''' - method to calculate thermodynamic parameters from dropsonde data - - Inputs - ------ - Dictionary of sounding data. - Uses calculations from thermocalcs.py - - Output - ------ - multiple arrays containing thermodynamic information - ''' - tC = ThermoCalcs() - - T = data['temperature']['data'][:] - Td = data['dewpoint']['data'][:] - p = data['presssure']['data'][:] - RH = data['relative_humidity']['data'][:] - u = data['u_component']['data'][:] - v = data['v_component']['data'][:] - h = data['height']['data'][:] - - # Convert temperatures to Kelvin for calcs - TK = T + 273.15 - TdK = Td + 273.15 - - LCLT = round(( - tC._LCL_temperature(h, TK, TdK) - 273.15), 2) - LCLP = round(( - tC._LCL_presssure(h, p, TK, TdK)), 0) - LCLZ = round(( - tC._LCL_Height(h, p, TK, TdK)), 0) - THETA = tC._PTk2_Theta(p, TK) - MIXR = tC._RH_2_MixR(RH, p, TK) - THETAE = tC._Tk_RH_MixR_2_ThetaE( - p, TK, RH, MIXR/1000.) - ESAT = tC._esat(TK) - - # builds nice dictionary of all the thermodynamic/moisture data - - thermoCalcData = dict() - thermoCalcData['lclt'] = _build_dict( - LCLT,'K', 'Lifting Condensation Level Temperature', 'LCL Temp') - thermoCalcData['lclp'] = build_dict( - LCLP, 'hPa', 'Lifting Condensation Level Pressure', 'LCL Pressure') - thermoCalcData['lclz'] = build_dict( - LCLZ, 'm', 'Lifting Condensation Level Height', 'LCL Height') - thermoCalcData['theta'] = build_dict( - THETA, 'K', 'Potential Temperature', 'Potential Temp') - thermoCalcData['mixr'] = build_dict( - MIXR, 'g/m^3', 'Mixing Ratio', 'Mixing Ratio') - thermoCalcData['thetae'] = build_dict( - THETAE, 'K', 'Equivalent Potential Temperature', 'Equiv Potential Temp') - thermoCalcData['esat'] = build_dict( - ESAT, 'hPa', 'Saturation Vapor Pressure', 'Saturation Vapor Pressure') - return thermoCalcData - - -def add_shear_calcs(data): - ''' - method to calculate thermodynamic parameters from dropsonde data - - Input - ----- - Dictionary of sounding data. - Uses calculations from thermocalcs.py - - Output - ------ - multiple arrays containing thermodynamic information - - ''' - # pulls data from the main data dictionary(Needs updating) - - T = data['temperature'] - Td = data['dewpoint'] - p = data['presssure'] - RH = data['relative_humidity'] - uwind = data['u_component'] - vwind = data['v_component'] - height = data['height'] - - mask = h.mask - uwind = u[~mask] - vwind = v[~mask] - height = h[~mask] - - # Runs the shear calcs routines from shearcac.py - SHEAR1KM = sC._VertShear_Sfc_to_1km(h, u, v) - SHEAR3KM = sC._VertShear_Sfc_to_3km(h, u, v) - SHEAR6KM = sC._VertShear_Sfc_to_6km(h, u, v) - BULKSHEAR1km = round(sC._bulkshear_sfc_1km(h, u, v), 2) - BULKSHEAR3km = round(sC._bulkshear_sfc_3km(h, u, v), 2) - BULKSHEAR6km = round(sC._bulkshear_sfc_6km(h, u, v), 2) - - # Builds nice dictionary of shear data - shearCalcData = dict() - shearCalcData['SHEAR1KM'] = _build_dict( - SHEAR1KM, 's^-1', '1km vertical wind shear', '1km wind shear') - shearCalcData['SHEAR3KM'] = _build_dict( - SHEAR3KM, 's^-1', '3km vertical wind shear', '3km wind shear') - shearCalcData['SHEAR6KM'] = _build_dict( - SHEAR6KM, 's^-1', '6km vertical wind shear', '6km wind shear') - shearCalcData['BULKSHEAR1km'] = _build_dict( - BULKSHEAR1km, 'm/s', '1km Bulk wind shear', '1km Bulk shear') - shearCalcData['BULKSHEAR1km'] = _build_dict( - BULKSHEAR3km, 'm/s', '3km Bulk wind shear', '3km Bulk shear') - shearCalcData['BULKSHEAR1km'] = _build_dict( - BULKSHEAR6km, 'm/s', '6km Bulk wind shear', '6km Bulk shear') - return shearCalcData - -#def dry_lift(data): -# -# T = data['temperature'] -# Td = data['dewpoint'] -# p = data['presssure'] -# RH = data['relative_humidity'] -# u = data['u_component'] -# v = data['v_component'] -# h = data['Height'] -# -# t_parcel, p_parcel = tC.dry_lift(T, p, LCLT, LCLP) -# -# ax1.semilogy(t_parcel, p_parcel, 'k--', ms=1) diff --git a/awot/util/thermocalcs.py b/awot/util/thermocalcs.py index f8304bb..b1aa064 100644 --- a/awot/util/thermocalcs.py +++ b/awot/util/thermocalcs.py @@ -1,4 +1,4 @@ -from __future__ import print_function + import numpy as np diff --git a/awot/util/track_distance.py b/awot/util/track_distance.py index 10bba5d..c8dd163 100644 --- a/awot/util/track_distance.py +++ b/awot/util/track_distance.py @@ -25,7 +25,7 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from __future__ import division + from netCDF4 import date2num import numpy as np @@ -103,8 +103,7 @@ def calc_ground_distance(awot, method='great circle', try: lon = awot['longitude']['data'][:] except: - print("WARNING: Cannot find suitable longitude variable in file") - return + _error_missing_var("longitude") else: lon = awot[lon_key]['data'][:] @@ -112,8 +111,7 @@ def calc_ground_distance(awot, method='great circle', try: lat = awot['latitude']['data'][:] except: - print("WARNING: Cannot find suitable latitude variable in file") - return + _error_missing_var("latitude") else: lat = awot[lat_key]['data'][:] @@ -177,8 +175,7 @@ def calc_air_distance(awot, airspeed_key=None, time_key=None, try: airspeed = awot['tas']['data'][:] except: - print("WARNING: Cannot find suitable air speed variable in file") - return + _error_missing_var("air speed") else: airspeed = awot[airspeed_key]['data'][:] @@ -186,8 +183,7 @@ def calc_air_distance(awot, airspeed_key=None, time_key=None, try: timesec = date2num(awot['time']['data'][:], awot['time']['units']) except: - print("WARNING: Cannot find suitable time variable in file") - return + _error_missing_var("time") else: timesec = date2num(awot[time_key]['data'][:], awot[time_key]['units']) time_elapsed = timesec[1:] - timesec[:-1] @@ -337,7 +333,7 @@ class `__init__`, with a default of 20). It may be preferable to use cos_lam_lon) ** 2) if sin_sig == 0: - print("WARNING: Coincident points found") + print("--> WARNING: Coincident points found") return 0 cos_sig = (sin_red1[nn] * sin_red2[nn] + cos_red1[nn] * @@ -382,3 +378,8 @@ class `__init__`, with a default of 20). It may be preferable to use else: dist[nn] = np.nan return dist + + +def _error_missing_var(var): + err = f"--> WARNING: Cannot find suitable {var} variable in file" + raise ValueError(err) diff --git a/awot/util/write_kmz.py b/awot/util/write_kmz.py index b8a6b5a..ff3d8ae 100644 --- a/awot/util/write_kmz.py +++ b/awot/util/write_kmz.py @@ -10,8 +10,9 @@ This present method is proof of concept and is expected to expand over time. """ -from __future__ import absolute_import -from __future__ import print_function + + +from datetime import datetime import matplotlib.pyplot as plt import numpy as np import os @@ -30,7 +31,7 @@ def write_track_kmz(awot, field, lat_name=None, lon_name=None, latrange=None, lonrange=None, cmap=None, track_color='k', track_lw=2.5, file_path=None, file_name=None, - show_legend=True, legend_label=None): + show_legend=True, legend_label=None, verbose=False): """ This method plots geolocated AWOT track data as a filled color Google Earth kmz. @@ -82,10 +83,11 @@ def write_track_kmz(awot, field, lat_name=None, lon_name=None, legend_label : str Label to display in legend. If None, AWOT attempts to build using field dictionary information. + verbose (bool): Print additional information """ plt.close() # mpl seems buggy if multiple windows are left open _method_printout() - print('Writing AWOT track KMZ file:') + print('--> Writing AWOT track KMZ file:') # Check to see if field exists gcommon._check_field(awot, field) @@ -140,8 +142,9 @@ def write_track_kmz(awot, field, lat_name=None, lon_name=None, if cmap is None: cmap = plt.get_cmap() - print(lonrange) - print(latrange) + if verbose: + print(f"--> Lonrange: {lonrange}, Latrange: {latrange}") + fig, ax = gearth_fig(np.min(lonrange), np.min(latrange), np.max(lonrange), np.max(latrange)) @@ -175,7 +178,7 @@ def write_track_kmz(awot, field, lat_name=None, lon_name=None, kmzfile=longname, times=times) os.remove('overlay.png') - print('Google Earth image saved to: %s' % longname) + print(f"--> Google Earth image saved to: {longname}") _method_printout() return @@ -234,7 +237,7 @@ def write_line_kml(awot, field, lat_name=None, lon_name=None, """ plt.close() # mpl seems buggy if multiple windows are left open _method_printout() - print('Writing AWOT track KML file:') + print('--> Writing AWOT track KML file:') # Check to see if field exists gcommon._check_field(awot, field) @@ -290,7 +293,7 @@ def write_line_kml(awot, field, lat_name=None, lon_name=None, # Now we convert to KMZ kml = simplekml.Kml() linestr = kml.newlinestring(name=line_name) - linestr.coords = zip(lond, latd) + linestr.coords = list(zip(lond, latd)) # Set properties according to keywords if lw is not None: @@ -303,7 +306,7 @@ def write_line_kml(awot, field, lat_name=None, lon_name=None, # Save the file kml.save(longname) - print('KML file saved to: %s' % longname) + print(f"--> KML file saved to: {longname}") _method_printout() return @@ -386,7 +389,7 @@ def write_poly_kml(name="Polygon", innerboundary=None, outerboundary=None, def write_kmz(fig, ax, plot, lonrange, latrange, times, file_path=None, file_name=None, - show_legend=True, legend_label=None): + show_legend=True, legend_label=None, verbose=False): """ This method plots geolocated AWOT track data as a filled color Google Earth kmz. @@ -410,15 +413,17 @@ def write_kmz(fig, ax, plot, lonrange, latrange, times, legend_label : str Label to display in legend. If None, AWOT attempts to build using field dictionary information. + verbose (bool): Print further information """ plt.close() # mpl seems buggy if multiple windows are left open _method_printout() - print('Writing AWOT track KMZ file:') + print('--> Writing AWOT track KMZ file:') longname = os.path.join(file_path, file_name) - print(lonrange) - print(latrange) + if verbose: + print(f"--> Lonrange: {lonrange}, Latrange: {latrange}") + ax.set_axis_off() fig.savefig('overlay.png', transparent=True, format='png') @@ -444,7 +449,7 @@ def write_kmz(fig, ax, plot, lonrange, latrange, times, kmzfile=longname, times=times) os.remove('overlay.png') - print('Google Earth image saved to: %s' % longname) + print(f"--> Google Earth image saved to: {longname}") _method_printout() return @@ -479,7 +484,7 @@ def _filter_bad_geolocations(lats, lons, data, time): condition = np.logical_or(cond1, cond2) indices = np.where(condition) if np.shape(indices)[1] > 0: - print("Removing bad data") + print("--> Removing bad data") data = np.delete(data, indices[0], axis=0) lons = np.delete(lons, indices[0], axis=0) lats = np.delete(lats, indices[0], axis=0)