def generate_corners(center, hpixels, vpixels): map_x, map_y = center[0], center[1] c_lon, c_lat = retrievals.standard_bmap(map_x, map_y, inverse=True) satlat = sat_lat satlon = sat_lon altitude = sat_alt map_x, map_y = retrievals.transform(satlat, satlon, lon=c_lon, lat=c_lat, inv=True) # find how far off of nadir the satellite must point to view the centre # of the FOV # retrivals.distance = distance along earth from sub-satellite point offsetx = retrievals.distance(altitude, arc_length=map_x, inv=True) offsety = retrievals.distance(altitude, arc_length=map_y, inv=True) # find orthographic coordinates of the positions of the pixels in the FOV x_increments = np.array([-(hpixels) / 2, hpixels / 2]) y_increments = np.array([-(vpixels) / 2, vpixels / 2]) x_increments, y_increments = np.meshgrid(x_increments, y_increments) x_posns = retrievals.distance(altitude, position=x_increments + offsetx) y_posns = retrievals.distance(altitude, position=y_increments + offsety) # for some reason these came out in the wrong order # so we must flip second last items x = x_posns.flatten() sorted_xs = np.array([x[0], x[1], x[3], x[2]]) y = y_posns.flatten() sorted_ys = np.array([y[0], y[1], y[3], y[2]]) # convert back into lats, lons lats, lons = retrievals.transform(satlat, satlon, inv=False, x=sorted_xs, y=sorted_ys) # now convert to mappable coordinates so we can map easier later map_x, map_y = retrievals.standard_bmap(lons, lats) # print(type(map_x), type(map_y)) vertices = np.array(list(zip(map_x.filled(), map_y.filled()))) return vertices
def clear_mask(sat_lat, sat_lon, time): """ Produces a mask of clear land as well as coordinates, where the coordinates are given in orthographic projection coordinates """ cloud_thresh = 0.1 # get clouds # cloudmask has indices for [lat, lon] cloudmask = clouds.Clouds(time).get_clouds(time, (90, -90), out="") cloudmask = (cloudmask > cloud_thresh).astype(int) # 'invert' cloudmask so we have clear = 1, cloud = 0 cloudmask = 1 - cloudmask # get land: use same resolution as MERRA data for easy comparison # that resolution is 0.5 x 0.625 landmask = np.load("../earth_data/landmask_full_05deg_lat_0625deg_lon.npy") # now multiply elementwise to get # 1 = clear and land, 0 = cloudy or water (or both) clear = cloudmask * landmask # transform lons, lats into x and y orthographic projection coords lons = np.arange(-180., 180. + 1e-8, 0.625) lats = np.arange(-90, 90 + 1e-8, 0.5) zero_index = list(lats).index(0) lats = lats[zero_index:] clear = clear[zero_index:, :] # transpose clear so you also access it as [lon_index, lat_index] clear = clear.T # access coords as [lon_index, lat_index] too coords = np.empty(clear.shape, dtype=tuple) for i, lon in enumerate(lons): for j, lat in enumerate(lats): # convert to ortho coords x, y = retrievals.transform(sat_lat, sat_lon, lon=lon, lat=lat, inv=True) coords[i, j] = (x, y) return Mask(clear, coords)
def closest_value(self, sat_lat, sat_lon, lat, lon): # returns the value of the mask at the closest point to the coords # closest defined in terms of orthographic projection coordinates, # I hope that is a good metric! x0, y0 = retrievals.transform(sat_lat, sat_lon, lat=lat, lon=lon, inv=True) point = np.array([np.array([x0, y0])]) # efficient function for distance calculation distances = scipy.spatial.distance.cdist(self.flat_coords, point) # take first match if any ambiguity index = np.where(distances == np.min(distances))[0][0] min_coords = self.flat_coords[index] x = min_coords[0] y = min_coords[1] x_index = np.where(self.xs == x)[0][0] y_index = np.where(self.ys == y)[0][0] value = self.mask[x_index, y_index] return value
def shift_lattice(fname, index, action, lattice_inds, shift): """ shift the position of a lattice point, so as to optimize its position, likely to avoid water/ice The shifts are given in units of pixels, as this is the linear space, whereas trying to work in distances or lat/lon would be more difficult to track. args: fname - lattice file name. The full path does not need to be given, just the actual file name. index - Index for the segment of the obs. period action - one of 'hshift','vshift' or 'shift'. indicates what kind of shift will be used: horizontal, vertical or both. lattice_inds - a single int or list of ints, indicating which lattice points will be shifted shift - size of the shift in pixels. For 'hshift' and 'vshift', this will be a float. for 'shift', it will be a tuple of (hshift, vshift) example: shift the lattice points with indices from 24 to 32 from the 0th segment of the lattice file, with a vertical shift of 64 pixels: shift_lattice('lattice_128x128_095W.npz', 0, 'vshift', range(24,33), 64) """ # load the lattice fpath = os.path.join('../earth_data/', fname) lattice = np.load(fpath) lattice_name = 'coords_{}'.format(index) # only shift in the horizontal direction if action == 'hshift': # load needed params from the lattice file lattice_points = lattice[lattice_name] satpoint = lattice['satpoints'][index] params = lattice['params'] psize = params[2] refalt = params[3] lat, lon = lattice_points.T satrad, satlat, satlon = orbit_utils.latlon(satpoint) satalt = satrad - re_km # find the current pointing offset to the centre of the FOV x_inv, y_inv = retrievals.transform(satlat, satlon, lat=lat, lon=lon, inv=True) h_inv = retrievals.distance(satalt, arc_length=x_inv, inv=True, pixel_size=psize, ref_altitude=refalt) # add the specified shift to the lattice point h_inv[lattice_inds] += shift h_fwd = h_inv # recalculate lat/lon position of the lattice point x_fwd = retrievals.distance(satalt, position=h_fwd, pixel_size=psize, ref_altitude=refalt) y_fwd = y_inv lat_new, lon_new = retrievals.transform(satlat, satlon, x=x_fwd, y=y_fwd) # get new array of lattice points new_lattice_points = np.array([lat_new, lon_new]).T # only shift in the vertical direction elif action == 'vshift': lattice_points = lattice[lattice_name] satpoint = lattice['satpoints'][index] params = lattice['params'] psize = params[2] refalt = params[3] lat, lon = lattice_points.T satrad, satlat, satlon = orbit_utils.latlon(satpoint) satalt = satrad - re_km x_inv, y_inv = retrievals.transform(satlat, satlon, lat=lat, lon=lon, inv=True) v_inv = retrievals.distance(satalt, arc_length=y_inv, inv=True, pixel_size=psize, ref_altitude=refalt) v_inv[lattice_inds] += shift v_fwd = v_inv x_fwd = x_inv y_fwd = retrievals.distance(satalt, position=v_fwd, pixel_size=psize, ref_altitude=refalt) lat_new, lon_new = retrievals.transform(satlat, satlon, x=x_fwd, y=y_fwd) new_lattice_points = np.array([lat_new, lon_new]).T # shift in both the vertical and # horizontal directions elif action == 'shift': # shift is a tuple ordered as # (horizontal shift, vertical shift) hshift, vshift = shift lattice_points = lattice[lattice_name] satpoint = lattice['satpoints'][index] params = lattice['params'] psize = params[2] refalt = params[3] lat, lon = lattice_points.T satrad, satlat, satlon = orbit_utils.latlon(satpoint) satalt = satrad - re_km x_inv, y_inv = retrievals.transform(satlat, satlon, lat=lat, lon=lon, inv=True) h_inv = retrievals.distance(satalt, arc_length=x_inv, inv=True, pixel_size=psize, ref_altitude=refalt) v_inv = retrievals.distance(satalt, arc_length=y_inv, inv=True, pixel_size=psize, ref_altitude=refalt) h_inv[lattice_inds] += hshift v_inv[lattice_inds] += vshift h_fwd = h_inv v_fwd = v_inv x_fwd = retrievals.distance(satalt, position=h_fwd, pixel_size=psize, ref_altitude=refalt) y_fwd = retrievals.distance(satalt, position=v_fwd, pixel_size=psize, ref_altitude=refalt) lat_new, lon_new = retrievals.transform(satlat, satlon, x=x_fwd, y=y_fwd) new_lattice_points = np.array([lat_new, lon_new]).T # save the new lattice points to the lattice file file_kwds = {} for key in lattice.files: if key != lattice_name: file_kwds[key] = lattice[key] else: file_kwds[key] = new_lattice_points np.savez(fpath, **file_kwds)
def generate_lattice(sim_file, hpixels, vpixels, lattice_min=42.5, lattice_max=90., lf_threshold=0.01, **mission_args): """ Generate a lattice of the centres of FOVs, indicating where a satellite will point when it is making observations. The shape of the lattice will depend on the orbit that is used, the size of the FOVs, and the revisit profile for the observation periods. other functions within this module can be used to edit the lattice that is generated, to optimize it to avoid observing over water. the lattice gets saved as a numpy .npz file args: sim_file - name of the .csv file in which the orbit data is stored. hpixels - number of pixels in the FOV in the horizontal direction vpixels - number of pixels in the FOv in the vertical direction lattice_min - minimum latitude at the centre for the FOV to get accepted lattice_max - maximum latitude at the centre for the FOV to get accepted lf_threshold - minimum land fraction for the FOV to be accepted mission_args - kwargs to pass to the mission instance """ # intialize the mission instance, and use the first # three full orbits (for TAP, change to two for molniya) # for satellite positions mission = retrievals.Mission(sim_file, **mission_args) a1, a2, a3 = mission.satellite.get_apogees()[2:5] files = [] for a in [a1, a2, a3]: # initialize the observation period, # with no lattice file passed, as it # will get created now obs = retrievals.ObservationPeriod(mission, a, None) # get relevant times obs_periods = len(obs.revisit_profile) obs_start = obs.apogee_time - obs.obs_length / 2 obs_times = [ obs_start + np.sum(obs.revisit_profile[:s]) for s in np.arange(obs_periods) ] obs_middle = [ obs_times[k] + obs.revisit_profile[k] / 2 for k in np.arange(obs_periods) ] # calculate the satellite position # at the centre of each segment of the obs. period satpoints = obs.satellite(obs_middle) # get satellite lat/lon, and altitude satrad, satlat, satlon = orbit_utils.latlon_arr(satpoints, axis='first') satalt = satrad - re_km # save some params that will get passed # on to the lattice file psize = obs.pixel_size refalt = obs.ref_altitude hp = obs.hpixels vp = obs.vpixels # create the lattices, for each segment # of the observation period coords = [] masks = [] for k in range(obs_periods): # find the maximum offset the satellite can have, # based on its altitude and pixel size # offset = farthest from nadir that the satellite can point # while still looking at the Earth max_offset = retrievals.distance(satalt[k], arc_length=re_km, inv=True, pixel_size=psize, ref_altitude=refalt) # create the lattice h_grid = np.append( np.arange(0., -max_offset, -hp)[::-1], np.arange(hp, max_offset, hp)) v_grid = np.append( np.arange(0., -max_offset, -vp)[::-1], np.arange(vp, max_offset, vp)) h_grid, v_grid = np.meshgrid(h_grid, v_grid) # get the lat/lon locations of the lattice points x_grid = retrievals.distance(satalt[k], position=h_grid, pixel_size=psize, ref_altitude=refalt) y_grid = retrievals.distance(satalt[k], position=v_grid, pixel_size=psize, ref_altitude=refalt) lat, lon = retrievals.transform(satlat[k], satlon[k], x=x_grid, y=y_grid) # uncomment to show a quick scatter # of the lattice points # it doesn't look like much though! """ m = Basemap(projection='cyl') m.scatter(lon, lat) plt.show() """ # get rid of any masked points, where the # FOVs extend to the other side of the Earth lat = lat.compressed() lon = lon.compressed() # mask points that fall outisde the # allowed latitude range lat_mask = np.ma.masked_outside(lat, lattice_min, lattice_max).mask lat = np.ma.array(lat, mask=lat_mask).compressed() lon = np.ma.array(lon, mask=lat_mask).compressed() # filter the lattice points by land # fraction, so that those completely # over water are excluded lf_mask = np.zeros(lat.size, dtype=np.bool) for j in range(lat.size): gs = retrievals.GridSquare(lat[j], lon[j], satpoints[k], hp, vp, psize, refalt) if not np.sum(gs.pixel_lats.mask): land_frac = gs.get_land_fraction(obs.landmask, obs.landmask_res)[0] if land_frac < lf_threshold: lf_mask[j] = True else: lf_mask[j] = True lat = np.ma.array(lat, mask=lf_mask).compressed() lon = np.ma.array(lon, mask=lf_mask).compressed() # create the coordinate and mask arrays # for the lattice file coords.append(np.array([lat, lon]).T) masks.append(np.zeros(lat.size, dtype=np.bool)) """ read the data into the .npz file, which is organized as follows: coords_k - the lattice point coordinates for the kth segment of the obs. period mask_k - the lattice mask for the kth segment of the obs. period satpoints - the cartesian satellite coords for the middle of each segment params - Other parameters needed to find FOV shape and individual pixel locations """ file_kwds = { 'satpoints': satpoints, 'params': np.array([hp, vp, psize, refalt]) } for k in range(obs_periods): file_kwds['coords_{}'.format(k)] = coords[k] file_kwds['mask_{}'.format(k)] = masks[k] apogee_lon = orbit_utils.latlon(mission.satellite(a)[0])[2] lon_key = '{lon:03}{sign}'.format(lon=int(abs(round(apogee_lon))), sign='W' if apogee_lon < 0. else 'E') lattice_fname = '../earth_data/TAP_lattice_{h}x{v}_{lon}.npz'.format( h=hp, v=vp, lon=lon_key) np.savez(lattice_fname, **file_kwds) files.append(lattice_fname) return files
def generate_clear_lattice(mission, time, index, lattice_min=42.5, lattice_max=90., lf_threshold=0.01): """ Generate a lattice of the centres of FOVs, indicating where a satellite will point when it is making observations. The shape of the lattice will depend on the orbit that is used, the size of the FOVs, and the revisit profile for the observation periods. other functions within this module can be used to edit the lattice that is generated, to optimize it to avoid observing over water. args: sim_file - name of the .csv file in which the orbit data is stored. hpixels - number of pixels in the FOV in the horizontal direction vpixels - number of pixels in the FOv in the vertical direction lattice_min - minimum latitude at the centre for the FOV to get accepted lattice_max - maximum latitude at the centre for the FOV to get accepted lf_threshold - minimum land fraction for the FOV to be accepted """ obs = retrievals.ObservationPeriod(mission, time, None) # --> 4s # calculate the satellite position at the centre of the obs. period satpoint = obs.satellite(time) # get satellite lat/lon, and altitude satrad, satlat, satlon = orbit_utils.latlon_arr(satpoint, axis='first') satalt = satrad - re_km satlat, satlon, satalt = satlat[0], satlon[0], satalt[0] cm = clear_mask(satlat, satlon, time) # --> 12s # save some params that will get passed # on to the lattice file psize = obs.pixel_size refalt = obs.ref_altitude hp = obs.hpixels vp = obs.vpixels # find the maximum offset the satellite can have, # based on its altitude and pixel size # offset = farthest from nadir that the satellite can point # while still looking at the Earth max_offset = retrievals.distance(satalt, arc_length=re_km, inv=True, pixel_size=psize, ref_altitude=refalt) # create the lattice h_grid = np.append( np.arange(0., -max_offset, -hp)[::-1], np.arange(hp, max_offset, hp)) v_grid = np.append( np.arange(0., -max_offset, -vp)[::-1], np.arange(vp, max_offset, vp)) h_grid, v_grid = np.meshgrid(h_grid, v_grid) # get the lat/lon locations of the lattice points x_grid = retrievals.distance(satalt, position=h_grid, pixel_size=psize, ref_altitude=refalt) y_grid = retrievals.distance(satalt, position=v_grid, pixel_size=psize, ref_altitude=refalt) lat, lon = retrievals.transform(satlat, satlon, x=x_grid, y=y_grid) # get rid of any masked points, where the # FOVs extend to the other side of the Earth lat = lat.compressed() lon = lon.compressed() # uncomment to show a quick scatter # of the lattice points """ m = Basemap(projection='ortho', lat_0=90, lon_0=-95) plon, plat = m(lon, lat) m.scatter(plon, plat) plt.show() """ # mask points that fall outisde the # allowed latitude range lat_mask = np.ma.masked_outside(lat, lattice_min, lattice_max).mask lat = np.ma.array(lat, mask=lat_mask).compressed() lon = np.ma.array(lon, mask=lat_mask).compressed() """ m = Basemap(projection='ortho', lat_0=90, lon_0=-95) plon, plat = m(lon, lat) m.scatter(plon, plat) plt.show() """ # filter the lattice points by land # fraction, so that those completely # over water are excluded # also filter by cloud fraction lf_mask = np.zeros(lat.size, dtype=np.bool) satpoint = satpoint[0] # there's only one satpoint but it's inside [] start = t.clock() for j, point in enumerate(zip(lat, lon)): la, lo = point gs = retrievals.GridSquare(la, lo, satpoint, hp, vp, psize, refalt) if np.all(gs.pixel_lats.mask == 0): # i.e. if no points are masked land_frac = gs.get_land_fraction(obs.landmask, obs.landmask_res)[0] if land_frac < lf_threshold: # not enough land lf_mask[j] = True else: # do a cloud check -- this will be T/F; Cloudy/Not Cloudy cloud_value = cm.closest_value(satlat, satlon, la, lo) if cloud_value: # bad pixel, too cloudy lf_mask[j] = True else: pass # pixel is clear else: # bad pixel already lf_mask[j] = True print(t.clock() - start, 'g') # --> 360s lat = np.ma.array(lat, mask=lf_mask).compressed() lon = np.ma.array(lon, mask=lf_mask).compressed() #m = Basemap(projection='ortho', lat_0=90, lon_0=-95) #plon, plat = m(lon, lat) #m.scatter(plon, plat) #plt.show() return (lat, lon)