def xyz2llz(x, y, z): """convert earth-centered, earth-fixed (ECEF) cartesian x, y, z to latitude, longitude, and altitude code is based on: https://www.mathworks.com/matlabcentral/fileexchange/7941-convert-cartesian--ecef--coordinates-to-lat--lon--alt? focused=5062924&tab=function Keyword arguments: x: x-coordinate normalized to the radius of Earh y: y-coordinate normalized to the radius of Earh z: z-coordinate normalized to the radius of Earh Return values: lat: latitude (deg) lon: longitude (deg) depth: depth (km) """ # World Geodetic System 1984, WGS 84 erad = np.float64( 6378137.0) # Radius of the Earth in meters (equatorial radius, WGS84) rad = 1 # sphere radius e = np.float64(8.1819190842622e-2) # convert to radius x = x * erad / rad y = y * erad / rad z = z * erad / rad b = np.sqrt(erad * erad * (1 - e * e)) ep = np.sqrt((erad * erad - b * b) / (b * b)) p = np.sqrt(x * x + y * y) th = np.arctan2(erad * z, b * p) lon = np.arctan2(y, x) lat = np.arctan2((z + ep * ep * b * np.sin(th) * np.sin(th) * np.sin(th)), (p - e * e * erad * np.cos(th) * np.cos(th) * np.cos(th))) N = erad / np.sqrt(1.0 - e * e * np.sin(lat) * np.sin(lat)) alt = p / np.cos(lat) - N lon = np.mod(lon, (math.pi * 2.0)) lon = np.rad2deg(lon) lon = utils.lon_180(lon) lat = np.rad2deg(lat) alt = -1 * alt / 1000.0 # depth as negative alt return lat, lon, alt
def read_geocsv_model_2d(model_file, ll, ur, inc, roughness, unit_factor=1, base=0, extent=False): """Read in a 3-D Earth model in the GeoCSV format. Keyword arguments: model_file: model file ll: lower-left coordinate ur: upper-right coordinate inc: grid sampling interval roughness: set the variable as depth and use this for exaggeration extent: should only compute model extent? (True or False) Return values: x: x-coordinate normalized to the radius of the Earth y: y-coordinate normalized to the radius of the Earth z: z-coordinate normalized to the radius of the Earth meta: file metadata information """ # model data and metadata (params, lines) = read_geocsv(model_file, is_2d=True) # model data raw_data = [] for line in lines: raw_data.append(line.split(params['delimiter'])) data = [list(utils.str2float(sublist)) for sublist in raw_data] # model variables variables = [] for this_param in list(params.keys()): if params[this_param] not in ( params['longitude_column'], params['latitude_column']) and '_column' in this_param: variables.append(params[this_param]) # index to the variables var_index = {} for i, val in enumerate(params['header']): if val == params['longitude_column']: lon_index = i elif val == params['latitude_column']: lat_index = i else: for this_var in variables: if val == params[this_var + '_column']: var_index[this_var] = i break lat = list(set(get_column(data, lat_index))) lon = list(set(get_column(data, lon_index))) lon.sort(key=float) lon, lon_map = utils.lon_180(lon, fix_gap=True) depth = [base] # get coordinates sorted otherwise inc will not function properly lat.sort(key=float) lon.sort(key=float) # select the values within the ranges (this is to get a count only) latitude, longitude, depth2 = get_points_in_area(lat, lon, depth, ll, ur, inc) # model data grid definition V = {} nx = len(longitude) ny = len(depth2) nz = len(latitude) if extent: return nx - 1, ny - 1, nz - 1 meta = { 'depth': [], 'lat': [100, -100], 'lon': [400, -400], 'source': model_file } for l, var_val in enumerate(variables): X = np.zeros((nx, ny, nz)) X[:] = np.nan Y = np.zeros((nx, ny, nz)) Y[:] = np.nan Z = np.zeros((nx, ny, nz)) Z[:] = np.nan # we want the grid to be in x, y z order (longitude, depth, latitude) data.sort(key=itemgetter(lat_index, lon_index)) for i, values in enumerate(data): lon_val = float(lon_map[utils.float_key(values[lon_index])]) lat_val = float(values[lat_index]) this_value = float(values[var_index[var_val]]) if this_value is None: depth_val = base else: depth_val = base + float(this_value) * float( roughness) * float(unit_factor) if lon_val in longitude and lat_val in latitude: meta['lon'] = [ min(meta['lon'][0], lon_val), max(meta['lon'][0], lon_val) ] meta['lat'] = [ min(meta['lat'][0], lat_val), max(meta['lat'][0], lat_val) ] if depth_val not in meta['depth']: meta['depth'].append(depth_val) x, y, z = llz2xyz(lat_val, lon_val, depth_val) ii = longitude.index(lon_val) jj = 0 kk = latitude.index(lat_val) X[ii, jj, kk] = x Y[ii, jj, kk] = y Z[ii, jj, kk] = z if var_val not in list(V.keys()): V[var_val] = np.zeros((nx, ny, nz)) V[var_val][:] = np.nan if '_'.join([var_val, 'missing_value']) in params: # skip the designated missing_value if float(this_value) == float(params['_'.join( [var_val, 'missing_value'])]): continue V[var_val][ii, jj, kk] = float(this_value) return X, Y, Z, V, meta
def read_netcdf_model(model_file, lat_variable, lon_variable, depth_variable, ll, ur, depth_min, depth_max, roughness, inc, extent=False): """read in an EMC Earth model in the netCDF format Keyword arguments: model_file: model file ll: lower-left coordinate ur: upper-right coordinate depth_min: minimum depth depth_max: maximum depth inc: grid sampling interval Return values: x: x-coordinate normalized to the radius of the Earth y: y-coordinate normalized to the radius of the Earth z: z-coordinate normalized to the radius of the Earth meta: file metadata information """ # ParaView on some platforms does not have SciPy module if not utils.support_nc(): print( "[ERR] Cannot read netCDF files on this platform, try GeoCSV format!" ) return [], [], [], [], {} # NetCDF files, when opened read-only, return arrays that refer directly to memory-mapped data on disk: print(f"[INFO] Reading model file {model_file}") data = netcdf.netcdf_file(model_file, 'r') variables = [] for name in list(data.variables.keys()): if name not in (depth_variable, lon_variable, lat_variable): variables.append(name) # expects variables be a function of latitude, longitude and depth, find the order var = variables[0] for i, value in enumerate(data.variables[var].dimensions): if value == depth_variable: depth_index = i elif value == lon_variable: lon_index = i else: lat_index = i lat = data.variables[lat_variable][:].copy() lon = data.variables[lon_variable][:].copy() lon, lon_map = utils.lon_180(lon, fix_gap=True) depth = data.variables[depth_variable][:].copy() # select the values within the ranges (this is to get a count only) latitude, longitude, depth2 = get_points_in_volume(lat, lon, depth, ll, ur, inc, depth_min, depth_max) # model data grid definition V = {} nx = len(longitude) ny = len(depth2) nz = len(latitude) if extent: return nx - 1, ny - 1, nz - 1 index = [-1, -1, -1] meta = { 'depth': [], 'lat': [100, -100], 'lon': [400, -400], 'source': model_file } missing_value = None if hasattr(data.variables[var], 'missing_value'): missing_value = float(data.variables[var].missing_value) for l, var_val in enumerate(variables): X = np.zeros((nx, ny, nz)) Y = np.zeros((nx, ny, nz)) Z = np.zeros((nx, ny, nz)) v = np.zeros((nx, ny, nz)) data_in = data.variables[var_val][:].copy() # increment longitudes, we want to keep the first and last longitude regardless of inc for i, lon_val in enumerate(lon): for j, depth_val in enumerate(depth): for k, lat_val in enumerate(lat): if lon_val in longitude and lat_val in latitude and depth_val in depth2: meta['lon'] = [ min(meta['lon'][0], lon_val), max(meta['lon'][0], lon_val) ] meta['lat'] = [ min(meta['lat'][0], lat_val), max(meta['lat'][0], lat_val) ] if depth_val not in meta['depth']: meta['depth'].append(depth_val) x, y, z = llz2xyz(lat_val, lon_val, depth_val * roughness) ii = longitude.index(lon_val) jj = depth2.index(depth_val) kk = latitude.index(lat_val) X[ii, jj, kk] = x Y[ii, jj, kk] = y Z[ii, jj, kk] = z index[depth_index] = j index[lat_index] = k index[lon_index] = i this_value = data_in[index[0]][index[1]][index[2]] if this_value is None: v[ii, jj, kk] = None elif this_value is not None: if this_value == missing_value: v[ii, jj, kk] = None this_value = None else: v[ii, jj, kk] = this_value else: v[ii, jj, kk] = this_value V[var_val] = v data.close() return X, Y, Z, V, meta
def read_geocsv_model_3d(model_file, ll, ur, depth_min, depth_max, roughness, inc, extent=False): """Read in a 3-D Earth model in the GeoCSV format. Keyword arguments: model_file: model file ll: lower-left coordinate ur: upper-right coordinate depth_min: minimum depth depth_max: maximum depth inc: grid sampling interval extent: provide model extent only (True or False) Return values: x: x-coordinate normalized to the radius of the Earth y: y-coordinate normalized to the radius of the Earth z: z-coordinate normalized to the radius of the Earth meta: file metadata information """ # model data and metadata (params, lines) = read_geocsv(model_file) # model data data = [] for line in lines: data.append(line.split(params['delimiter'])) # model variables depth_variable = params['depth_column'] lat_variable = params['latitude_column'] lon_variable = params['longitude_column'] elev_variable = params['elevation_column'] variables = [] for this_param in list(params.keys()): if params[this_param] not in ( depth_variable, lon_variable, lat_variable, elev_variable) and '_column' in this_param: variables.append(params[this_param]) # index to the variables var_index = {} for i, val in enumerate(params['header']): if val == depth_variable: depth_index = i elif val == lon_variable: lon_index = i elif val == lat_variable: lat_index = i else: var_index[val] = i lat = np.array(list(set(get_column(data, lat_index))), dtype=float) lon = np.array(list(set(get_column(data, lon_index))), dtype=float) # -180/180 models are the norm, so we convert 0/360 models to -180/180 first to unify # the rest of the code for all models data = np.ndarray.tolist(np.asfarray(data)) if utils.lon_is_360(lon): for i, values in enumerate(data): if float(values[lon_index]) > 180.0: data[i][lon_index] = float(values[lon_index]) - 360.0 lon = np.array(list(set(get_column(data, lon_index))), dtype=float) # we want the grid to be in x, y z order (longitude, depth, latitude) data.sort(key=itemgetter(lon_index, depth_index, lat_index)) lon.sort() lon, lon_map = utils.lon_180(lon, fix_gap=True) depth = np.array(list(set(get_column(data, depth_index))), dtype=float) # get coordinates sorted one last time lat.sort() lon.sort() depth.sort() # select the coordinates within the ranges (this is to get a count only) latitude = [] longitude = [] depth2 = [] last_i = -1 for i, lon_val in enumerate(lon): if i != 0 and i != len(lon) - 1 and i != last_i + inc: continue last_i = i for j, depth_val in enumerate(depth): last_k = -1 for k, lat_val in enumerate(lat): if k != 0 and k != len(lat) - 1 and k != last_k + inc: continue last_k = k if utils.isValueIn(float(lat_val), ll[0], ur[0]) and utils.isLongitudeIn( float(lon_val), ll[1], ur[1]) and \ utils.isValueIn(float(depth_val), depth_min, depth_max): if float(lon_map[utils.float_key( lon_val)]) not in longitude: longitude.append( float(lon_map[utils.float_key(lon_val)])) if float(depth_val) not in depth2: depth2.append(float(depth_val)) if float(lat_val) not in latitude: latitude.append(float(lat_val)) # model data grid definition V = {} nx = len(longitude) ny = len(depth2) nz = len(latitude) if extent: return nx - 1, ny - 1, nz - 1 meta = { 'depth': [], 'lat': [100, -100], 'lon': [400, -400], 'source': model_file } X = np.zeros((nx, ny, nz)) X[:] = np.nan Y = np.zeros((nx, ny, nz)) Y[:] = np.nan Z = np.zeros((nx, ny, nz)) Z[:] = np.nan for i, values in enumerate(data): lon_val = float(lon_map[utils.float_key(values[lon_index])]) lat_val = float(values[lat_index]) depth_val = float(values[depth_index]) if lon_val in longitude and lat_val in latitude and depth_val in depth2: meta['lon'] = [ min(meta['lon'][0], lon_val), max(meta['lon'][0], lon_val) ] meta['lat'] = [ min(meta['lat'][0], lat_val), max(meta['lat'][0], lat_val) ] if depth_val not in meta['depth']: meta['depth'].append(depth_val) x, y, z = llz2xyz(lat_val, lon_val, depth_val * roughness) ii = longitude.index(lon_val) jj = depth2.index(depth_val) kk = latitude.index(lat_val) X[ii, jj, kk] = x Y[ii, jj, kk] = y Z[ii, jj, kk] = z for l, var_val in enumerate(variables): if var_val not in list(V.keys()): V[var_val] = np.zeros((nx, ny, nz)) V[var_val][:] = np.nan if '_'.join([var_val, 'missing_value']) in params: # skip the designated missing_value if float(values[var_index[var_val]]) == float( params['_'.join([var_val, 'missing_value'])]): continue V[var_val][ii, jj, kk] = float(values[var_index[var_val]]) return X, Y, Z, V, meta
def read_slab_file(model_file, ll, ur, inc=1, depth_factor=-1, extent=False): """read in a 2-D netCDF Slab file Keyword arguments: model_file: model file ll: lower-left coordinate ur: upper-right coordinate inc: grid sampling interval RReturn values: X: x-coordinate normalized to the radius of Earh Y: y-coordinate normalized to the radius of Earh Z: z-coordinate normalized to the radius of Earh label: file label """ # ParaView on some systems does not have SciPy module if not utils.support_nc(): print( "[ERR] Cannot read netCDF files on this platform, try GeoCSV format!" ) return [], [], [], [], '' z_variable = 'z' lon_variable = 'x' lat_variable = 'y' depth = 0 # model data data = netcdf.netcdf_file(model_file, 'r') lat = data.variables[lat_variable][:].copy() lon = data.variables[lon_variable][:].copy() lon = utils.lon_180(lon) elevation_data = data.variables[z_variable][:].copy() data.close() dep = [depth] variables = [z_variable] # select the values within the ranges (this is to get a count only) latitude, longitude, depth2 = get_points_in_area(lat, lon, dep, ll, ur, inc) # model data grid definition V = {} nx = len(longitude) ny = len(depth2) nz = len(latitude) if extent: return nx - 1, ny - 1, nz - 1 label = '' if len(depth2): label = "%0.1f-%0.1fkm" % (min(depth2), max(depth2)) for l, var_value in enumerate(variables): X = np.zeros((nx, ny, nz)) Y = np.zeros((nx, ny, nz)) Z = np.zeros((nx, ny, nz)) v = np.zeros((nx, ny, nz)) for i, lon_val in enumerate(lon): for j, depth_val in enumerate(dep): for k, lat_val in enumerate(lat): if lon_val in longitude and lat_val in latitude and depth_val in depth2: ii = longitude.index(lon_val) jj = depth2.index(depth_val) kk = latitude.index(lat_val) x, y, z = llz2xyz(lat[k], lon[i], elevation_data[k][i] * depth_factor) X[ii, jj, kk] = x Y[ii, jj, kk] = y Z[ii, jj, kk] = z v[ii, jj, kk] = elevation_data[k][i] V[z_variable] = v return X, Y, Z, V, label
def read_netcdf_topo_file(model_file, ll, ur, inc, roughness, lon_var='longitude', lat_var='latitude', elev_var='elevation', base=0, unit_factor=1, extent=False): """read in etopo, a 2-D netCDF topo file Keyword arguments: model_file: model file ll: lower-left coordinate ur: upper-right coordinate inc: grid sampling interval roughness: set the variable as depth and use this for exaggeration extent: should only compute model extent? (True or False) RReturn values: X: x-coordinate normalized to the radius of Earh Y: y-coordinate normalized to the radius of Earh Z: z-coordinate normalized to the radius of Earh label: file label """ # ParaView on some platforms does not have SciPy module if not utils.support_nc(): print("[ERR] Sorry, cannot read netCDF files on this platform!") return 0, 0, 0 z_variable = elev_var lon_variable = lon_var lat_variable = lat_var depth = base # model data data = netcdf.netcdf_file(model_file, 'r') lat = data.variables[lat_variable][:].copy() lon = data.variables[lon_variable][:].copy() lon = utils.lon_180(lon) elevation_data = data.variables[z_variable][:].copy() data.close() dep = [depth] variables = [z_variable] # select the values within the ranges (this is to get a count only) latitude, longitude, depth2 = get_points_in_area(lat, lon, dep, ll, ur, inc) # model data grid definition V = {} nx = len(longitude) ny = len(depth2) nz = len(latitude) if extent: return nx - 1, ny - 1, nz - 1 label = '' if hasattr(data, 'description'): label = data.description elif hasattr(data, 'title'): label = data.title for l, var_value in enumerate(variables): X = np.zeros((nx, ny, nz)) Y = np.zeros((nx, ny, nz)) Z = np.zeros((nx, ny, nz)) v = np.zeros((nx, ny, nz)) for i, lon_val in enumerate(lon): for j, depth_val in enumerate(dep): for k, lat_val in enumerate(lat): if lon_val in longitude and lat_val in latitude and depth_val in depth2: ii = longitude.index(lon_val) jj = depth2.index(depth_val) kk = latitude.index(lat_val) # "+" since it is elevation but we already making roughness negative to make it positive up x, y, z = llz2xyz( lat_val, lon_val, depth_val + (elevation_data[k][i] * roughness * unit_factor)) X[ii, jj, kk] = x Y[ii, jj, kk] = y Z[ii, jj, kk] = z v[ii, jj, kk] = elevation_data[k][i] * float(unit_factor) V[z_variable] = v return X, Y, Z, V, label