def get_glm_earth_geometry(glm, nadir, ltgellipsever): x_1d = glm.x y_1d = glm.y x, y = np.meshgrid(x_1d, y_1d) # Two 2D arrays of fixed grid coordinates z = np.zeros_like(x) # Figure out the approximate GLM earth coverage based on GOES data book. # Convert below to radians and plot in fixed grid coordinates. # rect_span = np.radians(14.81)*satheight #minimum degrees # circ_diam = np.radians(15.59)*satheight #minimum degrees rect_span = np.radians( 15) #minimum degrees - matches figure in GOES data book circ_diam = np.radians(16) #minimum degrees glm_angle = np.sqrt(x * x + y * y) outside_glm_full_disk = ((np.abs(x) > rect_span / 2.0) | (np.abs(y) > rect_span / 2.0) | (glm_angle > circ_diam / 2.0)) geofixCS, grs80lla = get_GOESR_coordsys(nadir) geofix_ltg, lla_ltg = get_GOESR_coordsys_alt_ellps(nadir, ltgellipsever) X, Y, Z = geofix_ltg.toECEF(x, y, z) # X, Y, Z = geofix_ltg.toECEF(x,y,z) gives the 3D, earth-centered, # earth-fixed position of the intersection of the satellite-relative fixed # grid angle with the lightning ellipse. # The 3D position (X,Y,Z) defines an implicit lon, lat, alt with respect to # the target earth surface. Here, we intersect with the grs80 earth surface. lon_ltg, lat_ltg, alt_ltg = grs80lla.fromECEF(X, Y, Z) lon_ltg.shape = x.shape lat_ltg.shape = y.shape return (x, y), (X, Y, Z), (lon_ltg, lat_ltg, alt_ltg), outside_glm_full_disk
def get_glm_parallax_offsets(lon, lat, goes_ds): # Get parallax of glm files to goes projection x, y = get_abi_x_y(lat, lon, goes_ds) z = np.zeros_like(x) nadir = goes_ds.goes_imager_projection.longitude_of_projection_origin _, grs80lla = get_GOESR_coordsys(nadir) geofix_ltg, lla_ltg = get_GOESR_coordsys_alt_ellps(nadir) lon_ltg, lat_ltg, alt_ltg = grs80lla.fromECEF(*geofix_ltg.toECEF(x, y, z)) return lon_ltg - lon, lat_ltg - lat
def subdivided_fixed_grid(kwargs, process_flash_kwargs, out_kwargs, s=1, x_pad=100 * 28.0e-6, y_pad=100 * 28.0e-6): """ Generator function to turn a single set of keyword arguments to grid_GLM_flashes into an s by s block of keyword arguments. x_pad and y_pad are padding in fixed grid coordinates used to increase the bounding box over which flashes are subset from the data file. A flash with a centroid near the edge of the target grid may have events within the grid, so we want to capture that flash too. Default is the equivalent of 100 km at nadir. Yields (i,j) kwargs_ij, process_flash_kwargs_ij for each i,j subgrid. """ # Convert padding into an integer multiple of dx n_x_pad = int(x_pad / kwargs['dx']) n_y_pad = int(y_pad / kwargs['dy']) x_pad = float(n_x_pad * kwargs['dx']) y_pad = float(n_y_pad * kwargs['dy']) pads = (n_x_pad, n_y_pad, x_pad, y_pad) x_sub_bnd = subdivide_bnd(kwargs['x_bnd'], kwargs['dx'], s=s) y_sub_bnd = subdivide_bnd(kwargs['y_bnd'], kwargs['dy'], s=s) log.debug((x_sub_bnd, y_sub_bnd)) nadir_lon = process_flash_kwargs['nadir_lon'] geofixcs, grs80lla = get_GOESR_coordsys(sat_lon_nadir=nadir_lon) for i, j in itertools.product(range(s), range(s)): kwargsij = kwargs.copy() prockwargsij = process_flash_kwargs.copy() x_bnd_i = x_sub_bnd[i:i + 2].copy() y_bnd_j = y_sub_bnd[j:j + 2].copy() # Make a copy of this and use it as target grid specs before # we modify to use as the flash selection bounding box. # These abut one other exactly. kwargsij['x_bnd'], kwargsij['y_bnd'] = x_bnd_i.copy(), y_bnd_j.copy() x_bnd_i[0] -= x_pad x_bnd_i[1] += x_pad y_bnd_j[0] -= y_pad y_bnd_j[1] += y_pad # This x_bnd and y_bnd are different from the one above in kwargsij. # They are used to subset the flashes, and therefore have additional # padding. prockwargsij['x_bnd'], prockwargsij['y_bnd'] = x_bnd_i, y_bnd_j # The line below guarantees overlap across the grids. # The flash selection bouning box and the target grid are the same kwargsij['x_bnd'], kwargsij['y_bnd'] = x_bnd_i, y_bnd_j # No need to do lon_bnd and lat_bnd because we subset in fixed grid # coordinates instead. prockwargsij['lon_bnd'], prockwargsij['lat_bnd'] = None, None outfile_prefix_base = out_kwargs['output_filename_prefix'] outfile_prefix_base_ij = outfile_prefix_base + '-' outfile_prefix_base_ij += '{0:02d}-{1:02d}'.format(i, j) out_kwargs_ij = out_kwargs.copy() out_kwargs_ij['output_filename_prefix'] = outfile_prefix_base_ij # out_kwargs_ij['output_writer_cache'] = out_kwargs_ij['output_writer'] preprocess_out = GridOutputPreprocess( pads=pads, writer=out_kwargs_ij['output_writer']) out_kwargs_ij['preprocess_out'] = preprocess_out out_kwargs_ij['output_writer'] = preprocess_out.capture_write_call log.debug(("SUBGRID", i, j, x_bnd_i, y_bnd_j, pads)) yield (i, j), kwargsij, prockwargsij, out_kwargs_ij, pads
dx = dy = view['resolution'] nx, ny = view['pixelsEW'], view['pixelsNS'] if 'centerEW' in view: x_ctr, y_ctr = view['centerEW'], view['centerNS'] else: # won't be known for mesoscale sectors. # Assume that it's in ctr_lon, ctr_lat x_ctr, y_ctr = ctr_lon, ctr_lat x_bnd = (np.arange(nx, dtype='float') - nx / 2.0) * dx + x_ctr + 0.5 * dx y_bnd = (np.arange(ny, dtype='float') - ny / 2.0) * dy + y_ctr + 0.5 * dy x_bnd = np.asarray([x_bnd.min(), x_bnd.max()]) y_bnd = np.asarray([y_bnd.min(), y_bnd.max()]) geofixcs, grs80lla = get_GOESR_coordsys(sat_lon_nadir=nadir_lon) ctr_lon, ctr_lat, ctr_alt = grs80lla.fromECEF( *geofixcs.toECEF(x_ctr, y_ctr, 0.0)) fixed_grid = geofixcs log.debug((x_bnd, y_bnd, dx, dy, nx, ny)) else: dx, dy = args.dx, args.dy nadir_lon = -75.0 nx, ny = 1000, 1000 x_ctr, y_ctr = args.ctr_lon, args.ctr_lat x_bnd = (np.arange(nx, dtype='float') - nx / 2.0) * dx + x_ctr + 0.5 * dx y_bnd = (np.arange(ny, dtype='float') - ny / 2.0) * dy + y_ctr + 0.5 * dy x_bnd = np.asarray([x_bnd.min(), x_bnd.max()]) y_bnd = np.asarray([y_bnd.min(), y_bnd.max()])
def grid_setup(args): from lmatools.grid.make_grids import write_cf_netcdf_latlon, write_cf_netcdf_noproj, write_cf_netcdf_fixedgrid from lmatools.grid.make_grids import dlonlat_at_grid_center, grid_h5flashfiles from glmtools.grid.make_grids import grid_GLM_flashes from glmtools.io.glm import parse_glm_filename from lmatools.io.LMA_h5_file import parse_lma_h5_filename from lmatools.grid.fixed import get_GOESR_grid, get_GOESR_coordsys # When passed None for the minimum event or group counts, the gridder will skip # the check, saving a bit of time. min_events = int(args.min_events) if min_events <= 1: min_events = None min_groups = int(args.min_groups) if min_groups <= 1: min_groups = None if args.is_lma: filename_parser = parse_lma_h5_filename start_idx = 0 end_idx = 1 else: filename_parser = parse_glm_filename start_idx = 3 end_idx = 4 glm_filenames = args.filenames base_filenames = [os.path.basename(p) for p in glm_filenames] try: filename_infos = [filename_parser(f) for f in base_filenames] # opsenv, algorithm, platform, start, end, created = parse_glm_filename(f) filename_starts = [info[start_idx] for info in filename_infos] filename_ends = [info[end_idx] for info in filename_infos] except ValueError: log.error("One or more GLM files has a non-standard filename.") log.error("Assuming that --start and --end have been passed directly.") from glmtools.io.glm import parse_glm_filename if args.start is not None: start_time = datetime.strptime(args.start[:19], '%Y-%m-%dT%H:%M:%S') else: start_time = min(filename_starts) if args.end is not None: end_time = datetime.strptime(args.end[:19], '%Y-%m-%dT%H:%M:%S') else: end_time = max(filename_ends) date = datetime(start_time.year, start_time.month, start_time.day) # grid_dir = os.path.join('/data/LCFA-production/', 'grid_test') # outpath = grid_dir+'/20%s' %(date.strftime('%y/%b/%d')) outpath = os.path.join(args.outdir, '20%s' % (date.strftime('%y/%b/%d'))) if os.path.exists(outpath) == False: os.makedirs(outpath) # subprocess.call(['chmod', 'a+w', outpath, grid_dir+'/20%s' %(date.strftime('%y/%b')), grid_dir+'/20%s' %(date.strftime('%y'))]) if args.fixed_grid: proj_name = 'geos' if (args.goes_position != 'none') & (args.goes_sector != 'none'): resln = nearest_resolution(args) view = get_GOESR_grid(position=args.goes_position, view=args.goes_sector, resolution=resln) nadir_lon = view['nadir_lon'] dx = dy = view['resolution'] nx, ny = view['pixelsEW'], view['pixelsNS'] geofixcs, grs80lla = get_GOESR_coordsys(sat_lon_nadir=nadir_lon) if 'centerEW' in view: x_ctr, y_ctr = view['centerEW'], view['centerNS'] elif args.goes_sector == 'meso': # use ctr_lon, ctr_lat to get the center of the mesoscale FOV x_ctr, y_ctr, z_ctr = geofixcs.fromECEF( *grs80lla.toECEF(args.ctr_lon, args.ctr_lat, 0.0)) elif (args.goes_position != 'none') & (args.goes_sector == 'none'): # Requires goes_position, a center, and a width. Fully flexible # in resolution, i.e., doesn't slave it to one of the GOES-R specs view = get_GOESR_grid(position=args.goes_position, view='full', resolution='1.0km') nadir_lon = view['nadir_lon'] dx1km = dy1km = view['resolution'] geofixcs, grs80lla = get_GOESR_coordsys(sat_lon_nadir=nadir_lon) x_ctr, y_ctr, z_ctr = geofixcs.fromECEF( *grs80lla.toECEF(args.ctr_lon, args.ctr_lat, 0.0)) # Convert the specified resolution in km given by args.dx to # a delta in fixed grid coordinates using the 1 km delta from the # GOES-R PUG. dx, dy = args.dx * dx1km, args.dy * dy1km nx, ny = int(args.width / args.dx), int(args.height / args.dy) else: raise ValueError( "Gridding on the fixed grid requires " "goes_position and dx. For goes_sector='meso', also specify " "ctr_lon and ctr_lat. Without goes_sector, also include width " "and height.") x_bnd = (np.arange(nx, dtype='float') - nx / 2.0) * dx + x_ctr + 0.5 * dx y_bnd = (np.arange(ny, dtype='float') - ny / 2.0) * dy + y_ctr + 0.5 * dy x_bnd = np.asarray([x_bnd.min(), x_bnd.max()]) y_bnd = np.asarray([y_bnd.min(), y_bnd.max()]) geofixcs, grs80lla = get_GOESR_coordsys(sat_lon_nadir=nadir_lon) ctr_lon, ctr_lat, ctr_alt = grs80lla.fromECEF( *geofixcs.toECEF(x_ctr, y_ctr, 0.0)) fixed_grid = geofixcs log.debug((x_bnd, y_bnd, dx, dy, nx, ny)) output_writer = partial(write_cf_netcdf_fixedgrid, nadir_lon=nadir_lon) else: # Default proj_name = 'latlong' output_writer = write_cf_netcdf_latlon ctr_lat = float(args.ctr_lat) ctr_lon = float(args.ctr_lon) dx_km = float(args.dx) * 1.0e3 dy_km = float(args.dy) * 1.0e3 width, height = 1000.0 * float(args.width), 1000.0 * float(args.height) x_bnd_km = (-width / 2.0, width / 2.0) y_bnd_km = (-height / 2.0, height / 2.0) dx, dy, x_bnd, y_bnd = dlonlat_at_grid_center(ctr_lat, ctr_lon, dx=dx_km, dy=dy_km, x_bnd=x_bnd_km, y_bnd=y_bnd_km) # tuples of the corners corners = np.vstack([(x_bnd[0], y_bnd[0]), (x_bnd[0], y_bnd[1]), (x_bnd[1], y_bnd[1]), (x_bnd[1], y_bnd[0])]) # print(x_bnd, y_bnd) if args.is_lma: gridder = grid_h5flashfiles output_filename_prefix = 'LMA' else: gridder = grid_GLM_flashes output_filename_prefix = 'GLM' grid_kwargs = dict(proj_name=proj_name, base_date=date, do_3d=False, dx=dx, dy=dy, frame_interval=float(args.dt), x_bnd=x_bnd, y_bnd=y_bnd, ctr_lat=ctr_lat, ctr_lon=ctr_lon, outpath=outpath, min_points_per_flash=min_events, output_writer=output_writer, subdivide=args.subdivide_grid, output_filename_prefix=output_filename_prefix, spatial_scale_factor=1.0) if args.fixed_grid: grid_kwargs['fixed_grid'] = True grid_kwargs['nadir_lon'] = nadir_lon if args.split_events: grid_kwargs['clip_events'] = True if min_groups is not None: grid_kwargs['min_groups_per_flash'] = min_groups if args.is_lma: grid_kwargs['energy_grids'] = True else: grid_kwargs['energy_grids'] = ('total_energy', ) if (proj_name == 'pixel_grid') or (proj_name == 'geos'): grid_kwargs['pixel_coords'] = fixed_grid grid_kwargs['ellipse_rev'] = args.ellipse_rev # if args.corner_points: # grid_kwargs['corner_pickle'] = args.corner_points return gridder, glm_filenames, start_time, end_time, grid_kwargs
def grid_setup(args): from lmatools.grid.make_grids import write_cf_netcdf_latlon, write_cf_netcdf_noproj, write_cf_netcdf_fixedgrid from lmatools.grid.make_grids import dlonlat_at_grid_center, grid_h5flashfiles from glmtools.grid.make_grids import grid_GLM_flashes from glmtools.io.glm import parse_glm_filename from lmatools.io.LMA_h5_file import parse_lma_h5_filename from lmatools.grid.fixed import get_GOESR_grid, get_GOESR_coordsys # When passed None for the minimum event or group counts, the gridder will skip # the check, saving a bit of time. min_events = int(args.min_events) if min_events <= 1: min_events = None min_groups = int(args.min_groups) if min_groups <= 1: min_groups = None if args.is_lma: filename_parser = parse_lma_h5_filename start_idx = 0 end_idx = 1 else: filename_parser = parse_glm_filename start_idx = 3 end_idx = 4 glm_filenames = args.filenames base_filenames = [os.path.basename(p) for p in glm_filenames] try: filename_infos = [filename_parser(f) for f in base_filenames] # opsenv, algorithm, platform, start, end, created = parse_glm_filename(f) filename_starts = [info[start_idx] for info in filename_infos] filename_ends = [info[end_idx] for info in filename_infos] except ValueError: log.error("One or more GLM files has a non-standard filename.") log.error("Assuming that --start and --end have been passed directly.") from glmtools.io.glm import parse_glm_filename if args.start is not None: start_time = datetime.strptime(args.start[:19], '%Y-%m-%dT%H:%M:%S') else: start_time = min(filename_starts) if args.end is not None: end_time = datetime.strptime(args.end[:19], '%Y-%m-%dT%H:%M:%S') else: # Used to use max(filename_ends), but on 27 Oct 2020, the filename # ends started to report the time of the last event in the file, # causing a slight leakage (usually less than a second) into the # next minute. This caused two minutes of grids to be produced for every # three twenty second files passed to this script. # Instead, we now assume every LCFA file is 20 s long, beginning with # the start time. No doubt in the future we will see filenames that no # longer start on an even minute boundary. end_time = max(filename_starts) + timedelta(0, 20) date = datetime(start_time.year, start_time.month, start_time.day) outpath = args.outdir if args.fixed_grid: proj_name = 'geos' if (args.goes_position != 'none') & (args.goes_sector != 'none'): resln = nearest_resolution(args) view = get_GOESR_grid(position=args.goes_position, view=args.goes_sector, resolution=resln) nadir_lon = view['nadir_lon'] dx = dy = view['resolution'] nx, ny = view['pixelsEW'], view['pixelsNS'] geofixcs, grs80lla = get_GOESR_coordsys(sat_lon_nadir=nadir_lon) if 'centerEW' in view: x_ctr, y_ctr = view['centerEW'], view['centerNS'] elif args.goes_sector == 'meso': # use ctr_lon, ctr_lat to get the center of the mesoscale FOV x_ctr, y_ctr, z_ctr = geofixcs.fromECEF( *grs80lla.toECEF(args.ctr_lon, args.ctr_lat, 0.0)) elif (args.goes_position != 'none') & (args.goes_sector == 'none'): # Requires goes_position, a center, and a width. Fully flexible # in resolution, i.e., doesn't slave it to one of the GOES-R specs view = get_GOESR_grid(position=args.goes_position, view='full', resolution='1.0km') nadir_lon = view['nadir_lon'] dx1km = dy1km = view['resolution'] geofixcs, grs80lla = get_GOESR_coordsys(sat_lon_nadir=nadir_lon) x_ctr, y_ctr, z_ctr = geofixcs.fromECEF( *grs80lla.toECEF(args.ctr_lon, args.ctr_lat, 0.0)) # Convert the specified resolution in km given by args.dx to # a delta in fixed grid coordinates using the 1 km delta from the # GOES-R PUG. dx, dy = args.dx * dx1km, args.dy * dy1km nx, ny = int(args.width / args.dx), int(args.height / args.dy) else: raise ValueError( "Gridding on the fixed grid requires " "goes_position and dx. For goes_sector='meso', also specify " "ctr_lon and ctr_lat. Without goes_sector, also include width " "and height.") # Need to use +1 here to convert to xedge, yedge expected by gridder # instead of the pixel centroids that will result in the final image nx += 1 ny += 1 x_bnd = (np.arange(nx, dtype='float') - (nx) / 2.0) * dx + x_ctr + 0.5 * dx y_bnd = (np.arange(ny, dtype='float') - (ny) / 2.0) * dy + y_ctr + 0.5 * dy log.debug(("initial x,y_ctr", x_ctr, y_ctr)) log.debug(("initial x,y_bnd", x_bnd.shape, y_bnd.shape)) x_bnd = np.asarray([x_bnd.min(), x_bnd.max()]) y_bnd = np.asarray([y_bnd.min(), y_bnd.max()]) geofixcs, grs80lla = get_GOESR_coordsys(sat_lon_nadir=nadir_lon) ctr_lon, ctr_lat, ctr_alt = grs80lla.fromECEF( *geofixcs.toECEF(x_ctr, y_ctr, 0.0)) fixed_grid = geofixcs log.debug((x_bnd, y_bnd, dx, dy, nx, ny)) output_writer = partial(write_cf_netcdf_fixedgrid, nadir_lon=nadir_lon) else: # Default proj_name = 'latlong' output_writer = write_cf_netcdf_latlon ctr_lat = float(args.ctr_lat) ctr_lon = float(args.ctr_lon) dx_km = float(args.dx) * 1.0e3 dy_km = float(args.dy) * 1.0e3 width, height = 1000.0 * float(args.width), 1000.0 * float(args.height) x_bnd_km = (-width / 2.0, width / 2.0) y_bnd_km = (-height / 2.0, height / 2.0) dx, dy, x_bnd, y_bnd = dlonlat_at_grid_center(ctr_lat, ctr_lon, dx=dx_km, dy=dy_km, x_bnd=x_bnd_km, y_bnd=y_bnd_km) # tuples of the corners corners = np.vstack([(x_bnd[0], y_bnd[0]), (x_bnd[0], y_bnd[1]), (x_bnd[1], y_bnd[1]), (x_bnd[1], y_bnd[0])]) # print(x_bnd, y_bnd) if args.is_lma: gridder = grid_h5flashfiles output_filename_prefix = 'LMA' else: gridder = grid_GLM_flashes output_filename_prefix = 'GLM' grid_kwargs = dict( proj_name=proj_name, base_date=date, do_3d=False, dx=dx, dy=dy, frame_interval=float(args.dt), x_bnd=x_bnd, y_bnd=y_bnd, ctr_lat=ctr_lat, ctr_lon=ctr_lon, outpath=outpath, min_points_per_flash=min_events, output_writer=output_writer, subdivide=args.subdivide_grid, output_filename_prefix=output_filename_prefix, output_kwargs={'scale_and_offset': args.output_scale_and_offset}, spatial_scale_factor=1.0) if args.fixed_grid: grid_kwargs['fixed_grid'] = True grid_kwargs['nadir_lon'] = nadir_lon if args.split_events: grid_kwargs['clip_events'] = True if min_groups is not None: grid_kwargs['min_groups_per_flash'] = min_groups if args.is_lma: grid_kwargs['energy_grids'] = True else: grid_kwargs['energy_grids'] = ('total_energy', ) if (proj_name == 'pixel_grid') or (proj_name == 'geos'): grid_kwargs['pixel_coords'] = fixed_grid grid_kwargs['ellipse_rev'] = args.ellipse_rev # if args.corner_points: # grid_kwargs['corner_pickle'] = args.corner_points return gridder, glm_filenames, start_time, end_time, grid_kwargs