def V(self, x, y, u, F, a, i, correction=False): """Computes the model enhancements including SZA offsets. Incoming and reflected offsets are calculated with offsets method. These offsets are applied to the position (x, y), and then enhancements are calculated using PlumeModel.V function. Args: Args passed to PlumeModel.V (x, y, u, F, a) i; index in file to use for zenith and azimuth angles correction; A Bool controlling whether to account for the angle the ray passes through the plume in. No longer implemented, since we are using a 2D plume model and this opens up too many other uncertainties. Returns: Enhancements in g/m^2 that are expected at ground footprint (x,y) """ (x_incoming, y_incoming), (x_reflected, y_reflected) = self.offsets(i) V_incoming = PlumeModel.V(x + x_incoming, y + y_incoming, u, F, a) V_reflected = PlumeModel.V(x + x_reflected, y + y_reflected, u, F, a) V_corrected = 0.5 * (V_incoming + V_reflected) return V_corrected
def get_offset(self, overpass, wind, return_index=False): """ Calculates the offset (the (x,y) distance) from the source to the maximum enhancement (the center of the plume) Args: * overpass, a PST.Overpass instance * wind, a PST.Wind instance * return_index = False; optionally returns the index in the file with the closest point Output: * (x_offset, y_offset); the x and y distances to the center plume from the source """ # List [(x1, y1), (x2, y2), ...] for all points in the instance posns = [Geometry.CoordGeom(wind).coord_to_wind_basis(overpass.lat,overpass.lon,lat,lon) for (lat,lon) in zip(self.retrieval_latitude,self.retrieval_longitude)] # List of [V(x1, y1), V(x2, y2), ...] for all positions (xi, yi) in posns all_enhancements = [PlumeModel.V(x,y,wind.speed,1.,overpass.a) for (x,y) in posns] # Index of the maximum enhancement found in all_enhancements closest_index = all_enhancements.index(max(all_enhancements)) # (x, y) distance of the max enhancement with respect to the source. This way distances are measured from the center of the plume x_offset,y_offset = posns[closest_index] if return_index: return (x_offset, y_offset, closest_index) else: return (x_offset, y_offset)
def offset_boundary(a, start=(0, 0), offset=3000., xmax=80.e3, **kwargs): """Calculates the boundaries for the given parameters. Args: * a: atmospheric stability parameter * start [(0,0)]: starting coordinates to plot the boundaries from. If start=(100,100), the boundary will be calculated as usual but plotted starting at (100,100) instead of (0,0) * offset: background offset * xmax: maximum x-distance to consider as in the plume * kwargs: any other keyword argyments to pass to the PlumeModel.Background function Returns: Boundary: array[X, y positive boundary, y negative boundary] """ X = np.linspace(start[0], xmax, 100) Y = [] X_s = [] step = 10. for x in X: y = offset + start[1] x_shifted = x - start[0] y_shifted = y - start[1] if x_shifted >= 0: while not PlumeModel.Background( x_shifted, y_shifted, 1., 1., a, offset=offset, ** kwargs): y += step y_shifted = y - start[1] if y_shifted > 100.e3: break X_s.append(x) Y.append(y) X_points = np.array(X_s) y_plus = np.array(Y) y_minus = 2 * start[1] - np.array(Y) Boundary = np.array([X_points, y_plus, y_minus]) / 1000. return Boundary
def get_secondary_offset(self, overpass, wind, return_index=False, secondary_sources=None): offset_positions = [] if secondary_sources==None: secondary_sources = overpass.source.secondary for secondary in secondary_sources: # List [(x1, y1), (x2, y2), ...] for all points in the instance posns = [Geometry.CoordGeom(wind).coord_to_wind_basis(secondary.lat,secondary.lon,lat,lon) for (lat,lon) in zip(self.retrieval_latitude,self.retrieval_longitude)] # List of [V(x1, y1), V(x2, y2), ...] for all positions (xi, yi) in posns all_enhancements = [PlumeModel.V(x,y,wind.speed,1.,overpass.a) for (x,y) in posns] # Index of the maximum enhancement found in all_enhancements closest_index = all_enhancements.index(max(all_enhancements)) # (x, y) distance of the max enhancement with respect to the source. This way distances are measured from the center of the plume x_offset,y_offset = posns[closest_index] if return_index: offset_positions.append((x_offset, y_offset, closest_index)) else: offset_positions.append((x_offset, y_offset)) return offset_positions
def main(overpass, fname, **kwargs): params = defaults params.update(**kwargs) bg_kwargs = { 'background_factor': params.f_background, 'ymax_positive': params.y_max_positive, 'ymax_negative': params.y_max_negative, 'ymin_negative': params.y_min_negative, 'ymin_positive': params.y_min_positive, 'offset': params.offset, 'sign': params.direction } filt_args = { 'chi_squared_max': params.chi_squared_max, 'snr_strong_co2_min': params.snr_strong_co2_min, 'albedo_min': params.albedo_min, 'albedo_max': params.albedo_max, 'outcome_flags': params.outcome_flags, 'surface_pressure_min': params.surface_pressure_min, 'surface_pressure_max': params.surface_pressure_max } plume_args = dict(plume_factor=params.f_plume, xmax=params.x_max) print "Creating subplots" # Initialize gridspecs for the different panels obs_gs = gridspec.GridSpec(*params._shape) obs_gs.update(**params._obs_grid) mod_gs = gridspec.GridSpec(*params._shape) mod_gs.update(**params._mod_grid) cbar_gs = gridspec.GridSpec(*params._shape) cbar_gs.update(**params._cbar_grid) fig = plt.figure(figsize=params._size) h, w = params._shape half_w = params._obs_width # add subplots using gridspecs obs_ax = fig.add_subplot(obs_gs[:params._obs_height, :half_w]) mod_ax = fig.add_subplot(mod_gs[:int(h // 2), half_w:]) modfull_ax = fig.add_subplot(mod_gs[int(h // 2):, half_w:]) cbar_ax = fig.add_subplot(cbar_gs[params._cbar_height, :half_w]) # set tick locations if the step sizes are specified if params.x_step: dx = params.x_step xticks = np.arange(int(dx * math.ceil(params.xlim[0] / dx)), params.xlim[1] + 1., dx) obs_ax.set_xticks(xticks) mod_ax.set_xticks(xticks) modfull_ax.set_xticks(xticks) if params.y_step: dy = params.y_step yticks = np.arange(int(dy * math.ceil(params.ylim[0] / dy)), params.ylim[1], dy) yticks_full = np.arange(int(dy * math.ceil(params.ylim[0] / dy)), params.ylim[1] + 1., dy) obs_ax.set_yticks(yticks_full) mod_ax.set_yticks(yticks_full) modfull_ax.set_yticks(yticks) # add labels, titles, set fonts label_args = dict(fontsize=params._labelfont, fontname=params.font) obs_ax.set_xlabel(params._xlabel, **label_args) obs_ax.set_ylabel(params._ylabel, **label_args) modfull_ax.set_xlabel(params._xlabel, **label_args) mod_ax.tick_params('x', bottom='off', labelbottom='off') title_args = dict(fontsize=params._titlefont, fontname=params.font) obs_ax.set_title(params._obslabel, **title_args) mod_ax.set_title(params._modlabel, **title_args) # now start the real work, axes are set as much as we can right now a = params.stability if params.stability else overpass.a try: wind = getattr(overpass, params.wind_source) except AttributeError: raise AttributeError('Overpass has no wind source "%s"' % params.wind_source) except Exception: raise wind = wind.rotate(params.wind_adjustment) windspeed = Formats.windspeedformat % wind.speed wind_direction = Formats.winddirectionformat % wind.bearing fig.suptitle(params._title % (overpass.info, windspeed, wind_direction), **title_args) F = overpass.get_emissions(temporal_factors=params.temporal_factors) F.convert(Units._model_units) u = wind.speed free_secondary_emissions = [ src.get_emissions(overpass, temporal_factors=params.temporal_factors) for src in params.secondary_sources ] fixed_secondary_emissions = [ src.get_emissions(overpass, temporal_factors=params.temporal_factors) for src in params.fixed_secondary_sources ] all_emissions = [F] + free_secondary_emissions + fixed_secondary_emissions co2_var = "smoothed_{}_xco2" if params.smooth else "{}_xco2" co2_var = co2_var.format(params.bias_correction) print 'Running Model with parameters' model_results = ModelFit.Model( overpass, f_plume=params.f_plume, f_background=params.f_background, offset=params.offset, y_max_positive=params.y_max_positive, y_max_negative=params.y_max_negative, y_min_negative=params.y_min_negative, y_min_positive=params.y_min_positive, direction=params.direction, wind_adjustment=params.wind_adjustment, wind_sources=params.wind_source, smooth=params.smooth, surface_stability=a, stability=params.stability, temporal_factors=params.temporal_factors, bias_correction=params.bias_correction, LocalBackground=PlumeModel.InBackground, LocalInPlume=PlumeModel.InPlume, co2_source='xco2', custom_wind=None, snr_strong_co2_min=params.snr_strong_co2_min, chi_squared_max=params.chi_squared_max, albedo_min=params.albedo_min, albedo_max=params.albedo_max, outcome_flags=params.outcome_flags, surface_pressure_max=params.surface_pressure_max, surface_pressure_min=params.surface_pressure_min, background_average=params.background_average, secondary_sources=params.secondary_sources, fixed_secondary_sources=params.fixed_secondary_sources, x_max=params.x_max, scatter_plot=params.scatter_plot, force_winds=params.force_winds, units=params.units, sza_adjustments=params.sza_adjustments, weighted=params.weighted, uncertainty=params.uncertainty) _, _, n_plume, cor = model_results[0] if params.secondary_sources: coordinate = Geometry.CoordGeom(wind) posns_from_main = [(0, 0)] + [ coordinate.coord_to_wind_basis(overpass.lat, overpass.lon, second.lat, second.lon) for second in params.secondary_sources ] x = [pair[0] for pair in posns_from_main] y = [pair[1] for pair in posns_from_main] if params.plot_offset: x_center, y_center = np.average(np.array([x, y]), axis=1, weights=([F] + free_secondary_emissions)) x_from_center = [x0 - x_center for x0 in x] y_from_center = [y0 - y_center for y0 in y] # indices of top and bottom sources neg_offset = y_from_center.index(max(y_from_center)) pos_offset = y_from_center.index(min(y_from_center)) ## -1*y because the y in wind-basis is left-handed but we want the ## plots to be the regular right-handed system # offset of bottom source on plot pos_start = (x_from_center[pos_offset], -1 * y_from_center[pos_offset]) # offset of top source on plot neg_start = (x_from_center[neg_offset], -1 * y_from_center[neg_offset]) else: x_center, y_center = 0, 0 pos_start = (0, 0) neg_start = (0, 0) else: x_center, y_center = (0, 0) pos_start = (0, 0) neg_start = (0, 0) c_offset = (x_center, y_center) x_min = params.xlim[0] * 1000. x_max = params.xlim[1] * 1000. y_min = params.ylim[0] * 1000. y_max = params.ylim[1] * 1000. print 'Opening full file for making plots' data = File.full(overpass) def distance(vertices, tlat, tlon, wind, shift=(0, 0)): """Takes in an array shape (4, 2) of [ [lon1 lat1], [lon2 lat2],...] and returns an equivalenty shaped array of [ [x1 y1], [x2 y2], ...] As measured by in the direction of the wind""" distance_array = [] for i in range(4): lon, lat = vertices[i] x, y = Geometry.CoordGeom(wind).coord_to_wind_basis( tlat, tlon, lat, lon) distance_array.append((x - shift[0], -(y - shift[1]))) return np.array(Geometry.convex_hull(distance_array)) distances = np.array([ distance(data.retrieval_vertex_coordinates[j], overpass.lat, overpass.lon, wind, shift=c_offset) for j in range(len(data.retrieval_vertex_coordinates)) ]) background = File.File() background_k = [] plume = File.File() else_data = File.File() failed_quality_data = File.File() x_offset, y_offset = data.get_offset(overpass, wind) if params.secondary_sources: secondary_offsets = data.get_secondary_offset( overpass, wind, secondary_sources=params.secondary_sources) print 'Classifying points' verts = [] observed_xco2 = [] model_xco2 = [] verts_qf = [] observed_xco2_qf = [] model_xco2_qf = [] all_sources = params.secondary_sources + params.fixed_secondary_sources for i in range(len(data)): coordinate = Geometry.CoordGeom(wind) rlat = data.retrieval_latitude[i] rlon = data.retrieval_longitude[i] x, y = coordinate.coord_to_wind_basis(overpass.lat, overpass.lon, rlat, rlon) dist = Geometry.CoordGeom.cartesian_distance((x, y), (x_offset, y_offset)) in_background = PlumeModel.InBackground(x, y, dist, u, 1.0, a, **bg_kwargs) in_plume = PlumeModel.InPlume(x, y, u, F, a, **plume_args) for j, second in enumerate(params.secondary_sources): xs, ys = coordinate.coord_to_wind_basis(second.lat, second.lon, rlat, rlon) dx, dy = secondary_offsets[j] secondary_dist = Geometry.CoordGeom.cartesian_distance((xs, ys), (dx, dy)) in_secondary_background = PlumeModel.InBackground( xs, ys, secondary_dist, u, 1., a, **bg_kwargs) in_secondary_plume = PlumeModel.InPlume(xs, ys, u, 1., a, **plume_args) in_background = in_background and in_secondary_background in_plume = in_plume or in_secondary_plume if in_plume: if data.quality(i, **filt_args): plume.append(data, i) else: failed_quality_data.append(data, i) elif in_background: if data.quality(i, **filt_args): background.append(data, i) background_k.append(data.k[i]) else: failed_quality_data.append(data, i) else: if data.quality(i, **filt_args): else_data.append(data, i) else: failed_quality_data.append(data, i) x_shifted = x - c_offset[0] y_shifted = y + c_offset[1] if y_min <= y_shifted <= y_max and x_min <= x_shifted <= x_max: lat_row = int((y_max - y_shifted) // 1000) lon_col = int((x_shifted - x_min) // 1000) if params.sza_adjustments: sza = Geometry.SZA(data, wind) enhancement = sza.V(x, y, u, F, a, i) else: enhancement = PlumeModel.V(x, y, u, F, a) for ind, child in enumerate(all_sources): F_second = all_emissions[ind + 1] x_p, y_p = coordinate.coord_to_wind_basis( overpass.lat, overpass.lon, child.lat, child.lon) xs, ys = (x - x_p, y - y_p) if params.sza_adjustments: second_enhancement = sza.V(xs, ys, u, F_second, a, i) else: second_enhancement = PlumeModel.V(xs, ys, u, F_second, a) enhancement += second_enhancement if data.quality(i, **filt_args): model_xco2.append(enhancement) verts.append(distances[i]) observed_xco2.append(data[co2_var][i]) else: model_xco2_qf.append(enhancement) verts_qf.append(distances[i]) observed_xco2_qf.append(data[co2_var][i]) if params.background_average: background_mean = background_average else: background_mean = np.mean(background[co2_var]) background_mean_k = np.mean(background_k) background_n = len(background) model_xco2 = np.array(model_xco2) model_xco2 /= (background_mean * background_mean_k) model_xco2 += 1. model_xco2_qf = np.array(model_xco2_qf) model_xco2_qf /= (background_mean * background_mean_k) model_xco2_qf += 1. verts = np.array(verts) verts_qf = np.array(verts_qf) observed_xco2 = np.array(observed_xco2) / float(background_mean) observed_xco2_qf = np.array(observed_xco2_qf) / float(background_mean) print 'Making grid plot' observed_coll = PolyCollection(verts / 1000., array=observed_xco2, edgecolors='none', cmap=params._cmap) observed_coll_qf = PolyCollection(verts_qf / 1000., array=observed_xco2_qf, edgecolors='none', cmap=params._cmap, alpha=params.opacity) model_coll = PolyCollection(verts / 1000., array=model_xco2, edgecolors='none', cmap=params._cmap) model_coll_qf = PolyCollection(verts / 1000., array=model_xco2_qf, edgecolors='none', cmap=params._cmap, alpha=params.opacity) collections = [observed_coll, observed_coll_qf, model_coll, model_coll_qf] for coll in collections: coll.set_clim(*params.clim) obs_ax.add_collection(observed_coll) obs_ax.add_collection(observed_coll_qf) mod_ax.add_collection(model_coll) mod_ax.add_collection(model_coll_qf) cbarticks = np.linspace(params.clim[0], params.clim[1], 5) cbar = fig.colorbar(observed_coll, cax=cbar_ax, orientation='horizontal', use_gridspec=True, ticks=cbarticks) cbar.set_label(params._cbarlabel, **label_args) cbar.ax.set_xticklabels([params._ctick_fmt.format(x) for x in cbarticks], fontname=params.font) bg_mean = Formats.ppmformat % background_mean scor = Formats.ppmformat % cor # add model to plot print 'Calculating model values for grid plot' position_from_main = [(0, 0)] coordinate = Geometry.CoordGeom(wind) all_sources = params.secondary_sources + params.fixed_secondary_sources for second in all_sources: posn = coordinate.coord_to_wind_basis(overpass.lat, overpass.lon, second.lat, second.lon) position_from_main.append(posn) position_from_center = [(x - c_offset[0], y - c_offset[1]) for (x, y) in position_from_main] x_ax_absolute = np.arange(x_min, x_max + params._dx, params._dx) y_ax_absolute = np.arange(y_min, y_max + params._dy, params._dy) y_ax_absolute -= 0.5 * params._dy model_array = np.ones((len(y_ax_absolute), len(x_ax_absolute))) x_ax_plot = (x_ax_absolute - 0.5 * params._dx) / 1000. y_ax_plot = (y_ax_absolute - 0.5 * params._dx) / 1000. normalize = background_mean * background_mean_k for ind in range(len(position_from_main)): x_rel, y_rel = position_from_center[ind] x_ax = x_ax_absolute - x_rel y_ax = y_ax_absolute + y_rel F = all_emissions[ind] rows, cols = model_array.shape for row in range(rows): for col in range(cols): x, y = x_ax[col], y_ax[row] enhancements = PlumeModel.V(x, y, u, F, a) / normalize model_array[row, col] += enhancements mask_value = 1 + (params.clim[1] - 1) * (params._decay_threshold) model_array = np.ma.masked_less_equal(model_array, mask_value) modfull_ax.pcolormesh(x_ax_plot, y_ax_plot, model_array, cmap=params._cmap, vmin=params.clim[0], vmax=params.clim[1]) modfull_ax.grid(True) obs_ax.text(params._paneltext_x, params._paneltext_y, 'a = {}\nBackground = {} ppm'.format(a, bg_mean), fontsize=params._textfont, fontname=params.font, transform=obs_ax.transAxes) obs_ax.text(params._paneltext_xspace, 1 - params._paneltext_y, 'Number of points in plume: %s\nR = %s' % (n_plume, scor), fontsize=params._textfont, verticalalignment='top', fontname=params.font, transform=obs_ax.transAxes) bounds = boundaries.Boundaries(obs_ax, mod_ax, modfull_ax) bounds.plot(a, pos_start, neg_start, params.plume_thresholds, params.background_thresholds, xmax=1000 * params.xlim[1], ymax=params.ylim[1], offset=params.offset) grid_axes = (obs_ax, mod_ax, modfull_ax) axes = (obs_ax, mod_ax, modfull_ax) for gax in grid_axes: gax.set_xlim(*params.xlim) gax.set_ylim(*params.ylim) gax.grid(True) for ax in grid_axes: Formats.set_tickfont(ax) fig.savefig(fname, dpi=params._DPI, bbox_inches='tight') print 'Saved figure as', fname
def best_winds(overpass, adjust_min=-10, adjust_max=10, tolerance=2.5, f_plume=0.10, f_background=0.01, offset=3000., y_max_positive=50.e3, y_max_negative=50.e3, y_min_negative=0., y_min_positive=0., direction='y', wind_adjustment=0., smooth=False, surface_stability=True, stability="new", temporal_factors=False, bias_correction='corrected', LocalBackground=PlumeModel.InBackground, LocalInPlume=PlumeModel.InPlume, co2_source='xco2', snr_strong_co2_min=None, chi_squared_max=None, albedo_min=None, albedo_max=None, outcome_flags={1, 2}, surface_pressure_min=None, surface_pressure_max=None, background_average=None, secondary_sources=False, fixed_secondary_sources=[], x_max=75.e3, scatter_plot=False, force_winds=None, units=Units.output_units, sza_adjustments=True, output_file=False, weighted=False, run_model=True): """Searches for the wind adjustment that gives the highest correlation. Searches between increasingly narrow wind adjustments for the wind direction that gives the highest correlation. It prints out the adjustment, and then runs the full model function with this best adjustment value (if run_model==True). Searches for 10 degrees by default outside the limits set by ECMWF and MERRA. This can be adjsuted with the adjust_min and adjust_max arguments. tolerance is the difference between the outside limits of adjusting the wind at which to consider the search accurate enough and return the value. """ merra = overpass.MERRA ecmwf = overpass.ECMWF average_wind = overpass.Average direction_min = min(merra.bearing, ecmwf.bearing) + adjust_min direction_max = max(merra.bearing, ecmwf.bearing) + adjust_max starting_wind_speed = average_wind.speed adjust_min = direction_min - average_wind.bearing adjust_max = direction_max - average_wind.bearing first_adjustments = numpy.linspace(adjust_min, adjust_max, 9) ## First all the default arguments are dealt with, and then move on to actually classifying data and fitting model # Args to pass to full_file.quality(i,**kwargs) method quality_args = { 'chi_squared_max': chi_squared_max, 'snr_strong_co2_min': snr_strong_co2_min, 'albedo_min': albedo_min, 'albedo_max': albedo_max, 'outcome_flags': outcome_flags, 'surface_pressure_min': surface_pressure_min, 'surface_pressure_max': surface_pressure_max } bg_kwargs = { 'background_factor': f_background, 'ymax_positive': y_max_positive, 'ymax_negative': y_max_negative, 'ymin_negative': y_min_negative, 'ymin_positive': y_min_positive, 'offset': offset, 'sign': direction } plume_kwargs = {'xmax': x_max, 'plume_factor': f_plume} # if secondary_sources is passed a collection of sources, it uses those as the secondary sources if hasattr(secondary_sources, "__iter__"): secondary = secondary_sources elif secondary_sources == True: secondary = overpass.source.secondary else: secondary = [] # get emissions for the overpass and make sure they're in g/s F = overpass.get_emissions(temporal_factors=temporal_factors) F.convert(Units._model_units) secondary_emissions = [ src.get_emissions(overpass, temporal_factors=temporal_factors)( Units._model_units) for src in secondary ] all_emissions = [F] + secondary_emissions # atmospheric stability parameter if surface_stability == True: if stability == "new": a = overpass.a else: a = overpass.a_old elif surface_stability == False: a = overpass.a_elevated else: a = surface_stability print "Using reported emissions: {0}".format(F.convert_cp(units)) print "Using atmospheric stability paramter a={0}".format(a) print("Opening and reading full file") full_file = File.full(overpass) # bias correction: if bias_correction not in File.allowed_bias_correction: raise ValueError( "bias correction must be one of {0}; given {1}".format( ', '.join(File.allowed_bias_correction), bias_correction)) if not (co2_source == 'xco2' or co2_source == 'co2_column'): raise ValueError( "co2_source must be one of 'xco2' or 'co2_column' not '{0}'". format(co2_source)) co2 = "{0}_{1}".format(bias_correction, co2_source) if smooth: co2 = 'smoothed_' + co2 if sza_adjustments: print "Assigning a sign to the sensor zenith angle" full_file.sign_zenith_angle(overpass) # filtered_data has the same fields as ModelData but only the points that pass the quality filter filtered_data = full_file.filter(**quality_args) print "Adjusting the winds" delta = adjust_max - adjust_min while delta > tolerance: correlations = [] adjustments = numpy.linspace(adjust_min, adjust_max, 9) WindSources = [average_wind.rotate(x) for x in adjustments] print "Adjustments" print adjustments in_plume_objects = [] background_objects = [] model_enhancement_lists = [] model_alpha_lists = [] fixed_enhancement_lists = [] for wind in WindSources: height_list = [overpass.height ] + [source.height for source in secondary] mean_height = numpy.average(height_list, weights=all_emissions) wind.height = mean_height plume_data = File.File() background_data = File.File() model_enhancements = [] model_alpha = [] fixed_enhancements = [] # we already have a, F determined u = wind.speed # the offset (x,y) in wind basis components from the source to the center plume (highest enhancement) x_offset, y_offset = filtered_data.get_offset(overpass, wind) secondary_offsets = filtered_data.get_secondary_offset( overpass, wind, secondary_sources=secondary) fixed_emissions = [ fixed.get_emissions(overpass, temporal_factors=temporal_factors)( Units._model_units) for fixed in fixed_secondary_sources ] for i in range(len(filtered_data)): coordinate = Geometry.CoordGeom(wind) x, y = coordinate.coord_to_wind_basis( overpass.lat, overpass.lon, filtered_data.retrieval_latitude[i], filtered_data.retrieval_longitude[i]) dist = Geometry.CoordGeom.cartesian_distance( (x, y), (x_offset, y_offset)) sza = Geometry.SZA(filtered_data, wind) in_background = PlumeModel.InBackground( x, y, dist, u, F, a, **bg_kwargs) in_plume = PlumeModel.InPlume(x, y, u, F, a, **plume_kwargs) if secondary_sources: for ind, secondary_src in enumerate(secondary): SZA_secondary = Geometry.SZA(filtered_data, wind) xs, ys = Geometry.CoordGeom(wind).coord_to_wind_basis( secondary_src.lat, secondary_src.lon, filtered_data.retrieval_latitude[i], filtered_data.retrieval_longitude[i]) secondary_offset = secondary_offsets[ind] secondary_dist = Geometry.CoordGeom.cartesian_distance( (xs, ys), secondary_offset) in_secondary_plume = PlumeModel.InPlume( xs, ys, u, 1., a, **plume_kwargs) in_plume = in_plume or in_secondary_plume in_secondary_background = PlumeModel.InBackground( xs, ys, secondary_dist, u, 1., a, **bg_kwargs) in_background = in_background and in_secondary_background if in_background: background_data.append(filtered_data, i) if in_plume: total_enhancement = 0. #enhancements from all sources plume_data.append(filtered_data, i) # get enhancement from main source, then add secondary sources if sza_adjustments: main_enhancement = sza.V(x, y, u, F, a, i) else: main_enhancement = PlumeModel.V(x, y, u, F, a) alpha = [main_enhancement / F] total_enhancement += main_enhancement for ind, secondary_src in enumerate(secondary): xs, ys = Geometry.CoordGeom(wind).coord_to_wind_basis( secondary_src.lat, secondary_src.lon, filtered_data.retrieval_latitude[i], filtered_data.retrieval_longitude[i]) F_secondary = secondary_emissions[ind] if sza_adjustments: secondary_enhancement = sza.V( xs, ys, u, F_secondary, a, i) else: secondary_enhancement = PlumeModel.V( xs, ys, u, F_secondary, a) total_enhancement += secondary_enhancement alpha.append(secondary_enhancement / F_secondary) model_enhancements.append([total_enhancement]) model_alpha.append(alpha) fixed_enhancement = 0. for ind, fixed in enumerate(fixed_secondary_sources): xs, ys = Geometry.CoordGeom(wind).coord_to_wind_basis( fixed.lat, fixed.lon, filtered_data.retrieval_latitude[i], filtered_data.retrieval_longitude[i]) F_fixed = fixed_emissions[ind] if sza: source_enhancement = sza.V(xs, ys, u, F_fixed, a, i) else: source_enhancement = PlumeModel.V( xs, ys, u, F_fixed, a) fixed_enhancement += source_enhancement fixed_enhancements.append(fixed_enhancement) in_plume_objects.append(plume_data) background_objects.append(background_data) model_enhancement_lists.append(numpy.array(model_enhancements)) model_alpha_lists.append(numpy.array(model_alpha)) fixed_enhancement_lists.append(numpy.array(fixed_enhancements)) for (k, wind) in enumerate(WindSources): if len(in_plume_objects[k]) <= 2: correlations.append(0) else: try: fixed_enhance = fixed_enhancement_lists[k] / numpy.mean( background_objects[k].k) cor = numpy.corrcoef( in_plume_objects[k][co2] - fixed_enhance, model_enhancement_lists[k].flatten())[0, 1] except: raise correlations.append(cor) max_cor = max(correlations) max_index = correlations.index(max_cor) adjust_max = adjustments[min(8, max_index + 3)] adjust_min = adjustments[max(0, max_index - 3)] delta = abs(adjust_max - adjust_min) print "Maximum Correlation: {0}; with wind adjustment {1} relative to the average\n\n".format( max_cor, adjustments[max_index]) if run_model: print "Running the model with the best wind..." return Model(overpass, f_plume=f_plume, f_background=f_background, offset=offset, y_max_positive=y_max_positive, y_max_negative=y_max_negative, y_min_negative=y_min_negative, y_min_positive=y_min_positive, direction=direction, wind_adjustment=wind_adjustment, wind_sources=None, smooth=smooth, surface_stability=surface_stability, stability=stability, temporal_factors=temporal_factors, bias_correction=bias_correction, LocalBackground=LocalBackground, LocalInPlume=LocalInPlume, co2_source=co2_source, snr_strong_co2_min=snr_strong_co2_min, chi_squared_max=chi_squared_max, albedo_min=albedo_min, albedo_max=albedo_max, outcome_flags=outcome_flags, background_average=background_average, secondary_sources=secondary, x_max=x_max, scatter_plot=scatter_plot, force_winds=[WindSources[max_index]], units=units, sza_adjustments=sza_adjustments, weighted=weighted, fixed_secondary_sources=fixed_secondary_sources)
def Model(overpass, f_plume=0.10, f_background=0.01, offset=3000., y_max_positive=50.e3, y_max_negative=50.e3, y_min_negative=0., y_min_positive=0., direction='y', wind_adjustment=0., wind_sources=['Average'], smooth=False, surface_stability=True, stability="new", temporal_factors=False, bias_correction='corrected', LocalBackground=PlumeModel.InBackground, LocalInPlume=PlumeModel.InPlume, co2_source='xco2', custom_wind=None, snr_strong_co2_min=None, chi_squared_max=None, albedo_min=None, albedo_max=None, outcome_flags={1, 2}, surface_pressure_max=None, surface_pressure_min=None, background_average=None, secondary_sources=False, fixed_secondary_sources=[], x_max=75.e3, scatter_plot=False, force_winds=None, units=Units.output_units, sza_adjustments=True, weighted=False, uncertainty=True): """Computes model enhancements for an overpass, and compares them to observed data. The behaviour is specified by the many default arguments. This function does everything needed including calculating model enhancements, but delegates the actual comparison to the ModelFunctions module. See the keyword list document for an explanation of the optional arguments """ ## First all the default arguments are dealt with, and then move on to actually classifying data and fitting model # Args to pass to full_file.quality(i,**kwargs) method quality_args = { 'chi_squared_max': chi_squared_max, 'snr_strong_co2_min': snr_strong_co2_min, 'albedo_min': albedo_min, 'albedo_max': albedo_max, 'outcome_flags': outcome_flags, 'surface_pressure_min': surface_pressure_min, 'surface_pressure_max': surface_pressure_max } bg_kwargs = { 'background_factor': f_background, 'ymax_positive': y_max_positive, 'ymax_negative': y_max_negative, 'ymin_negative': y_min_negative, 'ymin_positive': y_min_positive, 'offset': offset, 'sign': direction } plume_kwargs = {'xmax': x_max, 'plume_factor': f_plume} if secondary_sources == False: secondary = [] secondary_sources = False elif secondary_sources == True: secondary = overpass.source.secondary secondary_sources = True elif hasattr(secondary_sources, "__iter__"): secondary = secondary_sources secondary_sources = True else: raise TypeError("Invalid argument '{}' for secondary_sources.\ Must be True, False, or iterable collection of PointSources".format( secondary_sources)) # bias correction: if bias_correction not in File.allowed_bias_correction: raise ValueError( "bias correction must be one of {0}; given {1}".format( ', '.join(File.allowed_bias_correction), bias_correction)) if not (co2_source == 'xco2' or co2_source == 'co2_column'): raise ValueError( "co2_source must be one of 'xco2' or 'co2_column' not '{0}'". format(co2_source)) co2 = "{0}_{1}".format(bias_correction, co2_source) if smooth: co2 = 'smoothed_' + co2 # get emissions for the overpass and make sure they're in g/s F = overpass.get_emissions(temporal_factors=temporal_factors) F.convert(Units._model_units) # atmospheric stability parameter if surface_stability == True: if stability == "new": a = overpass.a elif stability == "old": a = overpass.a_old elif surface_stability == False: a = overpass.a_elevated else: a = surface_stability secondary_emissions = [ second.get_emissions(overpass)(Units._model_units) for second in secondary ] all_emissions = [F(units)] + [em(units) for em in secondary_emissions] total_emissions = F(units) for second in secondary_emissions: total_emissions += second(units) emissions_info = ', '.join([str(emi) for emi in all_emissions]) sources_info = ', '.join( [src for src in [overpass.short] + [s.short for s in secondary]]) print "Using reported emissions:", emissions_info print "For sources:", sources_info print "Total emissions:", total_emissions print "Using atmospheric stability parameter a={0}".format(a) print "Opening and reading full file" full_file = File.full(overpass) # force_winds is an override for forcing the model to run with a given list of Wind instances. # Useful for running the model with many wind adjustments if force_winds is None: all_winds = PST.AllWinds(overpass) try: WindSources, wind_labels = all_winds.parse(wind_sources) WindSources = [wnd.rotate(wind_adjustment) for wnd in WindSources] except Exception as exc: WindSources, wind_labels = [], [] print "Exception raised and ignored:", exc try: custom_winds, custom_labels = all_winds.add_custom(custom_wind) except ValueError as ve: print "custom_wind value was not valid. See message:", ve except Exception as sxc: print "Unexpected error occured:", sxc else: WindSources.extend(custom_winds) wind_labels.extend(custom_labels) else: WindSources = force_winds wind_labels = [str(wind) for wind in force_winds] if sza_adjustments: print "Assigning a sign to the sensor zenith angle" full_file.sign_zenith_angle(overpass) # filtered_data has the same fields as full_file but only the points that pass the quality filter filtered_data = full_file.filter(**quality_args) print "Classifying points" in_plume_objects = [] background_objects = [] model_enhancement_lists = [] model_alpha_lists = [] fixed_enhancement_lists = [] for wind in WindSources: # set wind.height to the weighted (by emissions) average height height_list = [overpass.height ] + [source.height for source in secondary] mean_height = numpy.average(height_list, weights=all_emissions) wind.height = mean_height plume_data = File.File() background_data = File.File() model_enhancements = [] # will be column vector [ [V1], [V2], ... ] model_alpha = [] # will be matrix A from math docs fixed_enhancements = [] # list of enhancements from fixed sources # we already have a, F determined u = wind.speed # the offset (x,y) in wind basis components from the source to the center plume (highest enhancement) x_offset, y_offset = filtered_data.get_offset(overpass, wind) # same as above offset but for each secondary source secondary_offsets = filtered_data.get_secondary_offset( overpass, wind, secondary_sources=secondary) # emissons of all the fixed secondary sources fixed_emissions = [ fixed.get_emissions(overpass, temporal_factors=temporal_factors)( Units._model_units) for fixed in fixed_secondary_sources ] for i in range(len(filtered_data)): coordinate = Geometry.CoordGeom(wind) x, y = coordinate.coord_to_wind_basis( overpass.lat, overpass.lon, filtered_data.retrieval_latitude[i], filtered_data.retrieval_longitude[i]) dist = Geometry.CoordGeom.cartesian_distance((x, y), (x_offset, y_offset)) sza = Geometry.SZA(filtered_data, wind) # check if point is in background or in plume, including secondary sources in_background = PlumeModel.InBackground(x, y, dist, u, F, a, **bg_kwargs) in_plume = PlumeModel.InPlume(x, y, u, F, a, **plume_kwargs) if secondary_sources: for ind, secondary_src in enumerate(secondary): SZA_secondary = Geometry.SZA(filtered_data, wind) xs, ys = Geometry.CoordGeom(wind).coord_to_wind_basis( secondary_src.lat, secondary_src.lon, filtered_data.retrieval_latitude[i], filtered_data.retrieval_longitude[i]) secondary_dist = Geometry.CoordGeom.cartesian_distance( (xs, ys), secondary_offsets[ind]) in_secondary_plume = PlumeModel.InPlume( xs, ys, u, 1., a, **plume_kwargs) in_secondary_background = PlumeModel.InBackground( xs, ys, secondary_dist, u, 1., a, **bg_kwargs) # logic for when to consider it in-plume/background with secondary sources in_plume = in_plume or in_secondary_plume in_background = in_background and in_secondary_background if in_background: background_data.append(filtered_data, i) if in_plume: total_enhancement = 0. #enhancements from all sources plume_data.append(filtered_data, i) # get enhancement from main source, then add secondary sources if sza_adjustments: main_enhancement = sza.V(x, y, u, F, a, i) else: main_enhancement = PlumeModel.V(x, y, u, F, a) alpha = [main_enhancement / F] total_enhancement += main_enhancement for ind, secondary_src in enumerate(secondary): xs, ys = Geometry.CoordGeom(wind).coord_to_wind_basis( secondary_src.lat, secondary_src.lon, filtered_data.retrieval_latitude[i], filtered_data.retrieval_longitude[i]) F_secondary = secondary_emissions[ind] if sza_adjustments: secondary_enhancement = sza.V(xs, ys, u, F_secondary, a, i) else: secondary_enhancement = PlumeModel.V( xs, ys, u, F_secondary, a) total_enhancement += secondary_enhancement alpha.append(secondary_enhancement / F_secondary) model_enhancements.append([total_enhancement]) model_alpha.append(alpha) fixed_enhancement = 0. for ind, fixed in enumerate(fixed_secondary_sources): xs, ys = Geometry.CoordGeom(wind).coord_to_wind_basis( fixed.lat, fixed.lon, filtered_data.retrieval_latitude[i], filtered_data.retrieval_longitude[i]) F_fixed = fixed_emissions[ind] if sza: source_enhancement = sza.V(xs, ys, u, F_fixed, a, i) else: source_enhancement = PlumeModel.V( xs, ys, u, F_fixed, a) fixed_enhancement += source_enhancement fixed_enhancements.append(fixed_enhancement) in_plume_objects.append(plume_data) background_objects.append(background_data) model_enhancement_lists.append(numpy.array(model_enhancements)) model_alpha_lists.append(numpy.array(model_alpha)) fixed_enhancement_lists.append(numpy.array(fixed_enhancements)) all_results = [] for (k, wind) in enumerate(WindSources): print '' print wind_labels[k] defaults = ModelFunctions.results_defaults() defaults.co2_attribute = co2 defaults.co2_name = co2_source defaults.output_units = units in_plume_objects[k].xco2_uncert = numpy.array( in_plume_objects[k].xco2_uncert) try: results = ModelFunctions.interpret_results( in_plume_objects[k], background_objects[k], model_enhancement_lists[k], model_alpha_lists[k], fixed_enhancement_lists[k], overpass, wind, defaults, float(total_emissions), weights=weighted, background_average=background_average, uncertainty=uncertainty) # results is tuple (scale factor, estimated emissions, number of plume points, correlation) except: results = defaults.null_value # print "An exception was raised: see message following" # print exc raise all_results.append(results) if secondary_sources: print '\nEmissions are ordered as', ', '.join( [overpass.short] + map(lambda s: s.short, secondary)) print '' # make scatter plot if it's asked for, using last wind source if scatter_plot: if type(scatter_plot) != str: raise TypeError( "Keyword scatter_plot must be a string corresponding to a valid path. Given '{0}'" .format(scatter_plot)) try: sc_location = ModelFunctions.make_scatter_plot( numpy.array((model_enhancement_lists[k]) / numpy.mean(background_objects[k].k)).flatten(), in_plume_objects[k][co2], scatter_plot) except: raise else: print "Scatter plot made and saved as {0}".format(scatter_plot) return all_results
def Plot(overpass, filename, min_xco2=KML.default_cmin, max_xco2=KML.default_cmax, secondary_sources=[], arrow_scale=0.02, stability="new", wind_source="Average", wind_adjustment=0., f_plume=0.10, f_background=0.01, offset=3.e3, y_max_positive=50.e3, y_max_negative=50.e3, y_min_positive=0., y_min_negative=0., direction='y', x_max=75.e3, snr_strong_co2_min=None, chi_squared_max=None, albedo_min=None, albedo_max=None, outcome_flags={1,2}, surface_pressure_min=None, surface_pressure_max=None, lon_thresh=0.5, lat_thresh=0.5, bias_correction='corrected', npoints=500, width=0.5, height=None, labelsize=Colours.default_fontsize, x=0.05, wind_arrow_sources=['MERRA', 'ECMWF', 'GEM', 'Average']): """Creates a KML file for an overpass with parameters as specified. If xco2 limits are default values, no new colour bar is made. Otherwise, will make a new colour bar and save it alongside the kml on the server. Parameters for defining the plume and background are included because the extended data for each polygon contains a flag for whether each point is in the plume, in the background, or in neither. Polygons are plotted for 'npoints' points on either side of the box defined by the lat and lon thresholds. """ print "Making KML for",overpass.info if overpass.FullFile=="": raise ValueError("Corrupt overpass file") # Bundle together arguments that need to be passed to plume model functions # For determining background: bg_kwargs = {'background_factor':f_background, 'ymax_positive':y_max_positive, 'ymax_negative':y_max_negative, 'ymin_negative':y_min_negative, 'ymin_positive':y_min_positive, 'offset':offset, 'sign':direction } # For determining in plume points: plume_kwargs = {'plume_factor':f_plume, 'xmax':x_max } # Args to pass to full_file.quality(i,**kwargs) method quality_args = {'chi_squared_max':chi_squared_max, 'snr_strong_co2_min':snr_strong_co2_min, 'albedo_min':albedo_min, 'albedo_max':albedo_max, 'outcome_flags':outcome_flags, 'surface_pressure_min':surface_pressure_min, 'surface_pressure_max':surface_pressure_max } # parse bias_correction argument if bias_correction not in File.allowed_bias_correction: type_err = "'bias_correction' argument must be one of %s" raise TypeError(type_err % ', '.join(File.allowed_bias_correction)) # The co2 variable to read; ex, 'corrected_xco2', 'S31_xco2' co2 = bias_correction + "_xco2" try: wind = getattr(overpass,wind_source) except AttributeError: raise AttributeError("'wind_source' must be one of %s" % ', '.join(valid_winds)) # make arrow_lat, arrow_lon the average lat, lon weighted by emissions if secondary_sources: secondary_lats = [second.lat for second in secondary_sources] secondary_lons = [second.lon for second in secondary_sources] all_lats = [overpass.lat] + secondary_lats all_lons = [overpass.lon] + secondary_lons all_emissions = [overpass.get_emissions()(Units._model_units)] + \ [second.get_emissions(overpass)(Units._model_units) for second in secondary_sources] arrow_lat = numpy.average(all_lats, weights=all_emissions) arrow_lon = numpy.average(all_lons, weights=all_emissions) print "Using coordinate ({0}, {1}) for wind arrows".format(arrow_lat, arrow_lon) else: arrow_lat, arrow_lon = overpass.lat, overpass.lon # Use KML module to make the KML kml_description = overpass.strftime(KML.modis_description_fmt) Map = KML.KML(description=kml_description) oco2_name = "OCO-2 Data" oco2_description = "Data from OCO-2 v7 Full Files with '%s' "\ "bias correction" % bias_correction OCO2_folder = KML.Folder(oco2_name, oco2_description) Map.add_folder(OCO2_folder) warn_level_folders = {} # dictionary to be able to update the folders for wl in range(21): folder_name = "Warn Level %d" % wl folder_description = "Lite File Warn Level %d" % wl folder = KML.Folder(folder_name, folder_description) OCO2_folder.add_object(folder) warn_level_folders[wl]=folder wind_arrows = KML.Overlay.wind_arrow(overpass,lat=arrow_lat, lon=arrow_lon,size=arrow_scale, sources=wind_arrow_sources) height = height if height else (1.1/5.)*width cbar = Colours.colour_bar('', cmin=min_xco2, cmax=max_xco2, fontsize=labelsize) colour_scale = KML.Overlay.colour_scale(cbar, width=width, height=height, x=x) Map.add_object(wind_arrows) Map.add_object(colour_scale) print('Opening Full File; Performing Bias Correction') full_file = File.full(overpass) lite_file = File.lite(overpass) # warn_dict maps a sounding id to a warn level warn_dict = {lite_file.sounding_id[n]:lite_file.warn_level[n] for n in range(len(lite_file))} lon = overpass.lon lat = overpass.lat close_indices = [] for i in range(len(full_file)): lon_i = full_file.retrieval_longitude[i] lat_i = full_file.retrieval_latitude[i] dlon = abs(lon_i - overpass.lon)%360 dlat = abs(lat_i - overpass.lat)%360 if dlon<=lon_thresh and dlat<=lat_thresh: close_indices.append(i) i+=1 # (k_min, k_max) are limits on what points to plot k_min = max(0,close_indices[0]-npoints) k_max = min(len(full_file),close_indices[-1]+npoints) # plume-model parameters u = wind.speed F = 1.0 # actual emissions are not important for background a = overpass.a if stability=="new" else overpass.a_old x_offset, y_offset = full_file.get_offset(overpass, wind) secondary_offsets = full_file.get_secondary_offset(overpass, wind, secondary_sources=secondary_sources) print('Adding polygons to KML File') count_plume_points = 0 count_bg_points = 0 for k in range(k_min,k_max): lats = full_file.retrieval_vertex_latitude[k,0,:] lons = full_file.retrieval_vertex_longitude[k,0,:] sounding_xco2 = full_file.corrected_xco2[k] sounding_id = full_file.id[k] sounding_lat = full_file.retrieval_latitude[k] sounding_lon = full_file.retrieval_longitude[k] sounding_colour = Colours.Colour(sounding_xco2, vmin=min_xco2, vmax=max_xco2) if sounding_id in warn_dict: warn_level = warn_dict[sounding_id] else: warn_level=20 point = Geometry.CoordGeom(wind) sounding_x, sounding_y = point.coord_to_wind_basis(lat, lon, sounding_lat, sounding_lon) data = KML.Data() data.get_data(k, full_file) wl_data = data.add_new_data('Warn Level',warn_level) posn_data = data.add_new_data('Wind Basis Coord',(sounding_x,sounding_y)) data.add_new_data('Full File', os.path.split(overpass.FullFile)[1]) data.add_new_data('Lite File', os.path.split(overpass.LiteFile)[1]) data.add_new_data('Observation Mode', overpass.observation_mode) data.add_new_data('Latitude', sounding_lat) data.add_new_data('Longitude', sounding_lon) dist = ((sounding_x-x_offset)*(sounding_x-x_offset) + (sounding_y-y_offset)*(sounding_y-y_offset))**0.5 in_plume = PlumeModel.InPlume(sounding_x, sounding_y, u, F, a, **plume_kwargs) in_background = PlumeModel.InBackground(sounding_x, sounding_y, dist, u, F, a, **bg_kwargs) for (ind, second) in enumerate(secondary_sources): x0,y0 = secondary_offsets[ind] xs,ys = point.coord_to_wind_basis(second.lat, second.lon, sounding_lat, sounding_lon) second_dist = point.cartesian_distance((xs,ys),(x0,y0)) in_secondary_plume = PlumeModel.InPlume(xs, ys, u, 1.0, a, **plume_kwargs) in_secondary_bg = PlumeModel.InBackground(xs, ys, second_dist, u, 1.0, a, **bg_kwargs) in_plume = in_plume or in_secondary_plume in_background = in_background and in_secondary_bg if in_plume: model_status = KML.DataPoint('Notes','In Plume Point') if full_file.quality(k, **quality_args): count_plume_points+=1 elif in_background: model_status = KML.DataPoint('Notes','Background Point') if full_file.quality(k, **quality_args): count_bg_points+=1 else: model_status = KML.DataPoint('Notes','None') data.add_data_field(model_status) quality = full_file.quality(k, **quality_args) data.add_new_data("Quality Check", str(quality)) poly_col = sounding_colour.cformat(Colours.Colour.GE) sounding_polygon = KML.Polygon(lats, lons, sounding_id, poly_col) sounding_polygon.add_data(data) warn_level_folders[warn_level].add_object(sounding_polygon) Map.write(filename) print "KML saved as", filename print "Done Overpass"
def vertical_plot(overpass, file_name, bias_correction="corrected", x_max=75.e3, y_max_negative=50.e3, y_max_positive=50.e3, f_plume=0.10, f_background=0.01, wind_adjustment=0., wind_source="Average", y_min_positive=0., y_min_negative=0., offset=3.e3, direction='y', xco2_min=395, xco2_max=405, plot_max=125, plot_min=125, snr_strong_co2_min=None, chi_squared_max=None, albedo_min=None, albedo_max=None, surface_pressure_min=None, surface_pressure_max=None, smooth=False, units='g/s', outcome_flags={1, 2}, secondary_sources=False, stability="new", force_wind=None, surface_stability=None, failed_quality_color='grey', show_latitude=False): """vertical_plot makes so-called vertical plots. Args: * overpass: a PST.Overpass instance * file_name: str, valid file path to save the image as Keyword Args: * see keyword arguments document """ quality_args = { 'chi_squared_max': chi_squared_max, 'snr_strong_co2_min': snr_strong_co2_min, 'albedo_min': albedo_min, 'albedo_max': albedo_max, 'outcome_flags': outcome_flags, 'surface_pressure_min': surface_pressure_min, 'surface_pressure_max': surface_pressure_max } bg_kwargs = { 'ymax_negative': y_max_negative, 'ymax_positive': y_max_positive, 'background_factor': f_background, 'ymin_negative': y_min_negative, 'ymin_positive': y_min_positive, 'offset': offset, 'sign': direction } if bias_correction not in File.allowed_bias_correction: raise ValueError( "bias_correction must be one of {0}; given {1}".format( ', '.join(File.allowed_bias_correction), bias_correction)) co2 = "{0}_xco2".format(bias_correction) if smooth: co2 = 'smoothed_' + co2 try: wind = getattr(overpass, wind_source).rotate(wind_adjustment) except AttributeError: raise TypeError('wind_sources must be one of {0}; given {1}'.format( ", ".join(PST.AllWinds.valid_keys), wind_source)) if force_wind: wind = force_wind u = wind.speed if surface_stability: a = surface_stability else: a = overpass.a if stability == "new" else overpass.a_old F = overpass.get_emissions(units) print "Using stability parameter a={0}".format(a) print("Opening and reading Full File") full_file = File.full(overpass) (x0, y0, index_min) = full_file.get_offset(overpass, wind, return_index=True) if secondary_sources: if hasattr(secondary_sources, "__iter__"): overpass.source.secondary = secondary_sources secondary_sources = True secondary_offsets = full_file.get_secondary_offset(overpass, wind) print "Using secondary sources {0}".format(overpass.source.secondary) if secondary_sources: all_source_latitudes = [overpass.lat] + [ second.lat for second in overpass.source.secondary ] else: all_source_latitudes = [overpass.lat] weighted_avg_lat = numpy.average(all_source_latitudes, weights=all_source_latitudes) lat_offset = overpass.lat - weighted_avg_lat lat0 = full_file.retrieval_latitude[index_min] lon0 = full_file.retrieval_longitude[index_min] lat_length = 0.001 * Geometry.CoordGeom(wind).distance( overpass.lat, overpass.lon, overpass.lat + 1, overpass.lon) plume_data = File.File() else_data = File.File() background_data = File.File() failed_quality_data = File.File() delta_lat = 3. lat_max = weighted_avg_lat + plot_max / lat_length lat_min = weighted_avg_lat - plot_min / lat_length i = 0 print("Classifying data") for i in range(len(full_file)): if full_file.quality(i, **quality_args): coordinate = Geometry.CoordGeom(wind) x, y = coordinate.coord_to_wind_basis( overpass.lat, overpass.lon, full_file.retrieval_latitude[i], full_file.retrieval_longitude[i]) dist = Geometry.CoordGeom.cartesian_distance((x, y), (x0, y0)) # Don't have a separate if secondary_sources... loop for background since it's far enough away that it shouldn't make a difference # As long as it isn't in the secondary plume, add the point to the background in_background = PlumeModel.InBackground(x, y, dist, u, F, a, **bg_kwargs) in_plume = PlumeModel.InPlume(x, y, u, F, a, plume_factor=f_plume, xmax=x_max) if secondary_sources: for ind, secondary in enumerate(overpass.source.secondary): xs, ys = Geometry.CoordGeom(wind).coord_to_wind_basis( secondary.lat, secondary.lon, full_file.retrieval_latitude[i], full_file.retrieval_longitude[i]) secondary_offset = secondary_offsets[ind] secondary_dist = Geometry.CoordGeom.cartesian_distance( (xs, ys), (secondary_offset[0], secondary_offset[1])) in_secondary_plume = PlumeModel.InPlume( xs, ys, u, 1., a, plume_factor=f_plume, xmax=x_max) in_plume = in_plume or in_secondary_plume in_secondary_background = PlumeModel.InBackground( xs, ys, secondary_dist, u, 1., a, **bg_kwargs) in_background = in_background and in_secondary_background if in_plume: plume_data.append(full_file, i) elif in_background: background_data.append(full_file, i) else: else_data.append(full_file, i) else: failed_quality_data.append(full_file, i) max_dist = Geometry.CoordGeom(wind).distance(lat0, lon0, lat0 + delta_lat, lon0) min_dist = -Geometry.CoordGeom(wind).distance(lat0, lon0, lat0 - delta_lat, lon0) background_mean = numpy.mean(background_data[co2]) print "Background mean:", background_mean ticks_plus = numpy.append(numpy.arange(0, plot_max, 50), [plot_max]) ticks_minus = sorted( -1 * numpy.append(numpy.arange(50, plot_min, 50), [plot_min])) dist_ticks = numpy.append(ticks_minus, ticks_plus) fig = plt.figure(figsize=_fig_size) ax1 = fig.add_subplot(111) ax1.scatter(plume_data[co2], plume_data.retrieval_latitude, color=_plume_colour, s=_size, label=_plume_lbl) ax1.scatter(background_data[co2], background_data.retrieval_latitude, color=_bg_colour, s=_size, label=_bg_lbl) ax1.scatter(else_data[co2], else_data.retrieval_latitude, color=_other_colour, s=_size, label=_other_lbl) ax1.scatter(failed_quality_data[co2], failed_quality_data.retrieval_latitude, color=failed_quality_color, s=_size, label=_qual_lbl) ax1.plot([background_mean, background_mean], [-plot_min, plot_max], color=_bg_mean_colour) ax1.set_ylim(lat_min, lat_max) ax1.set_xlim(xco2_min, xco2_max) ax1.set_ylabel('Distance from source (km)', fontsize=labelfont, labelpad=leftlabelpad, fontname=Formats.globalfont) ax1.set_xlabel('Xco$_2$ (ppm)', fontsize=labelfont, fontname=Formats.globalfont) ax1.set_xticks(numpy.linspace(xco2_min, xco2_max, 5)) ax1.plot(background_mean, weighted_avg_lat, marker='.', color=_source_colour, markersize=_source_markersize, lw=5) ax1.set_title('Background: %s ppm' % (Formats.ppmformat % background_mean), fontsize=titlefont, fontname=Formats.globalfont) ax2 = ax1.twinx() ax1.tick_params(labelsize=ticklabelfont) ax2.tick_params(labelsize=ticklabelfont) ax2.set_ylim(-plot_min, plot_max) if show_latitude: ax2.set_ylabel('Latitude ($^{\circ}$N)', fontsize=labelfont, labelpad=rightlabelpad, fontname=Formats.globalfont) ax2.set_yticks(dist_ticks) ax2.set_ylim(-plot_min, plot_max) ax2.tick_params('y', left=True, labelleft=True, right=False, labelright=False) if show_latitude: ax1.tick_params('y', left=False, right=True, labelleft=False, labelright=True) else: ax1.tick_params('y', left=False, right=False, labelleft=False, labelright=False) ax1.set_yticks([]) Formats.set_tickfont(ax1) Formats.set_tickfont(ax2) plt.subplots_adjust(**_subplot_adjustments) plt.savefig(file_name, dpi=_dpi, bbox_inches='tight') plt.close()
def box_plot(overpass, file_name, bias_correction="corrected", x_max=75.e3, y_max_negative=50.e3, y_max_positive=50.e3, f_plume=0.10, f_background=0.01, wind_adjustment=0., wind_source="Average", y_min_positive=0., y_min_negative=0., offset=3.e3, direction='y', plot_min=125, plot_max=125, max_distance=100.e3, snr_strong_co2_min=None, chi_squared_max=None, albedo_min=None, albedo_max=None, surface_pressure_min=None, surface_pressure_max=None, smooth=False, units='g/s', outcome_flags={1, 2}, secondary_sources=False, stability="new", force_wind=None, surface_stability=None): """vertical_plot makes so-called vertical plots. Args: * overpass: a PST.Overpass instance * file_name: str, valid file path to save the image as Keyword Args: * see keyword arguments document """ quality_args = { 'chi_squared_max': chi_squared_max, 'snr_strong_co2_min': snr_strong_co2_min, 'albedo_min': albedo_min, 'albedo_max': albedo_max, 'outcome_flags': outcome_flags, 'surface_pressure_min': surface_pressure_min, 'surface_pressure_max': surface_pressure_max } bg_kwargs = { 'ymax_negative': y_max_negative, 'ymax_positive': y_max_positive, 'background_factor': f_background, 'ymin_negative': y_min_negative, 'ymin_positive': y_min_positive, 'offset': offset, 'sign': direction } if bias_correction not in File.allowed_bias_correction: raise ValueError( "bias_correction must be one of {0}; given {1}".format( ', '.join(File.allowed_bias_correction), bias_correction)) co2 = "{0}_xco2".format(bias_correction) if smooth: co2 = 'smoothed_' + co2 try: wind = getattr(overpass, wind_source).rotate(wind_adjustment) except AttributeError: raise TypeError('wind_sources must be one of {0}; given {1}'.format( ", ".join(PST.AllWinds.valid_keys), wind_source)) if force_wind: wind = force_wind u = wind.speed if surface_stability: a = surface_stability else: a = overpass.a if stability == "new" else overpass.a_old F = overpass.get_emissions(units) print "Using stability parameter a={0}".format(a) print("Opening and reading Full File") full_file = File.full(overpass) (x0, y0, index_min) = full_file.get_offset(overpass, wind, return_index=True) if secondary_sources: if hasattr(secondary_sources, "__iter__"): overpass.source.secondary = secondary_sources secondary_sources = True secondary_offsets = full_file.get_secondary_offset(overpass, wind) print "Using secondary sources {0}".format(overpass.source.secondary) if secondary_sources: all_source_latitudes = [overpass.lat] + [ second.lat for second in overpass.source.secondary ] else: all_source_latitudes = [overpass.lat] weighted_avg_lat = numpy.average(all_source_latitudes, weights=all_source_latitudes) lat_offset = overpass.lat - weighted_avg_lat lat0 = full_file.retrieval_latitude[index_min] lon0 = full_file.retrieval_longitude[index_min] lat_length = 0.001 * Geometry.CoordGeom(wind).distance( overpass.lat, overpass.lon, overpass.lat + 1, overpass.lon) plume_data = File.File() else_data = File.File() background_data = File.File() failed_quality_data = File.File() delta_lat = 3. lat_max = weighted_avg_lat + plot_max / lat_length lat_min = weighted_avg_lat - plot_min / lat_length i = 0 print("Classifying data") for i in range(len(full_file)): if full_file.quality(i, **quality_args): coordinate = Geometry.CoordGeom(wind) x, y = coordinate.coord_to_wind_basis( overpass.lat, overpass.lon, full_file.retrieval_latitude[i], full_file.retrieval_longitude[i]) dist = Geometry.CoordGeom.cartesian_distance((x, y), (x0, y0)) in_background = PlumeModel.InBackground(x, y, dist, u, F, a, **bg_kwargs) in_plume = PlumeModel.InPlume(x, y, u, F, a, plume_factor=f_plume, xmax=x_max) if secondary_sources: for ind, secondary in enumerate(overpass.source.secondary): xs, ys = Geometry.CoordGeom(wind).coord_to_wind_basis( secondary.lat, secondary.lon, full_file.retrieval_latitude[i], full_file.retrieval_longitude[i]) secondary_offset = secondary_offsets[ind] secondary_dist = Geometry.CoordGeom.cartesian_distance( (xs, ys), (secondary_offset[0], secondary_offset[1])) in_secondary_plume = PlumeModel.InPlume( xs, ys, u, 1., a, plume_factor=f_plume, xmax=x_max) in_plume = in_plume or in_secondary_plume in_secondary_background = PlumeModel.InBackground( xs, ys, secondary_dist, u, 1., a, **bg_kwargs) in_background = in_background and in_secondary_background if in_plume: plume_data.append(full_file, i) elif in_background: background_data.append(full_file, i) elif dist <= max_distance: else_data.append(full_file, i) else: failed_quality_data.append(full_file, i) max_dist = Geometry.CoordGeom(wind).distance(lat0, lon0, lat0 + delta_lat, lon0) min_dist = -Geometry.CoordGeom(wind).distance(lat0, lon0, lat0 - delta_lat, lon0) background_mean = numpy.mean(background_data[co2]) print "Background mean:", background_mean ticks_plus = numpy.append(numpy.arange(0, plot_max, 50), [plot_max]) ticks_minus = sorted( -1 * numpy.append(numpy.arange(50, plot_min, 50), [plot_min])) dist_ticks = numpy.append(ticks_minus, ticks_plus) fig, ax = plt.subplots(figsize=_fig_size) plot_data = [plume_data[co2], background_data[co2], else_data[co2]] ax.boxplot(plot_data) ax.set_xticklabels([ 'Plume', 'Background', 'All Points (d$\leq$%s km)' % (max_distance / 1000.) ]) ax.set_ylabel('Xco$_2$ (ppm)') ax.set_title('Boxplot for Xco$_2$, %s' % overpass) ax.axhline(background_mean, color='g', label='Background Mean: %.3f ppm' % background_mean) ax.legend(loc=3, prop={'size': 8}) plt.savefig(file_name, dpi=400) plt.close()