def corners(self, gmt_format=False): """ Retrieve corners of simulation domain. gmt_format: if True, also returns corners in GMT string format """ # compared with model_params format: # c1 = x0 y0 # c2 = xmax y0 # c3 = xmax ymax # c4 = x0 ymax # cannot just use self.ll_map as xmax, ymax for simulation domain # may have been decimated. sim nx 1400 (xmax 1399) with dxts 5 = 1395 gp_cnrs = np.array( [ [0, 0], [self.nx_sim - 1, 0], [self.nx_sim - 1, self.ny_sim - 1], [0, self.ny_sim - 1], ] ) amat = geo.gen_mat(self.mrot, self.mlon, self.mlat)[0] ll_cnrs = geo.xy2ll(geo.gp2xy(gp_cnrs, self.nx_sim, self.ny_sim, self.hh), amat) if np.min(ll_cnrs[:, 0]) < -90 and np.max(ll_cnrs[:, 0]) > 90: # assume crossing over 180 -> -180, extend past 180 ll_cnrs[ll_cnrs[:, 0] < 0, 0] += 360 if not gmt_format: return ll_cnrs.tolist() gmt_cnrs = "\n".join([" ".join(map(str, cnr)) for cnr in ll_cnrs]) return ll_cnrs.tolist(), gmt_cnrs
def __init__(self, xyts_path, meta_only=False): """ Load metadata and optionally prepare gridpoint datum locations. xyts_path: path to the xyts.e3d file meta_only: don't prepare gridpoint datum locations (slower) can't use timeslice (lon, lat, value) capability """ xytf = open(xyts_path, "rb") # determine endianness, an x-y timeslice has 1 z value nz = np.fromfile(xytf, dtype=">i4", count=7)[-1] if nz == 0x00000001: endian = ">" elif nz == 0x01000000: endian = "<" else: xytf.close() raise ValueError("File is not an XY timeslice file: %s" % (xyts_path)) xytf.seek(0) # read header self.x0, self.y0, self.z0, self.t0, self.nx, self.ny, self.nz, self.nt = np.fromfile( xytf, dtype="%si4" % (endian), count=8 ) self.dx, self.dy, self.hh, dt, self.mrot, self.mlat, self.mlon = np.fromfile( xytf, dtype="%sf4" % (endian), count=7 ) xytf.close() # dt is sensitive to float error eg 0.2 stores as 0.199999 (dangerous) self.dt = np.around(dt, decimals=4) # determine original sim parameters self.dxts = int(round(self.dx / self.hh)) self.dyts = int(round(self.dy / self.hh)) self.nx_sim = self.nx * self.dxts self.ny_sim = self.ny * self.dyts # orientation of components self.dip = 0 self.comps = { "X": radians(90 + self.mrot), "Y": radians(self.mrot), "Z": radians(90 - self.dip), } # rotation of components so Y is true north self.cosR = cos(self.comps["X"]) self.sinR = sin(self.comps["X"]) # simulation plane always flat, dip = 0 self.cosP = 0 # cos(self.comps['Z']) self.sinP = 1 # sin(self.comps['Z']) # xy dual component rotation matrix # must also flip vertical axis theta = radians(self.mrot) self.rot_matrix = np.array( [[cos(theta), -sin(theta), 0], [-sin(theta), -cos(theta), 0], [0, 0, -1]] ) # save speed when only loaded to read metadata section if meta_only: return # memory map for data section self.data = np.memmap( xyts_path, dtype="%sf4" % (endian), mode="r", offset=60, shape=(self.nt, len(self.comps), self.ny, self.nx), ) # create longitude, latitude map for data grid_points = ( np.mgrid[0 : self.nx_sim : self.dxts, 0 : self.ny_sim : self.dyts] .reshape(2, -1, order="F") .T ) amat = geo.gen_mat(self.mrot, self.mlon, self.mlat)[0] ll_map = geo.xy2ll( geo.gp2xy(grid_points, self.nx_sim, self.ny_sim, self.hh), amat ).reshape(self.ny, self.nx, 2) if np.min(ll_map[:, :, 0]) < -90 and np.max(ll_map[:, :, 0]) > 90: # assume crossing over 180 -> -180, extend past 180 ll_map[ll_map[:, :, 0] < 0, 0] += 360 self.ll_map = ll_map
def test_gp2xy(gp, nx, ny, hh, expected): assert np.allclose(geo.gp2xy(gp, nx, ny, hh), expected, atol=1e-5)