def run_post(conf): FIXdir = conf.get("DEFAULT","FIXbwrf") sfc_switch = conf.getint("post","plot_sfc") cloud_switch = conf.getint("post","plot_clouds") sfcdiags_switch = conf.getint("post","plot_sfcdiags") xcdiags_switch = conf.getint("post","plot_xcdiags") switch_700mb = conf.getint("post","plot_700mb") switch_500mb = conf.getint("post","plot_500mb") switch_300mb = conf.getint("post","plot_300mb") # sfc_switch = 0 # cloud_switch = 0 # sfcdiags_switch = 0 # xcdiags_switch = 0 # switch_700mb = 0 # switch_500mb = 0 # switch_300mb = 0 blat = conf.getfloat("post","blat") blon = conf.getfloat("post","blon") dx = conf.getfloat("wrf","dx") POSTwork = conf.get("DEFAULT","POSTwork") os.chdir(POSTwork) file_wrf = glob.glob("wrfout*")[0] print("Processing "+file_wrf) init_time = file_wrf[11:21]+" "+file_wrf[22:24]+"Z" # Download the states and coastlines states = cfeature.NaturalEarthFeature(category='cultural', scale='50m', facecolor='none', name='admin_1_states_provinces_shp') # Get counties. reader = shpreader.Reader(FIXdir+'/shapefiles/countyp010g.shp') counties = list(reader.geometries()) COUNTIES = cfeature.ShapelyFeature(counties, crs.PlateCarree()) # Open the NetCDF file ncfile = Dataset(file_wrf) # Get the times and convert to datetimes times = getvar(ncfile, "Times", timeidx=ALL_TIMES, meta=False) dtimes = [datetime.strptime(str(atime), '%Y-%m-%dT%H:%M:%S.000000000') for atime in times] numTimes = len(times) # Reflectivity ref = getvar(ncfile, "REFL_10CM", timeidx=ALL_TIMES) ref[0,0,0,0]=-21.0 # hack to plot a blank contour plot at the initial time # Get upper-air quantities. p = getvar(ncfile, "pressure", timeidx=ALL_TIMES) z = getvar(ncfile, "z", units="dm", timeidx=ALL_TIMES) u, v = getvar(ncfile, "uvmet", units="kt", timeidx=ALL_TIMES, meta=False) w = getvar(ncfile, "wa", units="m s-1", timeidx=ALL_TIMES)*100. # m/s to cm/s rh = getvar(ncfile, "rh", timeidx=ALL_TIMES) wspeed = (u**2.0+v**2.0)**0.5 tc = getvar(ncfile, "tc", timeidx=ALL_TIMES) dewT = getvar(ncfile, "td", units="degC", timeidx=ALL_TIMES) cloudfrac = getvar(ncfile, "cloudfrac", low_thresh=20., mid_thresh=955., high_thresh=4500., timeidx=ALL_TIMES) total_cloudfrac=np.max(cloudfrac,axis=0) low_cloudfrac = cloudfrac[0,:,:,:] mid_cloudfrac = cloudfrac[1,:,:,:] high_cloudfrac = cloudfrac[2,:,:,:] # Interpolate z_500 = smooth2d(interplevel(z, p, 500), 3) tc_500 = smooth2d(interplevel(tc, p, 500), 3) u_500 = interplevel(u, p, 500) v_500 = interplevel(v, p, 500) wspeed_500 = interplevel(wspeed, p, 500) z_300 = smooth2d(interplevel(z, p, 300), 3) u_300 = interplevel(u, p, 300) v_300 = interplevel(v, p, 300) z_700 = interplevel(z, p, 700) tc_700 = interplevel(tc, p, 700) u_700 = interplevel(u, p, 700) v_700 = interplevel(v, p, 700) w_700 = interplevel(w, p, 700) rh_700 = interplevel(rh, p, 700) if switch_700mb == 1: # Interpolate over NaNs. for itime in range(numTimes): z_700[itime,:,:] = interpnan(z_700[itime,:,:]) tc_700[itime,:,:] = interpnan(tc_700[itime,:,:]) rh_700[itime,:,:] = interpnan(rh_700[itime,:,:]) u_700[itime,:,:] = interpnan(u_700[itime,:,:]) v_700[itime,:,:] = interpnan(v_700[itime,:,:]) w_700[itime,:,:] = interpnan(w_700[itime,:,:]) z_700 = smooth2d(z_700, 3) tc_700 = smooth2d(tc_700, 3) w_700 = smooth2d(w_700, 3) div_300 = smooth2d(divergence([u_300*0.514444, v_300*0.514444], dx), 3) # kt to m/s # Get the sea level pressure slp = getvar(ncfile, "slp", timeidx=ALL_TIMES) # Get the wet bulb temperature twb = getvar(ncfile, "twb", units="degC", timeidx=ALL_TIMES) slp_levels=np.arange(980,1040,2) z_levels=np.arange(504,620,3) z_levels_300=np.arange(804,996,6) z_levels_700=np.arange(285,351,3) t2_levels=np.arange(-20,125,5) tc_levels=np.arange(-40,30,2) wspeed_levels=np.arange(40,150,10) ref_levels=np.arange(-20,60,5) rh_levels=np.arange(70,105,5) cldfrac_levels=np.arange(0.,1.1,0.1) twb_levels=np.arange(0,1,1) wup_levels=np.arange(5,55,10) wdown_levels=np.arange(-55,-5,10) div_levels=np.arange(-110,120,10) # Get the 10-m u and v wind components. u_10m, v_10m = getvar(ncfile, "uvmet10", units="kt", timeidx=ALL_TIMES) wind_10m = (u_10m**2.0+v_10m**2.0)**0.5 # Smooth the sea level pressure since it tends to be noisy near the mountains smooth_slp = smooth2d(slp, 3) twb=twb[:,0,:,:] # lowest model level twb=smooth2d(twb,3) # Get the latitude and longitude points lats, lons = latlon_coords(slp) # Get the cartopy mapping object cart_proj = get_cartopy(slp) if sfcdiags_switch == 1: bx, by = ll_to_xy(ncfile, blat, blon, meta=False, as_int=True) # Create a figure fig = plt.figure(figsize=(12,9)) fileout = "precip.png" # should be variable ACSNOW # accumulated_snow = getvar(ncfile, "SNOWNC", timeidx=ALL_TIMES) liq_equiv = (getvar(ncfile, "RAINC", timeidx=ALL_TIMES) + getvar(ncfile, "RAINNC", timeidx=ALL_TIMES))/25.4 # mm to inches liq_equiv_bdu = liq_equiv[:,by,bx] prate = np.zeros(numTimes) for itime in range(numTimes): if itime > 0 and itime < numTimes-1: prate[itime] = 0.5*((liq_equiv_bdu[itime+1]+liq_equiv_bdu[itime]) - (liq_equiv_bdu[itime-1]+liq_equiv_bdu[itime])) elif itime == 0: prate[0] = liq_equiv_bdu[0] else: prate[numTimes-1] = liq_equiv_bdu[numTimes-1]-liq_equiv_bdu[numTimes-2] plt.bar(times, prate, width=0.0425, color="black") plt.plot(times, liq_equiv_bdu, color="blue") plt.xlim(min(times), max(times)) plt.title("Forecast Precipitation: Boulder, CO") plt.xlabel("Time (UTC)") plt.ylabel("Running total (line) and rate (bars, inches of liquid)") plt.grid(b=True, which="major", axis="both", color="gray", linestyle="--") fig.savefig("sfcdiags/"+fileout,bbox_inches='tight') plt.close(fig) # Create a figure fig, ax1 = plt.subplots(figsize=(12,9)) fileout = "t2m_td2m_wind10m_prate.png" t2m = 1.8*(getvar(ncfile, "T2", timeidx=ALL_TIMES)-273.15)+32. td2m = getvar(ncfile, "td2", units="degF", timeidx=ALL_TIMES) color = 'tab:blue' ax1.bar(times, prate, width=0.0425, color="blue") ax1.plot(times, liq_equiv_bdu, color="blue") ax1.set_ylim(0.,max([max(prate)/0.1+0.01,max(liq_equiv_bdu)+0.1])) ax1.set_xlabel("Time (UTC)") ax1.set_ylabel("Precipitation liquid amount (in) and rate (bars, in/hr)", color=color) ax1.tick_params(axis="y", labelcolor=color) ax1.grid(b=True, which="major", axis="x", color="gray", linestyle="--") ax2=ax1.twinx() ax2.plot(times, wind_10m[:,by,bx], color="black") ax2.barbs(times, wind_10m[:,by,bx], u_10m[:,by,bx], v_10m[:,by,bx]) ax2.plot(times, t2m[:,by,bx], color="red") ax2.plot(times, td2m[:,by,bx], color="green") ax2.set_xlim(min(times), max(times)) plt.title("Forecast near-surface variables: Boulder, CO") ax2.set_ylabel("2-m T (red) and 2-m Td (green, degF), 10-m wind (black, kt)") ax2.grid(b=True, which="major", axis="y", color="gray", linestyle="--") fig.tight_layout() fig.savefig("sfcdiags/"+fileout,bbox_inches='tight') plt.close(fig) if xcdiags_switch == 1: zinterp = np.arange(550, 880, 10) u_xc = vertcross(u, p, levels=zinterp, wrfin=ncfile, stagger='u', pivot_point=CoordPair(lat=blat,lon=blon), angle=90., meta=False) w_xc = vertcross(w, p, levels=zinterp, wrfin=ncfile, stagger='u', pivot_point=CoordPair(lat=blat,lon=blon), angle=90., meta=False) tc_xc = vertcross(tc, p, levels=zinterp, wrfin=ncfile, stagger='u', pivot_point=CoordPair(lat=blat,lon=blon), angle=90., meta=False) rh_xc = vertcross(rh, p, levels=zinterp, wrfin=ncfile, stagger='u', pivot_point=CoordPair(lat=blat,lon=blon), angle=90., meta=False) nx = np.shape(u_xc)[-1] xinterp = np.arange(0,nx,1) bx, by = ll_to_xy(ncfile, blat, blon, meta=False, as_int=True) for itime in range(numTimes): # Create a figure fig = plt.figure(figsize=(12,9)) fileout = "mtnwave_xc"+str(itime).zfill(2)+".png" ax=plt.gca() ax.set_facecolor("black") plt.contourf(xinterp, zinterp, u_xc[itime,:,:], cmap=get_cmap("seismic"), levels=np.arange(-50,55,5)) plt.colorbar(shrink=.62) w_contour = plt.contour(xinterp, zinterp, w_xc[itime,:,:], "--", levels=np.arange(-120,-20,20),colors="black") plt.clabel(w_contour, inline=1, fontsize=10, fmt="%i") w_contour = plt.contour(xinterp, zinterp, w_xc[itime,:,:], levels=np.arange(20,120,20),colors="black") plt.clabel(w_contour, inline=1, fontsize=10, fmt="%i") t_contour = plt.contour(xinterp, zinterp, tc_xc[itime,:,:], levels=[-20,-10,0], colors="yellow") plt.clabel(t_contour, inline=1, fontsize=10, fmt="%i") # Add location of Boulder to plot. plt.scatter(bx,np.max(zinterp),c='r',marker='+') plt.ylim([np.max(zinterp),np.min(zinterp)]) # plt.yscale("log") # plt.xticks([np.arange(900,475,-25)], ["900", "875", # "850", "825", "800", "775", "750", "725", "700", "675", "650", # "625", "600", "575", "550", "525", "500"]) plt_time=str(dtimes[itime]) plt_time=plt_time[0:13] plt.title(plt_time+"Z fhr "+str(itime).zfill(2)+": zonal wind (fill, kt), temp (yellow lines, degC), VV (black lines, cm/s)") fig.savefig("xcdiags/"+fileout,bbox_inches='tight') plt.close(fig) os.system("convert -delay 90 -dispose background xcdiags/mtnwave_xc*.png -loop 0 xcdiags/mtnwave_xc.gif") for itime in range(numTimes): # Create a figure fig = plt.figure(figsize=(12,9)) fileout = "mtnwave_xc_rh"+str(itime).zfill(2)+".png" ax=plt.gca() ax.set_facecolor("black") plt.contourf(xinterp, zinterp, rh_xc[itime,:,:], cmap=get_cmap("Greens"), levels=rh_levels, extend='both') plt.colorbar(shrink=.62) skip=2 plt.quiver(xinterp[::skip], zinterp[::skip], u_xc[itime,::skip,::skip], w_xc[itime,::skip,::skip]/2., scale=500, headwidth=3, color='black', pivot='middle') t_contour = plt.contour(xinterp, zinterp, tc_xc[itime,:,:], levels=[-20,-10,0], colors="yellow") plt.clabel(t_contour, inline=1, fontsize=10, fmt="%i") # Add location of Boulder to plot. plt.scatter(bx,np.max(zinterp),c='r',marker='+') plt.ylim([np.max(zinterp),np.min(zinterp)]) plt_time=str(dtimes[itime]) plt_time=plt_time[0:13] plt.title(plt_time+"Z fhr "+str(itime).zfill(2)+": rh (fill), temp (yellow lines, degC), wind (arrows)") fig.savefig("xcdiags_rh/"+fileout,bbox_inches='tight') plt.close(fig) os.system("convert -delay 90 -dispose background xcdiags_rh/mtnwave_xc_rh*.png -loop 0 xcdiags_rh/mtnwave_xc_rh.gif") zinterp = np.arange(150, 900, 25) rh_xc = vertcross(rh, p, levels=zinterp, wrfin=ncfile, stagger='u', pivot_point=CoordPair(lat=blat,lon=blon), angle=90., meta=False) tc_xc = vertcross(tc, p, levels=zinterp, wrfin=ncfile, stagger='u', pivot_point=CoordPair(lat=blat,lon=blon), angle=90., meta=False) nx = np.shape(u_xc)[-1] xinterp = np.arange(0,nx,1) bx, by = ll_to_xy(ncfile, blat, blon, meta=False, as_int=True) for itime in range(numTimes): # Create a figure fig = plt.figure(figsize=(12,9)) fileout = "mtnwave_xc_rh_big"+str(itime).zfill(2)+".png" ax=plt.gca() ax.set_facecolor("black") plt.contourf(xinterp, zinterp, rh_xc[itime,:,:], cmap=get_cmap("Greens"), levels=rh_levels, extend='both') plt.colorbar(shrink=.62) t_contour = plt.contour(xinterp, zinterp, tc_xc[itime,:,:], levels=[-20,-10,0], colors="yellow") plt.clabel(t_contour, inline=1, fontsize=10, fmt="%i") # Add location of Boulder to plot. plt.scatter(bx,np.max(zinterp),c='r',marker='+') plt.ylim([np.max(zinterp),np.min(zinterp)]) plt_time=str(dtimes[itime]) plt_time=plt_time[0:13] plt.title(plt_time+"Z fhr "+str(itime).zfill(2)+": rh (fill), temp (yellow lines, degC)") fig.savefig("xcdiags_rh_big/"+fileout,bbox_inches='tight') plt.close(fig) os.system("convert -delay 90 -dispose background xcdiags_rh_big/mtnwave_xc_rh_big*.png -loop 0 xcdiags_rh_big/mtnwave_xc_rh_big.gif") if sfc_switch == 1: bx, by = ll_to_xy(ncfile, blat, blon, meta=False, as_int=True) for itime in range(numTimes): ps = p[itime,:,by,bx] ps_temp = ps T = tc[itime,:,by,bx] Td = dewT[itime,:,by,bx] us = u[itime,:,by,bx] vs = v[itime,:,by,bx] ps = ps[ps_temp >= 100.] T = T[ps_temp >= 100.] Td = Td[ps_temp >= 100.] us = us[ps_temp >= 100.] vs = vs[ps_temp >= 100.] fig = plt.figure(figsize=(9, 9)) skew = SkewT(fig, rotation=45) fileout = "sounding"+str(itime).zfill(2)+".png" # Plot the data using normal plotting functions, in this case using # log scaling in Y, as dictated by the typical meteorological plot. skew.plot(ps, T, 'r') skew.plot(ps, Td, 'g') skew.plot_barbs(ps, us, vs) skew.ax.set_ylim(1000, 100) skew.ax.set_xlim(-40, 60) # Calculate LCL height and plot as black dot. Because `p`'s first value is # ~1000 mb and its last value is ~250 mb, the `0` index is selected for # `p`, `T`, and `Td` to lift the parcel from the surface. If `p` was inverted, # i.e. start from low value, 250 mb, to a high value, 1000 mb, the `-1` index # should be selected. lcl_pressure, lcl_temperature = mpcalc.lcl(ps[0], T[0], Td[0]) skew.plot(lcl_pressure, lcl_temperature, 'ko', markerfacecolor='black') # Calculate full parcel profile and add to plot as black line prof = mpcalc.parcel_profile(ps, T[0], Td[0]).to('degC') skew.plot(ps, prof, 'k', linewidth=2) # Shade areas of CAPE and CIN # skew.shade_cin(ps, T, prof, Td) [cape, cin] = mpcalc.cape_cin(ps, T, Td, prof) # skew.shade_cape(ps, T, prof) # An example of a slanted line at constant T -- in this case the 0 # isotherm skew.ax.axvline(0, color='c', linestyle='--', linewidth=2) # Add the relevant special lines skew.plot_dry_adiabats() skew.plot_moist_adiabats() skew.plot_mixing_lines() plt_time=str(dtimes[itime]) plt_time=plt_time[0:13] plt.title(plt_time+"Z fhr "+str(itime).zfill(2)+": Boulder, CO, CAPE: "+str(round(cape.magnitude))+" J/kg CIN: "+str(round(cin.magnitude))+" J/kg") fig.savefig("sounding/"+fileout,bbox_inches='tight') plt.close(fig) os.system("convert -delay 90 -dispose background sounding/sounding*.png -loop 0 sounding/sounding.gif") for itime in range(numTimes): # Create a figure fig = plt.figure(figsize=(12,9)) fileout = "sfc_fhr"+str(itime).zfill(2)+".png" # Set the GeoAxes to the projection used by WRF ax = plt.axes(projection=cart_proj) # Add the states and coastlines ax.add_feature(states, linewidth=0.8, edgecolor='gray') ax.add_feature(COUNTIES, linewidth=0.4, facecolor='none', edgecolor='gray') ax.coastlines('50m', linewidth=0.8) # Reflectivity at the lowest model level. plt.contourf(to_np(lons), to_np(lats), to_np(ref[itime,0,:,:]), transform=crs.PlateCarree(), cmap=get_cmap("jet"), levels=ref_levels) # Add a color bar plt.colorbar(ax=ax, shrink=.62) # Contour the wetbulb temperature at 0 degC. c_p = plt.contour(to_np(lons), to_np(lats), to_np(twb[itime,:,:]), transform=crs.PlateCarree(), colors="red", levels=twb_levels) plt.clabel(c_p, inline=1, fontsize=10, fmt="%i") # Make the contour outlines and filled contours for the smoothed sea level pressure. c_p = plt.contour(to_np(lons), to_np(lats), to_np(smooth_slp[itime,:,:]), transform=crs.PlateCarree(), colors="black", levels=slp_levels) plt.clabel(c_p, inline=1, fontsize=10, fmt="%i") # Add location of Boulder to plot. plt.scatter(blon,blat,c='r',marker='+',transform=crs.PlateCarree()) # Add the 10-m wind barbs, only plotting every other data point. skip=2 plt.barbs(to_np(lons[::skip,::skip]), to_np(lats[::skip,::skip]), to_np(u_10m[itime, ::skip, ::skip]), to_np(v_10m[itime, ::skip, ::skip]), transform=crs.PlateCarree(), length=5.25, linewidth=0.5) # Set the map limits. ax.set_xlim(cartopy_xlim(smooth_slp)) ax.set_ylim(cartopy_ylim(smooth_slp)) plt_time=str(dtimes[itime]) plt_time=plt_time[0:13] plt.title(plt_time+"Z fhr "+str(itime).zfill(2)+": SLP (fill, hPa), 10-m wind (barbs, kt), LML Ref (fill, dBZ), LML WBT (red, degC)") fig.savefig("sfc/"+fileout,bbox_inches='tight') plt.close(fig) os.system("convert -delay 90 -dispose background sfc/sfc*.png -loop 0 sfc/sfc.gif") for itime in range(numTimes): # Create a figure fig = plt.figure(figsize=(12,9)) fileout = "sfc_temp_fhr"+str(itime).zfill(2)+".png" # Set the GeoAxes to the projection used by WRF ax = plt.axes(projection=cart_proj) # Add the states and coastlines ax.add_feature(states, linewidth=0.8, edgecolor='gray') ax.add_feature(COUNTIES, linewidth=0.4, facecolor='none', edgecolor='gray') ax.coastlines('50m', linewidth=0.8) # 2-m air temperature t2 = 1.8*(getvar(ncfile, "T2", timeidx=ALL_TIMES) - 273.15)+32. plt.contourf(to_np(lons), to_np(lats), to_np(t2[itime,:,:]), transform=crs.PlateCarree(), cmap=get_cmap("jet"), levels=t2_levels) # Add a color bar plt.colorbar(ax=ax, shrink=.62) # Make the contour outlines and filled contours for the smoothed sea level pressure. c_p = plt.contour(to_np(lons), to_np(lats), to_np(smooth_slp[itime,:,:]), transform=crs.PlateCarree(), colors="black", levels=slp_levels) plt.clabel(c_p, inline=1, fontsize=10, fmt="%i") # Add location of Boulder to plot. plt.scatter(blon,blat,c='r',marker='+',transform=crs.PlateCarree()) # Add the 10-m wind barbs, only plotting every other data point. skip=2 plt.barbs(to_np(lons[::skip,::skip]), to_np(lats[::skip,::skip]), to_np(u_10m[itime, ::skip, ::skip]), to_np(v_10m[itime, ::skip, ::skip]), transform=crs.PlateCarree(), length=5.25, linewidth=0.5) # Set the map limits. ax.set_xlim(cartopy_xlim(smooth_slp)) ax.set_ylim(cartopy_ylim(smooth_slp)) plt_time=str(dtimes[itime]) plt_time=plt_time[0:13] plt.title(plt_time+"Z fhr "+str(itime).zfill(2)+": SLP (contours, hPa), 10-m wind (barbs, kt), 2-m T (fill, degF)") fig.savefig("sfc_temp/"+fileout,bbox_inches='tight') plt.close(fig) os.system("convert -delay 90 -dispose background sfc_temp/sfc_temp*.png -loop 0 sfc_temp/sfc_temp.gif") if cloud_switch == 1: for itime in range(numTimes): # Create a figure fig = plt.figure(figsize=(12,9)) fileout = "cldfrac_fhr"+str(itime).zfill(2)+".png" # Set the GeoAxes to the projection used by WRF ax = plt.axes(projection=cart_proj) # Add the states and coastlines ax.add_feature(states, linewidth=0.8, edgecolor='gray') ax.add_feature(COUNTIES, linewidth=0.4, facecolor='none', edgecolor='gray') ax.coastlines('50m', linewidth=0.8) # Compute and plot the total cloud fraction. plt.contourf(to_np(lons), to_np(lats), to_np(total_cloudfrac[itime,:,:]), transform=crs.PlateCarree(), cmap=get_cmap("Greys"), levels=cldfrac_levels, extend='both') # Add a color bar plt.colorbar(ax=ax, shrink=.62) # Add location of Boulder to plot. plt.scatter(blon,blat,c='r',marker='+',transform=crs.PlateCarree()) # Set the map limits. ax.set_xlim(cartopy_xlim(cloudfrac)) ax.set_ylim(cartopy_ylim(cloudfrac)) plt_time=str(dtimes[itime]) plt_time=plt_time[0:13] plt.title(plt_time+"Z fhr "+str(itime).zfill(2)+": Total cloud fraction (fill)") fig.savefig("cldfrac/"+fileout,bbox_inches='tight') plt.close(fig) os.system("convert -delay 90 -dispose background cldfrac/cldfrac*.png -loop 0 cldfrac/cldfrac.gif") for itime in range(numTimes): # Create a figure fig = plt.figure(figsize=(12,9)) fileout = "low_cldfrac_fhr"+str(itime).zfill(2)+".png" # Set the GeoAxes to the projection used by WRF ax = plt.axes(projection=cart_proj) # Add the states and coastlines ax.add_feature(states, linewidth=0.8, edgecolor='gray') ax.add_feature(COUNTIES, linewidth=0.4, facecolor='none', edgecolor='gray') ax.coastlines('50m', linewidth=0.8) # Compute and plot the cloud fraction. plt.contourf(to_np(lons), to_np(lats), to_np(low_cloudfrac[itime,:,:]), transform=crs.PlateCarree(), cmap=get_cmap("Greys"), levels=cldfrac_levels, extend='both') # Add a color bar plt.colorbar(ax=ax, shrink=.62) # Add location of Boulder to plot. plt.scatter(blon,blat,c='r',marker='+',transform=crs.PlateCarree()) # Set the map limits. ax.set_xlim(cartopy_xlim(cloudfrac)) ax.set_ylim(cartopy_ylim(cloudfrac)) plt_time=str(dtimes[itime]) plt_time=plt_time[0:13] plt.title(plt_time+"Z fhr "+str(itime).zfill(2)+": Low cloud fraction (fill)") fig.savefig("low_cldfrac/"+fileout,bbox_inches='tight') plt.close(fig) os.system("convert -delay 90 -dispose background low_cldfrac/low_cldfrac*.png -loop 0 low_cldfrac/low_cldfrac.gif") for itime in range(numTimes): # Create a figure fig = plt.figure(figsize=(12,9)) fileout = "mid_cldfrac_fhr"+str(itime).zfill(2)+".png" # Set the GeoAxes to the projection used by WRF ax = plt.axes(projection=cart_proj) # Add the states and coastlines ax.add_feature(states, linewidth=0.8, edgecolor='gray') ax.add_feature(COUNTIES, linewidth=0.4, facecolor='none', edgecolor='gray') ax.coastlines('50m', linewidth=0.8) # Compute and plot the cloud fraction. plt.contourf(to_np(lons), to_np(lats), to_np(mid_cloudfrac[itime,:,:]), transform=crs.PlateCarree(), cmap=get_cmap("Greys"), levels=cldfrac_levels, extend='both') # Add a color bar plt.colorbar(ax=ax, shrink=.62) # Add location of Boulder to plot. plt.scatter(blon,blat,c='r',marker='+',transform=crs.PlateCarree()) # Set the map limits. ax.set_xlim(cartopy_xlim(cloudfrac)) ax.set_ylim(cartopy_ylim(cloudfrac)) plt_time=str(dtimes[itime]) plt_time=plt_time[0:13] plt.title(plt_time+"Z fhr "+str(itime).zfill(2)+": Mid cloud fraction (fill)") fig.savefig("mid_cldfrac/"+fileout,bbox_inches='tight') plt.close(fig) os.system("convert -delay 90 -dispose background mid_cldfrac/mid_cldfrac*.png -loop 0 mid_cldfrac/mid_cldfrac.gif") for itime in range(numTimes): # Create a figure fig = plt.figure(figsize=(12,9)) fileout = "high_cldfrac_fhr"+str(itime).zfill(2)+".png" # Set the GeoAxes to the projection used by WRF ax = plt.axes(projection=cart_proj) # Add the states and coastlines ax.add_feature(states, linewidth=0.8, edgecolor='gray') ax.add_feature(COUNTIES, linewidth=0.4, facecolor='none', edgecolor='gray') ax.coastlines('50m', linewidth=0.8) # Compute and plot the cloud fraction. plt.contourf(to_np(lons), to_np(lats), to_np(high_cloudfrac[itime,:,:]), transform=crs.PlateCarree(), cmap=get_cmap("Greys"), levels=cldfrac_levels, extend='both') # Add a color bar plt.colorbar(ax=ax, shrink=.62) # Add location of Boulder to plot. plt.scatter(blon,blat,c='r',marker='+',transform=crs.PlateCarree()) # Set the map limits. ax.set_xlim(cartopy_xlim(cloudfrac)) ax.set_ylim(cartopy_ylim(cloudfrac)) plt_time=str(dtimes[itime]) plt_time=plt_time[0:13] plt.title(plt_time+"Z fhr "+str(itime).zfill(2)+": High cloud fraction (fill)") fig.savefig("high_cldfrac/"+fileout,bbox_inches='tight') plt.close(fig) os.system("convert -delay 90 -dispose background high_cldfrac/high_cldfrac*.png -loop 0 high_cldfrac/high_cldfrac.gif") if switch_500mb == 1: for itime in range(numTimes): # Create a figure fig = plt.figure(figsize=(12,9)) fileout = "500mb_fhr"+str(itime).zfill(2)+".png" # Set the GeoAxes to the projection used by WRF ax = plt.axes(projection=cart_proj) # Add the states and coastlines ax.add_feature(states, linewidth=0.8, edgecolor='gray') ax.add_feature(COUNTIES, linewidth=0.4, facecolor='none', edgecolor='gray') ax.coastlines('50m', linewidth=0.8) # wind color fill if np.max(wspeed_500[itime,:,:]) > np.min(wspeed_levels): plt.contourf(to_np(lons), to_np(lats), to_np(wspeed_500[itime,:,:]), transform=crs.PlateCarree(), cmap=get_cmap("rainbow"), levels=wspeed_levels) # Make the 500 mb height contours. c_z = plt.contour(to_np(lons), to_np(lats), to_np(z_500[itime,:,:]), transform=crs.PlateCarree(), colors="black", levels=z_levels) plt.clabel(c_z, inline=1, fontsize=10, fmt="%i") # Make the 500 mb temp contours. c_t = plt.contour(to_np(lons), to_np(lats), to_np(tc_500[itime,:,:]), transform=crs.PlateCarree(), colors="red", levels=tc_levels) plt.clabel(c_t, inline=1, fontsize=10, fontcolor="red", fmt="%i") # Add a color bar # plt.colorbar(ax=ax, shrink=.62) # Add location of Boulder to plot. plt.scatter(blon,blat,c='r',marker='+',transform=crs.PlateCarree()) # Add the 500mb wind barbs, only plotting every other data point. skip=3 plt.barbs(to_np(lons[::skip,::skip]), to_np(lats[::skip,::skip]), to_np(u_500[itime, ::skip, ::skip]), to_np(v_500[itime, ::skip, ::skip]), transform=crs.PlateCarree(), length=5.25, linewidth=0.5) # Set the map limits. ax.set_xlim(cartopy_xlim(z_500)) ax.set_ylim(cartopy_ylim(z_500)) plt_time=str(dtimes[itime]) plt_time=plt_time[0:13] plt.title(plt_time+"Z fhr "+str(itime).zfill(2)+": 500-mb height (black, dm), temp (red, degC), and wind (fill/barbs, kt)") fig.savefig("500mb/"+fileout,bbox_inches='tight') plt.close(fig) os.system("convert -delay 90 -dispose background 500mb/500mb*.png -loop 0 500mb/500mb.gif") if switch_700mb == 1: for itime in range(numTimes): # Create a figure fig = plt.figure(figsize=(12,9)) fileout = "700mb_fhr"+str(itime).zfill(2)+".png" # Set the GeoAxes to the projection used by WRF ax = plt.axes(projection=cart_proj) # Add the states and coastlines ax.add_feature(states, linewidth=0.8, edgecolor='gray') ax.add_feature(COUNTIES, linewidth=0.4, facecolor='none', edgecolor='gray') ax.coastlines('50m', linewidth=0.8) # rh color fill plt.contourf(to_np(lons), to_np(lats), to_np(rh_700[itime,:,:]), transform=crs.PlateCarree(), cmap=get_cmap("Greens"), levels=rh_levels, extend='both') # Add a color bar plt.colorbar(ax=ax, shrink=.62) # Make the 700 mb height contours. c_z = plt.contour(to_np(lons), to_np(lats), to_np(z_700[itime,:,:]), transform=crs.PlateCarree(), colors="black", levels=z_levels_700) plt.clabel(c_z, inline=1, fontsize=10, fmt="%i") # Make the 700 mb temp contours. c_t = plt.contour(to_np(lons), to_np(lats), to_np(tc_700[itime,:,:]), transform=crs.PlateCarree(), colors="red", levels=tc_levels) plt.clabel(c_t, inline=1, fontsize=10, fontcolor="red", fmt="%i") # Make the 700 mb VV contours. c_d = plt.contour(to_np(lons), to_np(lats), to_np(w_700[itime,:,:]), transform=crs.PlateCarree(), colors="magenta", levels=wup_levels, linewidths=0.9) plt.clabel(c_d, inline=1, fontsize=10, fontcolor="magenta", fmt="%i") c_c = plt.contour(to_np(lons), to_np(lats), to_np(w_700[itime,:,:]), transform=crs.PlateCarree(), colors="blue", levels=wdown_levels, linewidths=0.9) plt.clabel(c_c, inline=1, fontsize=10, fontcolor="blue", fmt="%i") # Add location of Boulder to plot. plt.scatter(blon,blat,c='r',marker='+',transform=crs.PlateCarree()) # Add the 700mb wind barbs, only plotting every other data point. skip=3 plt.barbs(to_np(lons[::skip,::skip]), to_np(lats[::skip,::skip]), to_np(u_700[itime, ::skip, ::skip]), to_np(v_700[itime, ::skip, ::skip]), transform=crs.PlateCarree(), length=5.25, linewidth=0.5) # Set the map limits. ax.set_xlim(cartopy_xlim(z_700)) ax.set_ylim(cartopy_ylim(z_700)) plt_time=str(dtimes[itime]) plt_time=plt_time[0:13] plt.title(plt_time+"Z fhr "+str(itime).zfill(2)+": 700-mb hgt (black, dm), T (red, degC), wind (barbs, kt), VV (cm/s), rh (fill, %)") fig.savefig("700mb/"+fileout,bbox_inches='tight') plt.close(fig) os.system("convert -delay 90 -dispose background 700mb/700mb*.png -loop 0 700mb/700mb.gif") if switch_300mb == 1: for itime in range(numTimes): # Create a figure fig = plt.figure(figsize=(12,9)) fileout = "300mb_fhr"+str(itime).zfill(2)+".png" # Set the GeoAxes to the projection used by WRF ax = plt.axes(projection=cart_proj) # Add the states and coastlines ax.add_feature(states, linewidth=0.8, edgecolor='gray') ax.add_feature(COUNTIES, linewidth=0.4, facecolor='none', edgecolor='gray') ax.coastlines('50m', linewidth=0.8) # 300 mb divergence plt.contourf(to_np(lons), to_np(lats), to_np(div_300[itime,:,:]), transform=crs.PlateCarree(), cmap=get_cmap("seismic"), levels=div_levels) # Add a color bar plt.colorbar(ax=ax, shrink=.62) # Make the 300 mb height contours. c_z = plt.contour(to_np(lons), to_np(lats), to_np(z_300[itime,:,:]), transform=crs.PlateCarree(), colors="black", levels=z_levels_300) plt.clabel(c_z, inline=1, fontsize=10, fmt="%i") # Make the 300 mb divergence contours. # c_d = plt.contour(to_np(lons), to_np(lats), to_np(div_300[itime,:,:]), transform=crs.PlateCarree(), # colors="red", levels=div_levels, linewidths=0.8) # plt.clabel(c_d, inline=1, fontsize=10, fontcolor="red", fmt="%i") # # c_c = plt.contour(to_np(lons), to_np(lats), to_np(div_300[itime,:,:]), transform=crs.PlateCarree(), # colors="blue", levels=conv_levels, linewidths=0.8) # plt.clabel(c_c, inline=1, fontsize=10, fontcolor="blue", fmt="%i") # Add location of Boulder to plot. plt.scatter(blon,blat,c='r',marker='+',transform=crs.PlateCarree()) # Add the 300mb wind barbs, only plotting every other data point. skip=3 plt.barbs(to_np(lons[::skip,::skip]), to_np(lats[::skip,::skip]), to_np(u_300[itime, ::skip, ::skip]), to_np(v_300[itime, ::skip, ::skip]), transform=crs.PlateCarree(), length=5.25, linewidth=0.5) # Set the map limits. ax.set_xlim(cartopy_xlim(z_300)) ax.set_ylim(cartopy_ylim(z_300)) plt_time=str(dtimes[itime]) plt_time=plt_time[0:13] plt.title(plt_time+"Z fhr "+str(itime).zfill(2)+": 300-mb height (black, dm), wind (barbs, kt), and divergence x 10^5 (red/blue, s^-1)") fig.savefig("300mb/"+fileout,bbox_inches='tight') plt.close(fig) os.system("convert -delay 90 -dispose background 300mb/300mb*.png -loop 0 300mb/300mb.gif")
def calculate_basic_thermo(self): #Enclose in try, except because not every sounding will have a converging parcel path or CAPE. try: #Precipitable Water self.pw = mc.precipitable_water(self.sounding["pres"], self.sounding["dewp"]) #Lifting condensation level self.lcl_pres, self.lcl_temp = mc.lcl(self.sounding["pres"][0], self.sounding["temp"][0], self.sounding["dewp"][0]) #Surface-based CAPE and CIN self.parcel_path = mc.parcel_profile(self.sounding["pres"], self.sounding["temp"][0], self.sounding["dewp"][0]) self.sfc_cape, self.sfc_cin = mc.cape_cin(self.sounding["pres"], self.sounding["temp"], self.sounding["dewp"], self.parcel_path) #Do this when parcel path fails to converge except Exception as e: print("WARNING: No LCL, CAPE, or PW stats because:\n{}.".format(e)) self.parcel_path = numpy.nan self.pw = numpy.nan self.lcl_pres = numpy.nan self.lcl_temp = numpy.nan self.sfc_cape = numpy.nan self.sfc_cin = numpy.nan #Returning return
def get_skewt_vars(p, tc, tdc, pro): """This function processes the dataset values and returns a string element which can be used as a subtitle to replicate the styles of NCL Skew-T Diagrams. Args: p (:class: `pint.quantity.build_quantity_class.<locals>.Quantity`): Pressure level input from dataset tc (:class: `pint.quantity.build_quantity_class.<locals>.Quantity`): Temperature for parcel from dataset tdc (:class: `pint.quantity.build_quantity_class.<locals>.Quantity`): Dew point temperature for parcel from dataset pro (:class: `pint.quantity.build_quantity_class.<locals>.Quantity`): Parcel profile temperature converted to degC Returns: :class: 'str' """ # CAPE cape = mpcalc.cape_cin(p, tc, tdc, pro) cape = cape[0].magnitude # Precipitable Water pwat = mpcalc.precipitable_water(p, tdc) pwat = (pwat.magnitude / 10) * units.cm # Convert mm to cm pwat = pwat.magnitude # Pressure and temperature of lcl lcl = mpcalc.lcl(p[0], tc[0], tdc[0]) plcl = lcl[0].magnitude tlcl = lcl[1].magnitude # Showalter index shox = showalter_index(p, tc, tdc) shox = shox[0].magnitude # Place calculated values in iterable list vals = [plcl, tlcl, shox, pwat, cape] vals = [round(num) for num in vals] # Define variable names for calculated values names = ['Plcl=', 'Tlcl[C]=', 'Shox=', 'Pwat[cm]=', 'Cape[J]='] # Combine the list of values with their corresponding labels lst = list(chain.from_iterable(zip(names, vals))) lst = map(str, lst) # Create one large string for later plotting use joined = ' '.join(lst) return joined
def test_cape_cin(): """Tests the basic CAPE and CIN calculation.""" p = np.array([959., 779.2, 751.3, 724.3, 700., 269.]) * units.mbar temperature = np.array([22.2, 14.6, 12., 9.4, 7., -38.]) * units.celsius dewpoint = np.array([19., -11.2, -10.8, -10.4, -10., -53.2]) * units.celsius parcel_prof = parcel_profile(p, temperature[0], dewpoint[0]) cape, cin = cape_cin(p, temperature, dewpoint, parcel_prof) assert_almost_equal(cape, 58.0368212 * units('joule / kilogram'), 6) assert_almost_equal(cin, -89.8073512 * units('joule / kilogram'), 6)
def test_cape_cin_no_el(): """Tests that CAPE works with no EL.""" p = np.array([959., 779.2, 751.3, 724.3]) * units.mbar temperature = np.array([22.2, 14.6, 12., 9.4]) * units.celsius dewpoint = np.array([19., -11.2, -10.8, -10.4]) * units.celsius parcel_prof = parcel_profile(p, temperature[0], dewpoint[0]).to('degC') cape, cin = cape_cin(p, temperature, dewpoint, parcel_prof) assert_almost_equal(cape, 0.08750805 * units('joule / kilogram'), 6) assert_almost_equal(cin, -89.8073512 * units('joule / kilogram'), 6)
def test_cape_cin_no_lfc(): """Test that CAPE is zero with no LFC.""" p = np.array([959., 779.2, 751.3, 724.3, 700., 269.]) * units.mbar temperature = np.array([22.2, 24.6, 22., 20.4, 18., -10.]) * units.celsius dewpoint = np.array([19., -11.2, -10.8, -10.4, -10., -53.2]) * units.celsius parcel_prof = parcel_profile(p, temperature[0], dewpoint[0]).to('degC') cape, cin = cape_cin(p, temperature, dewpoint, parcel_prof) assert_almost_equal(cape, 0.0 * units('joule / kilogram'), 6) assert_almost_equal(cin, 0.0 * units('joule / kilogram'), 6)
def test_cape_cin_no_el(): """Test that CAPE works with no EL.""" p = np.array([959., 779.2, 751.3, 724.3]) * units.mbar temperature = np.array([22.2, 14.6, 12., 9.4]) * units.celsius dewpoint = np.array([19., -11.2, -10.8, -10.4]) * units.celsius parcel_prof = parcel_profile(p, temperature[0], dewpoint[0]).to('degC') cape, cin = cape_cin(p, temperature, dewpoint, parcel_prof) assert_almost_equal(cape, 0.08750805 * units('joule / kilogram'), 6) assert_almost_equal(cin, -89.8073512 * units('joule / kilogram'), 6)
def test_cape_cin(): """Test the basic CAPE and CIN calculation.""" p = np.array([959., 779.2, 751.3, 724.3, 700., 269.]) * units.mbar temperature = np.array([22.2, 14.6, 12., 9.4, 7., -38.]) * units.celsius dewpoint = np.array([19., -11.2, -10.8, -10.4, -10., -53.2]) * units.celsius parcel_prof = parcel_profile(p, temperature[0], dewpoint[0]) cape, cin = cape_cin(p, temperature, dewpoint, parcel_prof) assert_almost_equal(cape, 58.0368212 * units('joule / kilogram'), 6) assert_almost_equal(cin, -89.8073512 * units('joule / kilogram'), 6)
def test_cape_cin_custom_profile(): """Test the CAPE and CIN calculation with a custom profile passed to LFC and EL.""" p = np.array([959., 779.2, 751.3, 724.3, 700., 269.]) * units.mbar temperature = np.array([22.2, 14.6, 12., 9.4, 7., -38.]) * units.celsius dewpoint = np.array([19., -11.2, -10.8, -10.4, -10., -53.2]) * units.celsius parcel_prof = parcel_profile(p, temperature[0], dewpoint[0]) + 5 * units.delta_degC cape, cin = cape_cin(p, temperature, dewpoint, parcel_prof) assert_almost_equal(cape, 1443.505086499895 * units('joule / kilogram'), 6) assert_almost_equal(cin, 0.0 * units('joule / kilogram'), 6)
def test_cape_cin_no_lfc(): """Tests that CAPE is zero with no LFC.""" p = np.array([959., 779.2, 751.3, 724.3, 700., 269.]) * units.mbar temperature = np.array([22.2, 24.6, 22., 20.4, 18., -10.]) * units.celsius dewpoint = np.array([19., -11.2, -10.8, -10.4, -10., -53.2]) * units.celsius parcel_prof = parcel_profile(p, temperature[0], dewpoint[0]).to('degC') cape, cin = cape_cin(p, temperature, dewpoint, parcel_prof) assert_almost_equal(cape, 0.0 * units('joule / kilogram'), 6) assert_almost_equal(cin, 0.0 * units('joule / kilogram'), 6)
def calcMLCAPE(levels, temperature, dewpoint, depth=100.0 * units.hPa): _, T_parc, Td_par = mixed_parcel( levels, temperature, dewpoint, depth=depth, interpolate=False, ) profile = parcel_profile(levels, T_parc, Td_parc) cape, cin = cape_cin(levels, temperature, dewpoint, profile) return cape
def get_cape(inargs, return_parcel_profile=False): pres_prof, temp_prof, dp_prof = inargs try: prof = mpcalc.parcel_profile(pres_prof, temp_prof[0], dp_prof[0]) cape, cin = mpcalc.cape_cin(pres_prof, temp_prof, dp_prof, prof) except Exception: cape, cin, prof = np.NaN, np.NaN, np.NaN print('Problem during CAPE-calculation. Likely NaN-related.') if return_parcel_profile: return cape, cin, prof else: return cape, cin
for i in range(1, 5): soundlat = 27.15 soundlon = 360 - (startlat + (londelt * i)) sound_temps = data["temperature"].interp(lat=soundlat, lon=soundlon) - 273.15 sound_rh = data["rh"].interp(lat=soundlat, lon=soundlon) sound_dp = mpcalc.dewpoint_from_relative_humidity( sound_temps.data * units.degC, sound_rh.data * units.percent ) skew = SkewT(fig=fig, rect=(0.75 - (0.15 * i), 0.2, 0.15, 0.1)) parcel_prof = mpcalc.parcel_profile( sound_pres, sound_temps[0].data * units.degC, sound_dp[0] ) cape = mpcalc.cape_cin( sound_pres, sound_temps.data * units.degC, sound_dp, parcel_prof ) capeout = int(cape[0].m) cinout = int(cape[1].m) skew.plot(sound_pres, sound_dp, "g", linewidth=3) skew.plot(sound_pres, sound_temps, "r", linewidth=3) if capeout > capemin: # Shade areas of CAPE and CIN skew.shade_cin(sound_pres, sound_temps.data * units.degC, parcel_prof) skew.shade_cape(sound_pres, sound_temps.data * units.degC, parcel_prof) skew.plot(sound_pres, parcel_prof, color="fuchsia", linewidth=1) skew.ax.axvline(0, color="purple", linestyle="--", linewidth=3) skew.ax.set_ylim((1000, ptop))
def getData(self, time, model_vars, mdl2stnd, previous_data=None): ''' Name: awips_model_base Purpose: A function to get data from NAM40 model to create HDWX products Inputs: request : A DataAccessLayer request object time : List of datatime(s) for data to grab model_vars : Dictionary with variables/levels to get mdl2stnd : Dictionary to convert from model variable names to standardized names Outputs: Returns a dictionary containing all data Keywords: previous_data : Dictionary with data from previous time step ''' log = logging.getLogger(__name__) # Set up function for logger initTime, fcstTime = get_init_fcst_times(time[0]) data = { 'model': self._request.getLocationNames()[0], 'initTime': initTime, 'fcstTime': fcstTime } # Initialize empty dictionary log.info('Attempting to download {} data'.format(data['model'])) for var in model_vars: # Iterate over variables in the vars list log.debug('Getting: {}'.format(var)) self._request.setParameters(*model_vars[var]['parameters']) # Set parameters for the download request self._request.setLevels(*model_vars[var]['levels']) # Set levels for the download request response = DAL.getGridData(self._request, time) # Request the data for res in response: # Iterate over all data request responses varName = res.getParameter() # Get name of the variable in the response varLvl = res.getLevel() # Get level of the variable in the response varName = mdl2stnd[varName] # Convert variable name to local standarized name if varName not in data: data[varName] = {} # If variable name NOT in data dictionary, initialize new dictionary under key data[varName][varLvl] = res.getRawData() # Add data under level name try: # Try to unit = units(res.getUnit()) # Get units and convert to MetPy units except: # On exception unit = '?' # Set units to ? else: # If get units success data[varName][varLvl] *= unit # Get data and create MetPy quantity by multiplying by units log.debug( 'Got data for:\n Var: {}\n Lvl: {}\n Unit: {}'.format( varName, varLvl, unit)) data['lon'], data['lat'] = res.getLatLonCoords() # Get latitude and longitude values data['lon'] *= units('degree') # Add units of degree to longitude data['lat'] *= units('degree') # Add units of degree to latitude # Absolute vorticity dx, dy = lat_lon_grid_deltas(data['lon'], data['lat']) # Get grid spacing in x and y uTag = mdl2stnd[model_vars['wind']['parameters'][0]] # Get initial tag name for u-wind vTag = mdl2stnd[model_vars['wind']['parameters'][1]] # Get initial tag name for v-wind if (uTag in data) and ( vTag in data): # If both tags are in the data structure data['abs_vort'] = {} # Add absolute vorticity key for lvl in model_vars['wind'][ 'levels']: # Iterate over all leves in the wind data if (lvl in data[uTag]) and ( lvl in data[vTag] ): # If given level in both u- and v-wind dictionaries log.debug('Computing absolute vorticity at {}'.format(lvl)) data['abs_vort'][ lvl ] = \ absolute_vorticity( data[uTag][lvl], data[vTag][lvl], dx, dy, data['lat'] ) # Compute absolute vorticity # 1000 MB equivalent potential temperature if ('temperature' in data) and ( 'dewpoint' in data): # If temperature AND depoint data were downloaded data['theta_e'] = {} T, Td = 'temperature', 'dewpoint' if ('1000.0MB' in data[T]) and ( '1000.0MB' in data[Td] ): # If temperature AND depoint data were downloaded log.debug( 'Computing equivalent potential temperature at 1000 hPa') data['theta_e']['1000.0MB'] = equivalent_potential_temperature( 1000.0 * units('hPa'), data[T]['1000.0MB'], data[Td]['1000.0MB']) return data # MLCAPE log.debug('Computing mixed layer CAPE') T_lvl = list(data[T].keys()) Td_lvl = list(data[Td].keys()) levels = list(set(T_lvl).intersection(Td_lvl)) levels = [float(lvl.replace('MB', '')) for lvl in levels] levels = sorted(levels, reverse=True) nLvl = len(levels) if nLvl > 0: log.debug( 'Found {} matching levels in temperature and dewpoint data' .format(nLvl)) nLat, nLon = data['lon'].shape data['MLCAPE'] = np.zeros(( nLat, nLon, ), dtype=np.float32) * units('J/kg') TT = np.zeros(( nLvl, nLat, nLon, ), dtype=np.float32) * units('degC') TTd = np.zeros(( nLvl, nLat, nLon, ), dtype=np.float32) * units('degC') log.debug('Sorting temperature and dewpoint data by level') for i in range(nLvl): key = '{:.1f}MB'.format(levels[i]) TT[i, :, :] = data[T][key].to('degC') TTd[i, :, :] = data[Td][key].to('degC') levels = np.array(levels) * units.hPa depth = 100.0 * units.hPa log.debug('Iterating over grid boxes to compute MLCAPE') for j in range(nLat): for i in range(nLon): try: _, T_parc, Td_parc = mixed_parcel( levels, TT[:, j, i], TTd[:, j, i], depth=depth, interpolate=False, ) profile = parcel_profile(levels, T_parc, Td_parc) cape, cin = cape_cin(levels, TT[:, j, i], TTd[:, j, i], profile) except: log.warning( 'Failed to compute MLCAPE for lon/lat: {}; {}'. format(data['lon'][j, i], data['lat'][j, i])) else: data['MLCAPE'][j, i] = cape return data
def entropy_plots(pressure, temperature, mixing_ratio, altitude, h0_std=2000, ensemble_size=20, ent_rate=np.arange(0, 2, 0.05), entrain=False): """ plotting the summarized entropy diagram with annotations and thermodynamic parameters """ p = pressure * units('mbar') T = temperature * units('degC') q = mixing_ratio * units('kilogram/kilogram') qs = mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(T), p) Td = mpcalc.dewpoint(mpcalc.vapor_pressure(p, q)) # dewpoint Tp = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') # parcel profile # Altitude based on the hydrostatic eq. if len(altitude) == len(pressure): # (1) altitudes for whole levels altitude = altitude * units('meter') elif len(altitude ) == 1: # (2) known altitude where the soundings was launched z_surf = altitude.copy() * units('meter') # given altitude altitude = np.zeros((np.size(T))) * units('meter') for i in range(np.size(T)): altitude[i] = mpcalc.thickness_hydrostatic( p[:i + 1], T[:i + 1]) + z_surf # Hypsometric Eq. for height else: print( '***NOTE***: the altitude at the surface is assumed 0 meter, and altitudes are derived based on the hypsometric equation' ) altitude = np.zeros( (np.size(T))) * units('meter') # surface is 0 meter for i in range(np.size(T)): altitude[i] = mpcalc.thickness_hydrostatic( p[:i + 1], T[:i + 1]) # Hypsometric Eq. for height # specific entropy [joule/(kg*K)] # sd : specific entropy of dry air # sm1 : specific entropy of airborne mositure in state 1 (water vapor) # sm2 : specific entropy of airborne mositure in state 2 (saturated water vapor) sd = entropy(T.magnitude, q.magnitude * 1e-6, p.magnitude) sm1 = entropy(T.magnitude, q.magnitude, p.magnitude) sm2 = entropy(T.magnitude, qs.magnitude, p.magnitude) ############################### # Water vapor calculations p_PWtop = min(p) #p_PWtop = max(200*units.mbar, min(p) + 1*units.mbar) # integrating until 200mb cwv = mpcalc.precipitable_water(Td, p, top=p_PWtop) # column water vapor [mm] cwvs = mpcalc.precipitable_water( T, p, top=p_PWtop) # saturated column water vapor [mm] crh = (cwv / cwvs) * 100. # column relative humidity [%] #================================================ # plotting MSE vertical profiles fig = plt.figure(figsize=[12, 8]) ax = fig.add_axes([0.1, 0.1, 0.6, 0.8]) ax.plot(sd, p, '-k', linewidth=2) ax.plot(sm1, p, '-b', linewidth=2) ax.plot(sm2, p, '-r', linewidth=2) # mse based on different percentages of relative humidity qr = np.zeros((9, np.size(qs))) * units('kilogram/kilogram') sm1_r = qr # container for i in range(9): qr[i, :] = qs * 0.1 * (i + 1) sm1_r[i, :] = entropy(T.magnitude, qr[i, :].magnitude, p.magnitude) for i in range(9): ax.plot(sm1_r[i, :], p[:], '-', color='grey', linewidth=0.7) ax.text(sm1_r[i, 3].magnitude - 2, p[3].magnitude, str((i + 1) * 10)) # drawing LCL and LFC levels [lcl_pressure, lcl_temperature] = mpcalc.lcl(p[0], T[0], Td[0]) lcl_idx = np.argmin(np.abs(p.magnitude - lcl_pressure.magnitude)) [lfc_pressure, lfc_temperature] = mpcalc.lfc(p, T, Td) lfc_idx = np.argmin(np.abs(p.magnitude - lfc_pressure.magnitude)) # conserved mse of air parcel arising from 1000 hpa sm1_p = np.squeeze(np.ones((1, np.size(T))) * sm1[0]) # illustration of CAPE el_pressure, el_temperature = mpcalc.el(p, T, Td) # equilibrium level el_idx = np.argmin(np.abs(p.magnitude - el_pressure.magnitude)) ELps = [el_pressure.magnitude ] # Initialize an array of EL pressures for detrainment profile [CAPE, CIN] = mpcalc.cape_cin(p[:el_idx], T[:el_idx], Td[:el_idx], Tp[:el_idx]) plt.plot(sm1_p, p, color='green', linewidth=2) #ax.fill_betweenx(p[lcl_idx:el_idx+1],sm1_p[lcl_idx:el_idx+1],sm2[lcl_idx:el_idx+1],interpolate=True # ,color='green',alpha='0.3') ax.fill_betweenx(p, sd, sm1, color='deepskyblue', alpha='0.5') ax.set_xlabel('Specific entropies: sd, sm, sm_sat [J K$^{-1}$ kg$^{-1}$]', fontsize=14) ax.set_ylabel('Pressure [hPa]', fontsize=14) ax.set_xticks([0, 50, 100, 150, 200, 250, 300, 350]) ax.set_xlim([0, 440]) ax.set_ylim(1030, 120) if entrain is True: # Depict Entraining parcels # Parcel mass solves dM/dz = eps*M, solution is M = exp(eps*Z) # M=1 at ground without loss of generality # Distribution of surface parcel h offsets h0offsets = np.sort(np.random.normal( 0, h0_std, ensemble_size)) * units('joule/kilogram') # Distribution of entrainment rates entrainment_rates = ent_rate / (units('km')) for h0offset in h0offsets: h4ent = sm1.copy() h4ent[0] += h0offset for eps in entrainment_rates: M = np.exp(eps * (altitude - altitude[0])).to('dimensionless') # dM is the mass contribution at each level, with 1 at the origin level. M[0] = 0 dM = np.gradient(M) # parcel mass is a sum of all the dM's at each level # conserved linearly-mixed variables like h are weighted averages if eps.magnitude == 0.0: hent = np.ones(len(h4ent)) * h4ent[0] # no mixing else: hent = np.cumsum(dM * h4ent) / np.cumsum(dM) # Boolean for positive buoyancy, and its topmost altitude (index) where curve is clippes posboy = (hent > sm2) posboy[0] = True # so there is always a detrainment level # defining the first EL by posboy as the detrainment layer, swiching from positive buoyancy to # negative buoyancy (0 to 1) and skipping the surface ELindex_ent = 0 for idx in range(len(posboy) - 1): if posboy[idx + 1] == 0 and posboy[idx] == 1 and idx > 0: ELindex_ent = idx break # Plot the curve plt.plot(hent[0:ELindex_ent + 2], p[0:ELindex_ent + 2], linewidth=0.6, color='g') #plt.plot( hent[0:], p[0:], linewidth=0.6, color='g') # Keep a list for a histogram plot (detrainment profile) if p[ELindex_ent].magnitude < lfc_pressure.magnitude: # buoyant parcels only ELps.append(p[ELindex_ent].magnitude) # Plot a crude histogram of parcel detrainment levels NBINS = 20 pbins = np.linspace(1000, 150, num=NBINS) # pbins for detrainment levels hist = np.zeros((len(pbins) - 1)) for x in ELps: for i in range(len(pbins) - 1): if (x < pbins[i]) & (x >= pbins[i + 1]): hist[i] += 1 break det_per = hist / sum(hist) * 100 # percentages of detrainment ensumbles at levels ax2 = fig.add_axes([0.705, 0.1, 0.1, 0.8], facecolor=None) ax2.barh(pbins[1:], det_per, color='lightgrey', edgecolor='k', height=15 * (20 / NBINS)) ax2.set_xlim([0, 100]) ax2.set_xticks([0, 20, 40, 60, 80, 100]) ax2.set_ylim([1030, 120]) ax2.set_xlabel('Detrainment [%]') ax2.grid() ax2.set_zorder(2) ax.plot([400, 400], [1100, 0]) ax.annotate('Detrainment', xy=(362, 320), color='dimgrey') ax.annotate('ensemble: ' + str(ensemble_size * len(entrainment_rates)), xy=(364, 340), color='dimgrey') ax.annotate('Detrainment', xy=(362, 380), color='dimgrey') ax.annotate(' scale: 0 - 2 km', xy=(365, 400), color='dimgrey') # Overplots on the mess: undilute parcel and CAPE, etc. ax.plot((1, 1) * sm1[0], (1, 0) * (p[0]), color='g', linewidth=2) # Replot the sounding on top of all that mess ax.plot(sm2, p, color='r', linewidth=1.5) ax.plot(sm1, p, color='b', linewidth=1.5) # label LCL and LCF ax.plot((sm2[lcl_idx] + (-2000, 2000) * units('joule/kilogram')), lcl_pressure + (0, 0) * units('mbar'), color='orange', linewidth=3) ax.plot((sm2[lfc_idx] + (-2000, 2000) * units('joule/kilogram')), lfc_pressure + (0, 0) * units('mbar'), color='magenta', linewidth=3) # Plot a crude histogram of parcel detrainment levels # Text parts ax.text(30, pressure[3], 'RH (%)', fontsize=11, color='k') ax.text(20, 200, 'CAPE = ' + str(np.around(CAPE.magnitude, decimals=2)) + ' [J/kg]', fontsize=12, color='green') ax.text(20, 250, 'CIN = ' + str(np.around(CIN.magnitude, decimals=2)) + ' [J/kg]', fontsize=12, color='green') ax.text(20, 300, 'LCL = ' + str(np.around(lcl_pressure.magnitude, decimals=2)) + ' [hpa]', fontsize=12, color='darkorange') ax.text(20, 350, 'LFC = ' + str(np.around(lfc_pressure.magnitude, decimals=2)) + ' [hpa]', fontsize=12, color='magenta') ax.text(20, 400, 'CWV = ' + str(np.around(cwv.magnitude, decimals=2)) + ' [mm]', fontsize=12, color='deepskyblue') ax.text(20, 450, 'CRH = ' + str(np.around(crh.magnitude, decimals=2)) + ' [%]', fontsize=12, color='blue') ax.legend(['DEnt', 'MEnt', 'SMEnt'], fontsize=12, loc=1) ax.set_zorder(3) return (ax)
def plot_soundings(fig,ax,temp,rh,centerlat,centerlon,domainsize,cape): ''' This function will plot a bunch of little soundings onto a matplotlib fig,ax. temp is an xarray dataarray with temperature data on pressure levels at least between 1000 and 300mb (you can change the ylimits for other datasets) rh is an xarray dataarray with temperature data on pressure levels at least between 1000 and 300mb (you can change ) centerlat and centerlon are the coordinates around which you want your map to be centered. both are floats or integers and are in degrees of latitude and degrees of longitude west (i.e. 70W would be input as positive 70 here) domainsize is a string either 'local' for ~WFO-size domains or 'regional' for NE/SE/Mid-Atlantic-size domains (12 deg lat by 15 deg lon). More will be added soon. cape is a boolean to indicate whether you want to overlay parcel paths and shade CAPE/CIN on soundings with >100 J/kg of CAPE (this value can be changed) note that this function doesn't "return" anything but if you just call it and provide the right arguments, it works. for example: import soundingmaps as smap ... smap.plot_soundings(fig,ax1,data['temperature'],data['rh'],30.5,87.5,'local',cape=True) ''' r=5 if domainsize=='local': init_lat_delt = 1.625 init_lon_delt = 0.45 lat_delts = [.2,.7,1.2,1.75,2.25,2.8] londelt = 0.76 startlon = centerlon-2+0.45 elif domainsize=='regional': init_lat_delt = 6 init_lon_delt = 1.6 lat_delts = [0.6,2.5,4.5,6.4,8.4,10.25] londelt = 2.9 startlon = centerlon-7.5+1.6 startlat = centerlat-init_lat_delt startlon = centerlon-2+0.45 sound_lats=[] sound_lons=[] for i in range(0,6): lats = startlat+lat_delts[i] sound_lats.append(lats) for i in range(0,r): lons = -startlon-(londelt*i) sound_lons.append(lons) plot_elevs=[0.2,0.3,0.4,0.5,0.6,0.7] dashed_red_line = lines.Line2D([], [], linestyle='solid', color='r', label='Temperature') dashed_purple_line = lines.Line2D([],[],linestyle='dashed',color='purple',label='0C Isotherm') dashed_green_line = lines.Line2D([], [], linestyle='solid', color='g', label='Dew Point') grey_line = lines.Line2D([], [], color='darkgray', label='MSLP (hPa)') blue_line = lines.Line2D([], [], color='b',label='2m 0C Isotherm') pink_line = lines.Line2D([], [], color='fuchsia',label='Surface-Based Parcel Path') red = mpatches.Patch(color='tab:red',label='CAPE') blue = mpatches.Patch(color='tab:blue',label='CIN') if cape==True: for k in range(len(plot_elevs)): soundlat = sound_lats[k] plot_elev = plot_elevs[k] if k==0: s=1 else: s=0 for i in range(s,r): sound_pres = temp.lev soundlon = -(startlon+(londelt*i)) sound_temps = temp.interp(lat=soundlat,lon=soundlon)-273.15 sound_rh = rh.interp(lat=soundlat,lon=soundlon) sound_dp = mpcalc.dewpoint_from_relative_humidity(sound_temps.data*units.degC,sound_rh.data*units.percent) skew = SkewT(fig=fig,rect=(0.75-(0.15*i),plot_elev,.15,.1)) parcel_prof = mpcalc.parcel_profile(sound_pres,sound_temps[0].data*units.degC,sound_dp[0]) cape = mpcalc.cape_cin(sound_pres,sound_temps.data*units.degC,sound_dp,parcel_prof) capeout = int(cape[0].m) cinout = int(cape[1].m) skew.plot(sound_pres,sound_dp,'g',linewidth=3) skew.plot(sound_pres,sound_temps,'r',linewidth=3) if capeout >100: # Shade areas of CAPE and CIN print(sound_temps) print(parcel_prof) skew.shade_cin(sound_pres, sound_temps.data*units.degC, parcel_prof) skew.shade_cape(sound_pres, sound_temps.data*units.degC, parcel_prof) skew.plot(sound_pres,parcel_prof,color='fuchsia',linewidth=1) skew.ax.axvline(0, color='purple', linestyle='--', linewidth=3) skew.ax.set_ylim((1000,300)) skew.ax.axis('off') leg = ax.legend(handles=[dashed_red_line,dashed_green_line,dashed_purple_line,pink_line,red,blue],title='Sounding Legend',loc=4,framealpha=1) else: for k in range(len(plot_elevs)): soundlat = sound_lats[k] plot_elev = plot_elevs[k] if k==0: s=1 else: s=0 for i in range(s,r): soundlon = -(startlon+(londelt*i)) sound_pres = temp.lev sound_temps = temp.interp(lat=soundlat,lon=soundlon)-273.15 sound_rh = rh.interp(lat=soundlat,lon=soundlon) sound_dp = mpcalc.dewpoint_from_relative_humidity(sound_temps.data*units.degC,sound_rh.data*units.percent) skew = SkewT(fig=fig,rect=(0.75-(0.15*i),plot_elev,.15,.1)) skew.plot(sound_pres,sound_dp,'g',linewidth=3) skew.plot(sound_pres,sound_temps,'r',linewidth=3) skew.ax.axvline(0, color='purple', linestyle='--', linewidth=3) skew.ax.set_ylim((1000,300)) skew.ax.axis('off') leg = ax.legend(handles=[dashed_red_line,dashed_green_line,dashed_purple_line],title='Sounding Legend',loc=4,framealpha=1)
def process_skewt(self): # Calculation index_p100 = get_pressure_level_index(self.p_i, 100) lcl_p, lcl_t = mpcalc.lcl(self.p_i[0], self.t_i[0], self.td_i[0]) lfc_p, lfc_t = mpcalc.lfc(self.p_i, self.t_i, self.td_i) el_p, el_t = mpcalc.el(self.p_i, self.t_i, self.td_i) prof = mpcalc.parcel_profile(self.p_i, self.t_i[0], self.td_i[0]).to('degC') cape, cin = mpcalc.cape_cin(self.p_i, self.t_i, self.td_i, prof) mucape, mucin = mpcalc.most_unstable_cape_cin(self.p_i, self.t_i, self.td_i) pwat = mpcalc.precipitable_water(self.td_i, self.p_i) i8 = get_pressure_level_index(self.p_i, 850) i7 = get_pressure_level_index(self.p_i, 700) i5 = get_pressure_level_index(self.p_i, 500) theta850 = mpcalc.equivalent_potential_temperature(850 * units('hPa'), self.t_i[i8], self.td_i[i5]) theta500 = mpcalc.equivalent_potential_temperature(500 * units('hPa'), self.t_i[i5], self.td_i[i5]) thetadiff = theta850 - theta500 k = self.t_i[i8] - self.t_i[i5] + self.td_i[i8] - (self.t_i[i7] - self.td_i[i7]) a = ((self.t_i[i8] - self.t_i[i5]) - (self.t_i[i8] - self.td_i[i5]) - (self.t_i[i7] - self.td_i[i7]) - (self.t_i[i5] - self.td_i[i5])) sw = c_sweat(np.array(self.t_i[i8].magnitude), np.array(self.td_i[i8].magnitude), np.array(self.t_i[i5].magnitude), np.array(self.u_i[i8].magnitude), np.array(self.v_i[i8].magnitude), np.array(self.u_i[i5].magnitude), np.array(self.v_i[i5].magnitude)) si = showalter_index(self.t_i[i8], self.td_i[i8], self.t_i[i5]) li = lifted_index(self.t_i[0], self.td_i[0], self.p_i[0], self.t_i[i5]) srh_pos, srh_neg, srh_tot = mpcalc.storm_relative_helicity(self.u_i, self.v_i, self.alt, 1000 * units('m')) sbcape, sbcin = mpcalc.surface_based_cape_cin(self.p_i, self.t_i, self.td_i) shr6km = mpcalc.bulk_shear(self.p_i, self.u_i, self.v_i, heights=self.alt, depth=6000 * units('m')) wshr6km = mpcalc.wind_speed(*shr6km) sigtor = mpcalc.significant_tornado(sbcape, delta_height(self.p_i[0], lcl_p), srh_tot, wshr6km)[0] # Plotting self.ax.set_ylim(1050, 100) self.ax.set_xlim(-40, 50) self.plot(self.p_i, self.t_i, 'r', linewidth=1) self.plot(self.p_i[:self.dp_idx], self.td_i[:self.dp_idx], 'g', linewidth=1) self.plot_barbs(self.p_i[:index_p100], self.u_i[:index_p100] * 1.94, self.v_i[:index_p100] * 1.94) self.plot(lcl_p, lcl_t, 'ko', markerfacecolor='black') self.plot(self.p_i, prof, 'k', linewidth=2) if cin.magnitude < 0: chi = -1 * cin.magnitude self.shade_cin(self.p_i, self.t_i, prof) elif cin.magnitude > 0: chi = cin.magnitude self.shade_cin(self.p_i, self.t_i, prof) else: chi = 0. self.shade_cape(self.p_i, self.t_i, prof) self.plot_dry_adiabats(linewidth=0.5) self.plot_moist_adiabats(linewidth=0.5) self.plot_mixing_lines(linewidth=0.5) plt.title('Skew-T Plot \nStation: {} Time: {}'.format(self.st, self.time.strftime('%Y.%m.%d %H:%M')), fontsize=14, loc='left') # Add hodograph ax = self._fig.add_axes([0.95, 0.71, 0.17, 0.17]) h = Hodograph(ax, component_range=50) h.add_grid(increment=20) h.plot_colormapped(self.u_i[:index_p100], self.v_i[:index_p100], self.alt[:index_p100], linewidth=1.2) # Annotate parameters # Annotate names namelist = ['CAPE', 'CIN', 'MUCAPE', 'PWAT', 'K', 'A', 'SWEAT', 'LCL', 'LFC', 'EL', 'SI', 'LI', 'T850-500', 'θse850-500', 'SRH', 'STP'] xcor = -50 ycor = -90 spacing = -9 for nm in namelist: ax.text(xcor, ycor, '{}: '.format(nm), fontsize=10) ycor += spacing # Annotate values varlist = [cape, chi, mucape, pwat, k, a, sw, lcl_p, lfc_p, el_p, si, li, self.t_i[i8] - self.t_i[i5], thetadiff, srh_tot, sigtor] xcor = 10 ycor = -90 for v in varlist: if hasattr(v, 'magnitude'): v = v.magnitude ax.text(xcor, ycor, str(np.round_(v, 2)), fontsize=10) ycor += spacing # Annotate units unitlist = ['J/kg', 'J/kg', 'J/kg', 'mm', '°C', '°C', '', 'hPa', 'hPa', 'hPa', '°C', '°C', '°C', '°C'] xcor = 45 ycor = -90 for u in unitlist: ax.text(xcor, ycor, ' {}'.format(u), fontsize=10) ycor += spacing
def plot_upper_air(station='11035', date=False): ''' ----------------------------- Default use of plot_upper_air: This will plot a SkewT sounding for station '11035' (Wien Hohe Warte) plot_upper_air(station='11035', date=False) ''' # sns.set(rc={'axes.facecolor':'#343837', 'figure.facecolor':'#343837', # 'grid.linestyle':'','axes.labelcolor':'#04d8b2','text.color':'#04d8b2', # 'xtick.color':'#04d8b2','ytick.color':'#04d8b2'}) # Get time in UTC station = str(station) if date is False: now = datetime.utcnow() # If morning then 0z sounding, otherwise 12z if now.hour < 12: hour = 0 else: hour = 12 date = datetime(now.year, now.month, now.day, hour) datestr = date.strftime('%Hz %Y-%m-%d') print('{}'.format(date)) else: year = int(input('Please specify the year: ')) month = int(input('Please specify the month: ')) day = int(input('Please specify the day: ')) hour = int(input('Please specify the hour: ')) if hour < 12: hour = 0 else: hour = 12 date = datetime(year, month, day, hour) datestr = date.strftime('%Hz %Y-%m-%d') print('You entered {}'.format(date)) # This requests the data 11035 is df = WyomingUpperAir.request_data(date, station) # Create single variables wih the right units p = df['pressure'].values * units.hPa T = df['temperature'].values * units.degC Td = df['dewpoint'].values * units.degC wind_speed = df['speed'].values * units.knots wind_dir = df['direction'].values * units.degrees wind_speed_6k = df['speed'][df.height <= 6000].values * units.knots wind_dir_6k = df['direction'][df.height <= 6000].values * units.degrees u, v = mpcalc.get_wind_components(wind_speed, wind_dir) u6, v6 = mpcalc.get_wind_components(wind_speed_6k, wind_dir_6k) # Calculate the LCL lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0]) print(lcl_pressure, lcl_temperature) # Calculate the parcel profile. parcel_prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') cape, cin = mpcalc.cape_cin(p, T, Td, parcel_prof) ############################# # Create a new figure. The dimensions here give a good aspect ratio fig = plt.figure(figsize=(9, 9)) gs = gridspec.GridSpec(3, 3) skew = SkewT(fig, rotation=45, subplot=gs[:, :2]) # Plot the data using normal plotting functions, in this case using # log scaling in Y, as dictated by the typical meteorological plot skew.plot(p, T, 'r') skew.plot(p, Td, 'g') skew.plot_barbs(p, u, v) skew.ax.set_ylim(1000, 100) skew.ax.set_xlim(-45, 40) # Plot LCL as black dot skew.plot(lcl_pressure, lcl_temperature, 'ko', markerfacecolor='black') # Plot the parcel profile as a black line skew.plot(p, parcel_prof, 'k', linewidth=2) # Shade areas of CAPE and CIN skew.shade_cin(p, T, parcel_prof) skew.shade_cape(p, T, parcel_prof) # Plot a zero degree isotherm skew.ax.axvline(0, color='c', linestyle='--', linewidth=2) skew.ax.set_title('Station: ' + str(station) + '\n' + datestr) # set title skew.ax.set_xlabel('Temperature (C)') skew.ax.set_ylabel('Pressure (hPa)') # Add the relevant special lines skew.plot_dry_adiabats(linewidth=0.7) skew.plot_moist_adiabats(linewidth=0.7) skew.plot_mixing_lines(linewidth=0.7) # Create a hodograph # Create an inset axes object that is 40% width and height of the # figure and put it in the upper right hand corner. # ax_hod = inset_axes(skew.ax, '40%', '40%', loc=1) ax = fig.add_subplot(gs[0, -1]) h = Hodograph(ax, component_range=60.) h.add_grid(increment=20) # Plot a line colored by windspeed h.plot_colormapped(u6, v6, wind_speed_6k) # add another subplot for the text of the indices # ax_t = fig.add_subplot(gs[1:,2]) skew2 = SkewT(fig, rotation=0, subplot=gs[1:, 2]) skew2.plot(p, T, 'r') skew2.plot(p, Td, 'g') # skew2.plot_barbs(p, u, v) skew2.ax.set_ylim(1000, 700) skew2.ax.set_xlim(-30, 10) # Show the plot plt.show() return cape
def msed_plots(pressure, temperature, mixing_ratio, h0_std=2000, ensemble_size=20, ent_rate=np.arange(0, 2, 0.05), entrain=False): """ plotting the summarized static energy diagram with annotations and thermodynamic parameters """ p = pressure * units('mbar') T = temperature * units('degC') q = mixing_ratio * units('kilogram/kilogram') qs = mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(T), p) Td = mpcalc.dewpoint(mpcalc.vapor_pressure(p, q)) # dewpoint Tp = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') # parcel profile # Altitude based on the hydrostatic eq. altitude = np.zeros((np.size(T))) * units('meter') # surface is 0 meter for i in range(np.size(T)): altitude[i] = mpcalc.thickness_hydrostatic( p[:i + 1], T[:i + 1]) # Hypsometric Eq. for height # Static energy calculations mse = mpcalc.moist_static_energy(altitude, T, q) mse_s = mpcalc.moist_static_energy(altitude, T, qs) dse = mpcalc.dry_static_energy(altitude, T) # Water vapor calculations p_PWtop = max(200 * units.mbar, min(p) + 1 * units.mbar) # integrating until 200mb cwv = mpcalc.precipitable_water(Td, p, top=p_PWtop) # column water vapor [mm] cwvs = mpcalc.precipitable_water( T, p, top=p_PWtop) # saturated column water vapor [mm] crh = (cwv / cwvs) * 100. # column relative humidity [%] #================================================ # plotting MSE vertical profiles fig = plt.figure(figsize=[12, 8]) ax = fig.add_axes([0.1, 0.1, 0.6, 0.8]) ax.plot(dse, p, '-k', linewidth=2) ax.plot(mse, p, '-b', linewidth=2) ax.plot(mse_s, p, '-r', linewidth=2) # mse based on different percentages of relative humidity qr = np.zeros((9, np.size(qs))) * units('kilogram/kilogram') mse_r = qr * units('joule/kilogram') # container for i in range(9): qr[i, :] = qs * 0.1 * (i + 1) mse_r[i, :] = mpcalc.moist_static_energy(altitude, T, qr[i, :]) for i in range(9): ax.plot(mse_r[i, :], p[:], '-', color='grey', linewidth=0.7) ax.text(mse_r[i, 3].magnitude / 1000 - 1, p[3].magnitude, str((i + 1) * 10)) # drawing LCL and LFC levels [lcl_pressure, lcl_temperature] = mpcalc.lcl(p[0], T[0], Td[0]) lcl_idx = np.argmin(np.abs(p.magnitude - lcl_pressure.magnitude)) [lfc_pressure, lfc_temperature] = mpcalc.lfc(p, T, Td) lfc_idx = np.argmin(np.abs(p.magnitude - lfc_pressure.magnitude)) # conserved mse of air parcel arising from 1000 hpa mse_p = np.squeeze(np.ones((1, np.size(T))) * mse[0].magnitude) # illustration of CAPE el_pressure, el_temperature = mpcalc.el(p, T, Td) # equilibrium level el_idx = np.argmin(np.abs(p.magnitude - el_pressure.magnitude)) ELps = [el_pressure.magnitude ] # Initialize an array of EL pressures for detrainment profile [CAPE, CIN] = mpcalc.cape_cin(p[:el_idx], T[:el_idx], Td[:el_idx], Tp[:el_idx]) plt.plot(mse_p, p, color='green', linewidth=2) ax.fill_betweenx(p[lcl_idx:el_idx + 1], mse_p[lcl_idx:el_idx + 1], mse_s[lcl_idx:el_idx + 1], interpolate=True, color='green', alpha='0.3') ax.fill_betweenx(p, dse, mse, color='deepskyblue', alpha='0.5') ax.set_xlabel('Specific static energies: s, h, hs [kJ kg$^{-1}$]', fontsize=14) ax.set_ylabel('Pressure [hpa]', fontsize=14) ax.set_xticks([280, 300, 320, 340, 360, 380]) ax.set_xlim([280, 390]) ax.set_ylim(1030, 120) if entrain is True: # Depict Entraining parcels # Parcel mass solves dM/dz = eps*M, solution is M = exp(eps*Z) # M=1 at ground without loss of generality # Distribution of surface parcel h offsets H0STDEV = h0_std # J/kg h0offsets = np.sort(np.random.normal( 0, H0STDEV, ensemble_size)) * units('joule/kilogram') # Distribution of entrainment rates entrainment_rates = ent_rate / (units('km')) for h0offset in h0offsets: h4ent = mse.copy() h4ent[0] += h0offset for eps in entrainment_rates: M = np.exp(eps * (altitude - altitude[0])).to('dimensionless') # dM is the mass contribution at each level, with 1 at the origin level. M[0] = 0 dM = np.gradient(M) # parcel mass is a sum of all the dM's at each level # conserved linearly-mixed variables like h are weighted averages hent = np.cumsum(dM * h4ent) / np.cumsum(dM) # Boolean for positive buoyancy, and its topmost altitude (index) where curve is clippes posboy = (hent > mse_s) posboy[0] = True # so there is always a detrainment level ELindex_ent = np.max(np.where(posboy)) # Plot the curve plt.plot(hent[0:ELindex_ent + 2], p[0:ELindex_ent + 2], linewidth=0.25, color='g') # Keep a list for a histogram plot (detrainment profile) if p[ELindex_ent].magnitude < lfc_pressure.magnitude: # buoyant parcels only ELps.append(p[ELindex_ent].magnitude) # Plot a crude histogram of parcel detrainment levels NBINS = 20 pbins = np.linspace(1000, 150, num=NBINS) # pbins for detrainment levels hist = np.zeros((len(pbins) - 1)) for x in ELps: for i in range(len(pbins) - 1): if (x < pbins[i]) & (x >= pbins[i + 1]): hist[i] += 1 break det_per = hist / sum(hist) * 100 # percentages of detrainment ensumbles at levels ax2 = fig.add_axes([0.705, 0.1, 0.1, 0.8], facecolor=None) ax2.barh(pbins[1:], det_per, color='lightgrey', edgecolor='k', height=15 * (20 / NBINS)) ax2.set_xlim([0, max(det_per)]) ax2.set_ylim([1030, 120]) ax2.set_xlabel('Detrainment [%]') ax2.grid() ax2.set_zorder(2) ax.plot([400, 400], [1100, 0]) ax.annotate('Detrainment', xy=(362, 320), color='dimgrey') ax.annotate('ensemble: ' + str(ensemble_size * len(entrainment_rates)), xy=(364, 340), color='dimgrey') ax.annotate('Detrainment', xy=(362, 380), color='dimgrey') ax.annotate(' scale: 0 - 2 km', xy=(365, 400), color='dimgrey') # Overplots on the mess: undilute parcel and CAPE, etc. ax.plot((1, 1) * mse[0], (1, 0) * (p[0]), color='g', linewidth=2) # Replot the sounding on top of all that mess ax.plot(mse_s, p, color='r', linewidth=1.5) ax.plot(mse, p, color='b', linewidth=1.5) # label LCL and LCF ax.plot((mse_s[lcl_idx] + (-2000, 2000) * units('joule/kilogram')), lcl_pressure + (0, 0) * units('mbar'), color='orange', linewidth=3) ax.plot((mse_s[lfc_idx] + (-2000, 2000) * units('joule/kilogram')), lfc_pressure + (0, 0) * units('mbar'), color='magenta', linewidth=3) ### Internal waves (100m adiabatic displacements, assumed adiabatic: conserves s, sv, h). #dZ = 100 *mpunits.units.meter dp = 1000 * units.pascal # depict displacements at sounding levels nearest these target levels targetlevels = [900, 800, 700, 600, 500, 400, 300, 200] * units.hPa for ilev in targetlevels: idx = np.argmin(np.abs(p - ilev)) # dp: hydrostatic rho = (p[idx]) / Rd / (T[idx]) dZ = -dp / rho / g # dT: Dry lapse rate dT/dz_dry is -g/Cp dT = (-g / Cp_d * dZ).to('kelvin') Tdisp = T[idx].to('kelvin') + dT # dhsat dqs = mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(Tdisp), p[idx] + dp) - qs[idx] dhs = g * dZ + Cp_d * dT + Lv * dqs # Whiskers on the data plots ax.plot((mse_s[idx] + dhs * (-1, 1)), p[idx] + dp * (-1, 1), linewidth=3, color='r') ax.plot((dse[idx] * (1, 1)), p[idx] + dp * (-1, 1), linewidth=3, color='k') ax.plot((mse[idx] * (1, 1)), p[idx] + dp * (-1, 1), linewidth=3, color='b') # annotation to explain it if ilev == 400 * ilev.units: ax.plot(360 * mse_s.units + dhs * (-1, 1) / 1000, 440 * units('mbar') + dp * (-1, 1), linewidth=3, color='r') ax.annotate('+/- 10mb', xy=(362, 440), fontsize=8) ax.annotate(' adiabatic displacement', xy=(362, 460), fontsize=8) # Plot a crude histogram of parcel detrainment levels # Text parts ax.text(290, pressure[3], 'RH (%)', fontsize=11, color='k') ax.text(285, 200, 'CAPE = ' + str(np.around(CAPE.magnitude, decimals=2)) + ' [J/kg]', fontsize=12, color='green') ax.text(285, 250, 'CIN = ' + str(np.around(CIN.magnitude, decimals=2)) + ' [J/kg]', fontsize=12, color='green') ax.text(285, 300, 'LCL = ' + str(np.around(lcl_pressure.magnitude, decimals=2)) + ' [hpa]', fontsize=12, color='darkorange') ax.text(285, 350, 'LFC = ' + str(np.around(lfc_pressure.magnitude, decimals=2)) + ' [hpa]', fontsize=12, color='magenta') ax.text(285, 400, 'CWV = ' + str(np.around(cwv.magnitude, decimals=2)) + ' [mm]', fontsize=12, color='deepskyblue') ax.text(285, 450, 'CRH = ' + str(np.around(crh.magnitude, decimals=2)) + ' [%]', fontsize=12, color='blue') ax.legend(['DSE', 'MSE', 'SMSE'], fontsize=12, loc=1) ax.set_zorder(3) return (ax)
def EnergyMassPlot(pressure, temperature, dewpoint, height, uwind, vwind, sphum=None, rh=None, label='', size=(12,10), return_fig=False): p=pressure Z=height T=temperature Td=dewpoint if isinstance(sphum,np.ndarray) and isinstance(rh,np.ndarray): q=sphum qs=q/rh else: q = mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(Td),p) qs= mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(T),p) s = g*Z + Cp_d*T sv= g*Z + Cp_d*mpcalc.virtual_temperature(T,q) h = s + Lv*q hs= s + Lv*qs parcel_Tprofile = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') CAPE,CIN = mpcalc.cape_cin(p,T,Td,parcel_Tprofile) ELp,ELT = mpcalc.el(p,T,Td) ELindex = np.argmin(np.abs(p - ELp)) lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0]) p_PWtop = max(200*units.mbar, min(p) +1*units.mbar) PW = mpcalc.precipitable_water(Td,p, top=p_PWtop) PWs = mpcalc.precipitable_water(T,p, top=p_PWtop) CRH = (PW/PWs).magnitude *100. fig,ax=setup_fig(size=size,label=label) ax.plot(s /1000, p, color='r', linewidth=1.5) ### /1000 for kJ/kg ax.plot(sv /1000, p, color='r', linestyle='-.') ax.plot(h /1000, p, color='b', linewidth=1.5) ax.plot(hs /1000, p, color='r', linewidth=1.5) ### RH rulings between s and h lines: annotate near 800 hPa level annot_level = 800 #hPa idx = np.argmin(np.abs(p - annot_level *units.hPa)) right_annot_loc = 380 for iRH in np.arange(10,100,10): ax.plot( (s+ Lv*qs*iRH/100.)/1000, p, linewidth=0.5, linestyle=':', color='k') ax.annotate(str(iRH), xy=( (s[idx]+Lv*qs[idx]*iRH/100.)/1000, annot_level), horizontalalignment='center',fontsize=6) ax.annotate('RH (%)', xy=(right_annot_loc, annot_level), fontsize=10) if not np.isnan(CAPE.magnitude) and CAPE.magnitude >10: parcelh = h [0] # for a layer mean: np.mean(h[idx1:idx2]) parcelsv = sv[0] parcelp0 = p[0] # Undilute parcel ax.plot( (1,1)*parcelh/1000., (1,0)*parcelp0, linewidth=0.5, color='g') maxbindex = np.argmax(parcel_Tprofile - T) ax.annotate('CAPE='+str(int(CAPE.magnitude)), xy=(parcelh/1000., p[maxbindex]), color='g') # Plot LCL at saturation point, above the lifted sv of the surface parcel lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0]) ax.annotate('LCL', xy=(sv[0]/1000., lcl_pressure), fontsize=10, color='g', horizontalalignment='right') # Purple fill for negative buoyancy below LCL: ax.fill_betweenx(p, sv/1000., parcelsv/1000., where=p>lcl_pressure, facecolor='purple', alpha=0.4) # Positive moist convective buoyancy in green # Above LCL: ax.fill_betweenx(p, hs/1000., parcelh/1000., where= parcelh>hs, facecolor='g', alpha=0.4) # Depict Entraining parcels # Parcel mass solves dM/dz = eps*M, solution is M = exp(eps*Z) # M=1 at ground without loss of generality entrainment_distance = 10000., 5000., 2000. ax.annotate('entrain: 10,5,2 km', xy=(parcelh/1000, 140), color='g', fontsize=8, horizontalalignment='right') ax.annotate('parcel h', xy=(parcelh/1000, 120), color='g', fontsize=10, horizontalalignment='right') for ED in entrainment_distance: eps = 1.0 / (ED*units.meter) M = np.exp(eps * (Z-Z[0]).to('m')).to('dimensionless') # dM is the mass contribution at each level, with 1 at the origin level. M[0] = 0 dM = np.gradient(M) # parcel mass is a sum of all the dM's at each level # conserved linearly-mixed variables like h are weighted averages hent = np.cumsum(dM*h) / np.cumsum(dM) ax.plot( hent[0:ELindex+3]/1000., p[0:ELindex+3], linewidth=0.5, color='g') ### Internal waves (100m adiabatic displacements, assumed adiabatic: conserves s, sv, h). dZ = 100 *units.meter # depict displacements at sounding levels nearest these target levels targetlevels = [900,800,700,600,500,400,300,200]*units.hPa for ilev in targetlevels: idx = np.argmin(np.abs(p - ilev)) # dT: Dry lapse rate dT/dz_dry is -g/Cp dT = (-g/Cp_d *dZ).to('kelvin') Tdisp = T[idx].to('kelvin') + dT # dp: hydrostatic rho = (p[idx]/Rd/T[idx]) dp = -rho*g*dZ # dhsat #qs = mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(T) ,p) dqs = mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(Tdisp) ,p[idx]+dp) -qs[idx] dhs = g*dZ + Cp_d*dT + Lv*dqs # Whiskers on the data plots ax.plot( (hs[idx]+dhs*(-1,1))/1000, p[idx]+dp*(-1,1), linewidth=3, color='r') ax.plot( (s [idx] *( 1,1))/1000, p[idx]+dp*(-1,1), linewidth=3, color='r') ax.plot( (h [idx] *( 1,1))/1000, p[idx]+dp*(-1,1), linewidth=3, color='b') # annotation to explain it if ilev == 600*ilev.units: ax.plot(right_annot_loc*hs.units +dhs*(-1,1)/1000, p[idx]+dp*(-1,1), linewidth=3, color='r') ax.annotate('+/- 100m', xy=(right_annot_loc,600), fontsize=8) ax.annotate(' internal', xy=(right_annot_loc,630), fontsize=8) ax.annotate(' waves', xy=(right_annot_loc,660), fontsize=8) ### Blue fill proportional to precipitable water, and blue annotation ax.fill_betweenx(p, s/1000., h/1000., where=h > s, facecolor='b', alpha=0.4) # Have to specify the top of the PW integral. # I want whole atmosphere of course, but 200 hPa captures it all really. #import metpy.calc as mpcalc p_PWtop = max(200*units.mbar, min(p) +1*units.mbar) PW = mpcalc.precipitable_water(Td,p, top=p_PWtop) PWs = mpcalc.precipitable_water(T,p, top=p_PWtop) CRH = (PW/PWs).magnitude *100. # PW annotation arrow tip at 700 mb idx = np.argmin(np.abs(p - 700*p.units)) centerblue = (s[idx]+h[idx])/2.0 /1000. ax.annotate('CWV='+str(round(PW.to('mm').magnitude, 1))+'mm', xy=(centerblue, 700), xytext=(285, 200), color='blue', fontsize=15, arrowprops=dict(width = 1, edgecolor='blue', shrink=0.02), ) ax.annotate('(' + str(round(CRH,1)) +'% of sat)', xy=(285, 230), color='blue', fontsize=12) ### Surface water values at 1C intervals, for eyeballing surface fluxes sT = np.trunc(T[0].to('degC')) sTint = int(sT.magnitude) for idT in [-2,0,2,4]: ssTint = sTint + idT # UNITLESS degC integers, for labels # Kelvin values for computations ssTC = ssTint * units.degC ssTK = ssTC.to('kelvin') ss = g*Z[0] + Cp_d*ssTK hs = ss + Lv*mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(ssTK) ,p[0]) ax.annotate(str(ssTint), xy=(ss/1000., p[0]+0*p.units), verticalalignment='top', horizontalalignment='center', color='red', fontsize=7) ax.annotate(str(ssTint), xy=(hs/1000., p[0]+0*p.units), verticalalignment='top', horizontalalignment='center', color='red', fontsize=9) ax.annotate('\u00b0C water', xy=(right_annot_loc, p[0]), verticalalignment='top', fontsize=10, color='r') if return_fig: return ax,fig
'dewpoint': Td, 'speed': ws, 'direction': wd }) p = df['pressure'].values * units.hPa T = df['temperature'].values * units.degC Td = df['dewpoint'].values * units.degC wind_speed = df['speed'].values * units.meter / (units.second) wind_dir = df['direction'].values * units.degrees u, v = mpcalc.wind_components(wind_speed, wind_dir) lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0]) lfc_pressure, lfc_temperature = mpcalc.lfc(p, T, Td) parcel_prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') cape, cin = mpcalc.cape_cin(p, T, Td, parcel_prof) fig = plt.figure(figsize=(12., 9.)) fig.subplots_adjust(top=0.9, bottom=0.1, left=0.05, right=0.96, wspace=0.08, hspace=0.25) gs = gridspec.GridSpec(21, 5) skew = SkewT(fig, subplot=gs[:, :4], rotation=45) # Plot the data using normal plotting functions, in this case using # log scaling in Y, as dictated by the typical meteorological plot skip = 100 skew.plot(p, T, 'r', linewidth=3)
def cape_cin(self, index_from): return mpcalc.cape_cin(self.p[index_from:], self.T[index_from:], self.Td[index_from:], self.parcel_trace(index_from))
def fmi2skewt(station, time, img_name): apikey = 'e72a2917-1e71-4d6f-8f29-ff4abfb8f290' url = 'http://data.fmi.fi/fmi-apikey/' + str( apikey ) + '/wfs?request=getFeature&storedquery_id=fmi::observations::weather::sounding::multipointcoverage&fmisid=' + str( station) + '&starttime=' + str(time) + '&endtime=' + str(time) + '&' req = requests.get(url) xmlstring = req.content tree = ET.ElementTree(ET.fromstring(xmlstring)) root = tree.getroot() #reading location and time data to "positions" from XML positions = "" for elem in root.getiterator( tag='{http://www.opengis.net/gmlcov/1.0}positions'): positions = elem.text #'positions' is string type variable #--> split positions into a list by " " #then remove empty chars and "\n" # from pos_split --> data into positions_data try: pos_split = positions.split(' ') except NameError: return "Sounding data not found: stationid " + station + " time " + time pos_split = positions.split(' ') positions_data = [] for i in range(0, len(pos_split)): if not (pos_split[i] == "" or pos_split[i] == "\n"): positions_data.append(pos_split[i]) #index for height: 2,6,10 etc in positions_data height = [] myList = range(2, len(positions_data)) for i in myList[::4]: height.append(positions_data[i]) p = [] for i in range(0, len(height)): p.append(height2pressure(float(height[i]))) #reading wind speed, wind direction, air temperature and dew point data to 'values' values = "" for elem in root.getiterator( tag='{http://www.opengis.net/gml/3.2}doubleOrNilReasonTupleList'): values = elem.text #split 'values' into a list by " " #then remove empty chars and "\n" val_split = values.split(' ') values_data = [] for i in range(0, len(val_split)): if not (val_split[i] == "" or val_split[i] == "\n"): values_data.append(val_split[i]) #data in values_data: w_speed, w_dir, t_air, t_dew wind_speed = [] wind_dir = [] T = [] Td = [] myList = range(0, len(values_data)) for i in myList[::4]: wind_speed.append(float(values_data[i])) wind_dir.append(float(values_data[i + 1])) T.append(float(values_data[i + 2])) Td.append(float(values_data[i + 3])) if stationid == "101104": loc_time = "Jokioinen Ilmala " + time elif stationid == "101932": loc_time = "Sodankyla Tahtela " + time else: return None #calculate wind components u,v: u = [] v = [] for i in range(0, len(wind_speed)): u1, v1 = getWindComponent(wind_speed[i], wind_dir[i]) u.append(u1) v.append(v1) #find index for pressure < 100hPa (for number of wind bars) if min(p) > 100: wthin = len(p) / 20 u_plot = u v_plot = v p_plot = p else: for i in range(0, len(p)): if p[i] - 100 <= 0: wthin = i / 20 u_plot = u[0:i] v_plot = v[0:i] p_plot = p[0:i] break #units wind_speed = wind_speed * units("m/s") wind_dir = wind_dir * units.deg T = T * units.degC Td = Td * units.degC p = p * units("hPa") #calculate pwat, lcl, cape, cin and plot cape pwat = mpcalc.precipitable_water(Td, p, bottom=None, top=None) lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0]) prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') try: cape, cin = mpcalc.cape_cin(p, T, Td, prof) except IndexError: cape = 0 * units("J/kg") cin = 0 * units("J/kg") #__________________plotting__________________ fig = plt.figure(figsize=(9, 9)) skew = SkewT(fig, rotation=45) font_par = { 'family': 'monospace', 'color': 'darkred', 'weight': 'normal', 'size': 10, } font_title = { 'family': 'monospace', 'color': 'black', 'weight': 'normal', 'size': 20, } font_axis = { 'family': 'monospace', 'color': 'black', 'weight': 'normal', 'size': 10, } # Plot the data using normal plotting functions, in this case using # log scaling in Y, as dictated by the typical meteorological plot skew.plot(p, T, 'k') skew.plot(p, Td, 'b') skew.ax.set_ylim(1000, 100) skew.ax.set_xlim(-50, 30) skew.plot_barbs(p_plot[0::wthin], u_plot[0::wthin], v_plot[0::wthin]) skew.plot_dry_adiabats(alpha=0.4) skew.plot_moist_adiabats(alpha=0.4) skew.plot_mixing_lines(alpha=0.4) #skew.shade_cape(p, T, prof,color="orangered") plt.title(loc_time, fontdict=font_title) plt.xlabel("T (C)", fontdict=font_axis) plt.ylabel("P (hPa)", fontdict=font_axis) #round and remove units from cape,cin,plcl,tlcl,pwat if cape.magnitude > 0: capestr = str(np.round(cape.magnitude)) else: capestr = "NaN" if cin.magnitude > 0: cinstr = str(np.round(cin.magnitude)) else: cinstr = "NaN" lclpstr = str(np.round(lcl_pressure.magnitude)) lclTstr = str(np.round(lcl_temperature.magnitude)) pwatstr = str(np.round(pwat.magnitude)) # str_par = "CAPE[J/kg]=" + capestr + " CIN[J/kg]=" + cinstr + " Plcl[hPa]=" + lclpstr + " Tlcl[C]=" + lclTstr + " pwat[mm]=" + pwatstr # font = {'family': 'monospace', # 'color': 'darkred', # 'weight': 'normal', # 'size': 10, # } # plt.text(-20,1250,str_par,fontdict=font_par) save_file = img_name plt.savefig(save_file)
def plot_soundings(fig, ax, temp, rh, sfc_pressure, centerlat, centerlon, domainsize, model, cape=False, wetbulb=False): """ This function will plot a bunch of little soundings onto a matplotlib fig,ax. temp is an xarray dataarray with temperature data on pressure levels at least between 1000 and 300mb (you can change the ylimits for other datasets) rh is an xarray dataarray with temperature data on pressure levels at least between 1000 and 300mb (you can change ) sfc_pressure is an xarray dataarray with surface pressure data (NOT MSLP!) centerlat and centerlon are the coordinates around which you want your map to be centered. both are floats or integers and are in degrees of latitude and degrees of longitude west (i.e. 70W would be input as positive 70 here) domainsize is a string either 'local' for ~WFO-size domains or 'regional' for NE/SE/Mid-Atlantic-size domains (12 deg lat by 15 deg lon). More will be added soon. model is a string that specifies which model is providing data for the plots. This determines a few things, most importantly longitude selections. Models currently supported are 'GFS','NAM',and 'RAP' cape is a boolean to indicate whether you want to overlay parcel paths and shade CAPE/CIN on soundings with >100 J/kg of CAPE (this value can be changed) wetbulb is a boolean to indicate whether you want to draw wet bulb profiles note that this function doesn't "return" anything but if you just call it and provide the right arguments, it works. for example: import soundingmaps as smap ... smap.plot_soundings(fig,ax1,data['temperature'],data['rh'],30.5,87.5,'local',cape=True) """ r = 5 if domainsize == "local": init_lat_delt = 1.625 init_lon_delt = 0.45 lat_delts = [0.2, 0.7, 1.2, 1.75, 2.25, 2.8] londelt = 0.76 startlon = centerlon - 2 + 0.45 elif domainsize == "regional": init_lat_delt = 6 init_lon_delt = 1.6 lat_delts = [0.6, 2.5, 4.5, 6.4, 8.4, 10.25] londelt = 2.9 startlon = centerlon - 7.5 + 1.6 # Lon adjustment for GFS because it's [0,360] not [-180,180] if model == 'GFS': startlon = 360 - startlon # set lat/lon grid from which to pull data to plot soundings startlat = centerlat - init_lat_delt sound_lats = [] sound_lons = [] for i in range(0, 6): lats = startlat + lat_delts[i] sound_lats.append(lats) for i in range(0, r): if model == 'GFS': lons = startlon - (londelt * i) else: lons = -startlon - (londelt * i) sound_lons.append(lons) # this sets how high each row of soundings is on the plot plot_elevs = [0.2, 0.3, 0.4, 0.5, 0.6, 0.7] # whole bunch of legend stuff dashed_red_line = lines.Line2D([], [], linestyle='solid', color='r', label='Temperature') dashed_purple_line = lines.Line2D([], [], linestyle='dashed', color='purple', label='0C Isotherm') dashed_green_line = lines.Line2D([], [], linestyle='solid', color='g', label='Dew Point') grey_line = lines.Line2D([], [], color='darkgray', label='MSLP (hPa)') blue_line = lines.Line2D([], [], color='b', label='Wet Bulb') pink_line = lines.Line2D([], [], color='fuchsia', label='Surface-Based Parcel Path') teal_line = lines.Line2D([], [], linestyle='dashed', color='teal', label='HGZ') green_dot = lines.Line2D([], [], marker='o', color='forestgreen', label='LCL') black_dot = lines.Line2D([], [], marker='o', color='k', label='Sounding Origin') red = mpatches.Patch(color='tab:red', label='CAPE') blue = mpatches.Patch(color='tab:blue', label='CIN') # do the plotting based on user inputs if cape and wetbulb is True: print('CAPE + Wetbulb') for i, plot_elev in enumerate(plot_elevs): soundlat = sound_lats[i] if k < 2: s = 1 else: s = 0 for i in range(s, r): levs_abv_ground = [] soundlon = sound_lons[i] sound_temps = temp.interp(lat=soundlat, lon=soundlon) - 273.15 sound_rh = rh.interp(lat=soundlat, lon=soundlon) sound_pres = temp.lev spres = sfc_pressure.interp(lat=soundlat, lon=soundlon) sound_dp = mpcalc.dewpoint_from_relative_humidity( sound_temps.data * units.degC, sound_rh.data * units.percent) sound_wb = mpcalc.wet_bulb_temperature( sound_pres, sound_temps.data * units.degC, sound_dp) #Only want data above the ground abv_sfc_temp = spt.mask_below_terrain(spres, sound_temps, sound_pres)[0] abv_sfc_dewp = spt.mask_below_terrain(spres, sound_dp, sound_pres)[0] abv_sfc_wetb = spt.mask_below_terrain(spres, sound_wb, sound_pres)[0] pres_abv_ground = spt.mask_below_terrain( spres, sound_temps, sound_pres)[1] #sound_wb = sound_wb*units.degC skew = SkewT(fig=fig, rect=(0.75 - (0.15 * i), plot_elev, .15, .1)) parcel_prof = mpcalc.parcel_profile( pres_abv_ground, abv_sfc_temp[0].data * units.degC, abv_sfc_dewp[0]) cape = mpcalc.cape_cin(pres_abv_ground, abv_sfc_temp.data * units.degC, abv_sfc_dewp, parcel_prof) capeout = int(cape[0].m) cinout = int(cape[1].m) #skew.ax.axvspan(-30, -10, color='cyan', alpha=0.4) skew.plot(pres_abv_ground, abv_sfc_wetb, 'b', linewidth=2) skew.plot(pres_abv_ground, abv_sfc_dewp, 'g', linewidth=3) skew.plot(pres_abv_ground, abv_sfc_temp, 'r', linewidth=3) if capeout > 100: # Shade areas of CAPE and CIN print(pres_abv_ground) print(abv_sfc_temp.data * units.degC) print(parcel_prof) skew.shade_cin(pres_abv_ground, abv_sfc_temp.data * units.degC, parcel_prof) skew.shade_cape(pres_abv_ground, abv_sfc_temp.data * units.degC, parcel_prof) skew.plot(pres_abv_ground, parcel_prof, color='fuchsia', linewidth=1) lcl_pressure, lcl_temperature = mpcalc.lcl( pres_abv_ground[0], abv_sfc_temp.data[0] * units.degC, abv_sfc_dewp[0]) skew.plot(lcl_pressure, lcl_temperature, 'ko', markerfacecolor='forestgreen') skew.ax.axvline(-30, color='teal', linestyle='--', linewidth=1) skew.ax.axvline(-10, color='teal', linestyle='--', linewidth=1) skew.plot(975, 0, 'ko', markerfacecolor='k') skew.ax.axvline(0, color='purple', linestyle='--', linewidth=3) skew.ax.set_ylim((1000, 300)) skew.ax.axis('off') leg = ax.legend(handles=[ dashed_red_line, dashed_green_line, blue_line, dashed_purple_line, teal_line, green_dot, pink_line, red, blue, black_dot ], title='Sounding Legend', loc=4, framealpha=1) elif cape == True and wetbulb == False: print('CAPE no wetbulb') for k in range(len(plot_elevs)): soundlat = sound_lats[k] plot_elev = plot_elevs[k] if k == 0: s = 1 else: s = 0 for i in range(s, r): levs_abv_ground = [] soundlon = sound_lons[i] sound_temps = temp.interp(lat=soundlat, lon=soundlon) - 273.15 sound_rh = rh.interp(lat=soundlat, lon=soundlon) sound_pres = temp.lev spres = sfc_pressure.interp(lat=soundlat, lon=soundlon) sound_dp = mpcalc.dewpoint_from_relative_humidity( sound_temps.data * units.degC, sound_rh.data * units.percent) abv_sfc_temp = spt.mask_below_terrain(spres, sound_temps, sound_pres)[0] abv_sfc_dewp = spt.mask_below_terrain(spres, sound_dp, sound_pres)[0] pres_abv_ground = spt.mask_below_terrain( spres, sound_temps, sound_pres)[1] skew = SkewT(fig=fig, rect=(0.75 - (0.15 * i), plot_elev, .15, .1)) parcel_prof = mpcalc.parcel_profile( pres_abv_ground, abv_sfc_temp[0].data * units.degC, abv_sfc_dewp[0]) cape = mpcalc.cape_cin(pres_abv_ground, abv_sfc_temp.data * units.degC, abv_sfc_dewp, parcel_prof) capeout = int(cape[0].m) cinout = int(cape[1].m) skew.plot(pres_abv_ground, abv_sfc_dewp, 'g', linewidth=3) skew.plot(pres_abv_ground, abv_sfc_temp, 'r', linewidth=3) if capeout > 100: # Shade areas of CAPE and CIN skew.shade_cin(pres_abv_ground, abv_sfc_temp.data * units.degC, parcel_prof) skew.shade_cape(pres_abv_ground, abv_sfc_temp.data * units.degC, parcel_prof) skew.plot(pres_abv_ground, parcel_prof, color='fuchsia', linewidth=1) print(abv_sfc_temp) lcl_pressure, lcl_temperature = mpcalc.lcl( pres_abv_ground[0], abv_sfc_temp.data[0] * units.degC, abv_sfc_dewp[0]) skew.plot(lcl_pressure, lcl_temperature, 'ko', markerfacecolor='forestgreen') skew.ax.axvline(-30, color='teal', linestyle='--', linewidth=1) skew.ax.axvline(-10, color='teal', linestyle='--', linewidth=1) skew.plot(975, 0, 'ko', markerfacecolor='k') skew.ax.axvline(0, color='purple', linestyle='--', linewidth=3) skew.ax.set_ylim((1000, 300)) skew.ax.axis('off') leg = ax.legend(handles=[ dashed_red_line, dashed_green_line, dashed_purple_line, teal_line, green_dot, pink_line, red, blue, black_dot ], title='Sounding Legend', loc=4, framealpha=1) elif wetbulb == True and cape == False: print('Wetbulb no CAPE') for k in range(len(plot_elevs)): soundlat = sound_lats[k] plot_elev = plot_elevs[k] if k == 0: s = 1 else: s = 0 for i in range(s, r): levs_abv_ground = [] soundlon = sound_lons[i] sound_temps = temp.interp(lat=soundlat, lon=soundlon) - 273.15 sound_rh = rh.interp(lat=soundlat, lon=soundlon) sound_pres = temp.lev spres = sfc_pressure.interp(lat=soundlat, lon=soundlon) sound_dp = mpcalc.dewpoint_from_relative_humidity( sound_temps.data * units.degC, sound_rh.data * units.percent) sound_wb = mpcalc.wet_bulb_temperature( sound_pres, sound_temps.data * units.degC, sound_dp) abv_sfc_temp = spt.mask_below_terrain(spres, sound_temps, sound_pres)[0] abv_sfc_dewp = spt.mask_below_terrain(spres, sound_dp, sound_pres)[0] abv_sfc_wetb = spt.mask_below_terrain(spres, sound_wb, sound_pres)[0] pres_abv_ground = spt.mask_below_terrain( spres, sound_temps, sound_pres)[1] #sound_wb = sound_wb*units.degC skew = SkewT(fig=fig, rect=(0.75 - (0.15 * i), plot_elev, .15, .1)) skew.plot(pres_abv_ground, abv_sfc_wetb, 'b', linewidth=2) skew.plot(pres_abv_ground, abv_sfc_dewp, 'g', linewidth=3) skew.plot(pres_abv_ground, abv_sfc_temp, 'r', linewidth=3) skew.ax.axvline(0, color='purple', linestyle='--', linewidth=3) skew.ax.set_ylim((1000, 300)) skew.ax.axis('off') else: print('No Wetbulb or CAPE') for k in range(len(plot_elevs)): soundlat = sound_lats[k] plot_elev = plot_elevs[k] if k == 0: s = 1 else: s = 0 for i in range(s, r): sound_pres = temp.lev sound_temps = temp.interp(lat=soundlat, lon=soundlon) - 273.15 sound_rh = rh.interp(lat=soundlat, lon=soundlon) sound_dp = mpcalc.dewpoint_from_relative_humidity( sound_temps.data * units.degC, sound_rh.data * units.percent) skew = SkewT(fig=fig, rect=(0.75 - (0.15 * i), plot_elev, .15, .1)) skew.plot(sound_pres, sound_dp, 'g', linewidth=3) skew.plot(sound_pres, sound_temps, 'r', linewidth=3) skew.plot(1000, 0, 'ko', markerfacecolor='k') skew.ax.axvline(0, color='purple', linestyle='--', linewidth=3) skew.ax.set_ylim((1000, 300)) skew.ax.axis('off') leg = ax.legend(handles=[ dashed_red_line, dashed_green_line, blue_line, dashed_purple_line, black_dot ], title='Sounding Legend', loc=4, framealpha=1)