#!/usr/bin/env python
##################### IMPORT STANDARD MODULES #########################
# python 3 compatibility
from __future__ import absolute_import, division, print_function
import sys
if (sys.version_info[:2] < (3, 0)):
from builtins import int,float
import os
import numpy as np #for numerical analysis
#import matplotlib.cm as cmx
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
from matplotlib.ticker import (MultipleLocator, FormatStrFormatter)
from matplotlib.colors import LightSource
#from matplotlib.ticker import (MultipleLocator, FormatStrFormatter,
# AutoMinorLocator)
#import multiprocessing
#from joblib import Parallel, delayed
# from scipy.io import netcdf_file as netcdf #reading netcdf files
import scipy.interpolate as spint
from scipy.sparse import issparse
#import itertools
#import time
import xarray as xr
from six import string_types # to check if variable is string using isinstance
# For polar sectionplot
from matplotlib.transforms import Affine2D
import mpl_toolkits.axisartist.floating_axes as floating_axes
#import mpl_toolkits.axisartist.angle_helper as angle_helper
from matplotlib.projections import PolarAxes
#from mpl_toolkits.axisartist.grid_finder import MaxNLocator,DictFormatter,FixedLocator
from mpl_toolkits.axisartist.grid_finder import DictFormatter,FixedLocator
from matplotlib import gridspec # Relative size of subplots
import pandas as pd
import typing as tp
import pdb
####################### IMPORT AVNI LIBRARIES ###########################
from .. import mapping
from .. import tools
from .. import data
from .. import constants
from .common import initializecolor,updatefont
##########################################################################
[docs]def plot_gcpaths(m,
stlon: tp.Union[float,np.ndarray],stlat: tp.Union[float,np.ndarray],
eplon: tp.Union[float,np.ndarray],eplat: tp.Union[float,np.ndarray],
ifglobal: bool = False,**kwargs):
"""Plots great-circle paths from longitude and latitude arrays.
Parameters
----------
m
An instance of `mpl_toolkits.basemap` Class
stlon : tp.Union[float,np.ndarray]
Longitudes of station location(s)
stlat : tp.Union[float,np.ndarray]
Latitudes of station location(s)
eplon : tp.Union[float,np.ndarray]
Longitudes of station location(s)
eplat : tp.Union[float,np.ndarray]
Latitudes of station location(s)
ifglobal : bool, optional
Set extent to be global, by default False
**kwargs : dict
Optional arguments for Basemap
Returns
-------
m
Updated instance of `mpl_toolkits.basemap` Class
:Authors:
Raj Moulik (moulik@caa.columbia.edu)
:Last Modified:
2023.02.16 5.00
"""
if kwargs:
m.drawgreatcircle(eplon,eplat,stlon,stlat, **kwargs)
m.scatter(stlon, stlat, marker='^',edgecolors='k', **kwargs)
m.scatter(eplon, eplat, marker='o',edgecolors='k', **kwargs)
else:
m.drawgreatcircle(eplon,eplat,stlon,stlat)
m.scatter(stlon, stlat, marker='^', edgecolors='k')
m.scatter(eplon, eplat, marker='o', edgecolors='k')
m.coastlines(color='gray')
if ifglobal: m.set_global() # set global extent
return m
[docs]def plot_hotspots(m,
dbs_path: tp.Union[None,str] = None,
lon360: bool = False, **kwargs):
"""Reads hotspots.pkl from `dbs_path` and plots on to map instance
Earlier, the data was in pickle format, cross-platform compatibility required json
# hotspots = pickle.load(open('%s/hotspots.pkl' % (dbs_path), 'rb'))
# tools.writejson(hotspots,'%s/hotspots.json' % (dbs_path))
Parameters
----------
m
An instance of `mpl_toolkits.basemap` Class
dbs_path : tp.Union[None,str], optional
path specified by user where hotspots.json is located. If not found,
defaults to downloading the file from the AVNI server, by default None
lon360 : bool, optional
False if the no longitude above 180 is permitted and is wrapped around, by default False
**kwargs : dict
Optional arguments for Basemap
Returns
-------
m
Updated instance of `mpl_toolkits.basemap` Class
:Authors:
Raj Moulik (moulik@caa.columbia.edu)
:Last Modified:
2023.02.16 5.00
"""
# Get the correct path
if dbs_path is None:
dbs_path = os.path.join(tools.get_filedir(),constants.dbsfolder)
try:
hotspots = tools.readjson('%s/hotspots.json' % (dbs_path))
except IOError: #Download to default directory
success = False
_,success = data.update_file('hotspots.json',
subdirectory=constants.dbsfolder)
if not success: ValueError("Could not find file hotspots.json")
hotspots = tools.readjson('%s/hotspots.json' % (dbs_path))
if lon360:
hotspots[:,0] = (hotspots[:,0] + 360) % 360.0
x, y = m(hotspots[:,0], hotspots[:,1])
if kwargs:
m.scatter(x, y, **kwargs)
else:
m.scatter(x, y)
return
[docs]def plot_plates(m,
dbs_path: tp.Union[None,str] = None,
lon360: bool = False,
boundtypes: list = ['ridge', 'transform', 'trench'],**kwargs):
"""Plots different types of tectonic plates on to a map object
Parameters
----------
m
An instance of `mpl_toolkits.basemap` Class
dbs_path : tp.Union[None,str], optional
path specified by user where hotspots.json is located. If not found,
defaults to downloading the file from the AVNI server, by default None
lon360 : bool, optional
False if the no longitude above 180 is permitted and is wrapped around, by default False
boundtypes : list, optional
Types of boundaries to plot, by default ['ridge', 'transform', 'trench']
**kwargs : dict
Optional arguments for Basemap
Returns
-------
m
Updated instance of `mpl_toolkits.basemap` Class
:Authors:
Raj Moulik (moulik@caa.columbia.edu)
:Last Modified:
2023.02.16 5.00
"""
# Get the correct path
if dbs_path is None:
dbs_path = os.path.join(tools.get_filedir(),constants.dbsfolder)
# Earlier was in pickle format, cross-platform compatibility required json
# ridge,ridgeloc=pickle.load(open('%s/ridge.pkl' % (dbs_path),'rb'))
# tools.writejson(np.array([ridge,ridgeloc.tolist()]),'%s/ridge.json' % (dbs_path))
for bound in boundtypes:
#name, segs = pickle.load(open('%s/%s.pkl' % (dbs_path,bound), 'rb'))
try:
_ , segs = tools.readjson('%s/%s.json' % (dbs_path,bound))
except IOError: #Download to default directory
success = False
_,success = data.update_file('%s.json' % (bound),
subdirectory=constants.dbsfolder)
if not success: ValueError("Could not find file "+bound+'.json')
_ , segs = tools.readjson('%s/%s.json' % (dbs_path,bound))
segs=np.array(segs)
ind_nan, = np.nonzero(np.isnan(segs[:,0]))
segs[ind_nan,0] = 0
segs[ind_nan,1] = 0
if lon360:
segs[:,0] = (segs[:,0] + 360) % 360.0
x, y = m(segs[:,0], segs[:,1])
x[ind_nan] = np.nan
y[ind_nan] = np.nan
dx = np.abs(x[1:] - x[:-1])
ind_jump, = np.nonzero(dx > 1000000)
x[ind_jump] = np.nan
if kwargs:
m.plot(x, y, '-', **kwargs)
else:
m.plot(x, y, '-')
return
[docs]def globalmap(ax,
valarray: tp.Union[list,np.ndarray],
vmin: tp.Union[float,int],vmax: tp.Union[float,int],
dbs_path: tp.Union[None,str] = None,
colorlabel: tp.Union[None,str] = None, colorticks: bool = True,
ticklabels: tp.Union[None,list,np.ndarray] = None, colorpalette: str = 'avni',
colorcontour=21, hotspots: bool = False,
grid: tp.Union[list,np.ndarray] = [30.,90.], gridwidth: int = 0,
shading: bool = False, model: tp.Union[None,str] = None,
resolution: str = 'l', field: str = 'z', **kwargs):
"""Plots a 2-D cross-section of a 3D model on a predefined axis
Parameters
----------
ax
Axis handle number
valarray : tp.Union[list,np.ndarray]
a named numpy array containing latitudes (lat), longitudes (lon)
and values (val). Can be initialized from three numpy arrays lat, lon and val
$ data = np.vstack((lat,lon,val)).transpose()
$ dt = {'names':['latitude', 'longitude', 'value'], 'formats':[float, float, float]}
$ valarray = np.zeros(len(data), dtype=dt)
$ valarray['latitude'] = data[:,0]; valarray['longitude'] = data[:,1]; valarray['value'] = data[:,2]
vmin,vmax : tp.Union[float,int]
Minimum and maximum value of the color scale
dbs_path : tp.Union[None,str], optional
Path specified by user where database containing hotpot locations,
coastlines is located. If not found, defaults to downloading the files
from the AVNI server, by default None so uses :py:func:`tools.get_filedir()`.
colorlabel : tp.Union[None,str], optional
Label to use for the colorbar. If None, no colorbar is plotted, by default None
colorticks : bool, optional
Label and draw the ticks in the colorbar, by default True
ticklabels : tp.Union[None,list,np.ndarray], optional
Labels for ticks on the colorbar, by default None
colorpalette : str, optional
Matplotlib color scales or the AVNI one, by default 'avni'
colorcontour : int, optional
Number of contours for colors in the plot. Maximum is 520 and odd values
are preferred so that mid value is at white/yellow or other neutral colors, by default 21
hotspots : bool, optional
Plot hotspots, by default False
grid : tp.Union[list,np.ndarray], optional
Grid spacing in latitude and longitude, by default [30.,90.]
gridwidth : int, optional
Width of the grid lines, by default 0
shading : bool, optional
Shade the plot based on topography, by default False
model : tp.Union[None,str], optional
Name of the topography file in NETCDF4 format, by default None so use :py:func:`constants.topography`
resolution : str, optional
Resolution of boundary database to use in Basemap.
Can be c (crude), l (low), i (intermediate), h (high), f (full), by default 'l'
field : str, optional
Field name in the NETCDF4 file to use, by default 'z'
**kwargs : dict
Optional arguments for Basemap
Returns
-------
m
Updated instance of :py:func:`mpl_toolkits.basemap` Class
:Authors:
Raj Moulik (moulik@caa.columbia.edu)
:Last Modified:
2023.02.16 5.00
"""
# Get the default path
if dbs_path is None: dbs_path = tools.get_filedir()
if model is None: model = constants.topography
# defaults
parallels = np.arange(-90.,90.,grid[0])
meridians = np.arange(-180.,180.,grid[1])
# set up map
from mpl_toolkits.basemap import Basemap
if kwargs:
m = Basemap(ax=ax, **kwargs)
else:
projection='robin'
m = Basemap(ax=ax,projection='robin', lon_0=150, resolution=resolution)
#clip_path = m.drawmapboundary()
m.drawcoastlines(linewidth=1.5)
# draw parallels and meridians.
# label parallels on right and top
# meridians on bottom and left
# labels = [left,right,top,bottom]
if not(m.projection == 'hammer' and gridwidth == 0):
m.drawparallels(parallels,labels=[True,False,False,False],linewidth=gridwidth)
m.drawmeridians(meridians,labels=[False,False,False,True],linewidth=gridwidth)
# Get the color map
cpalette = initializecolor(colorpalette)
# define the 10 bins and normalize
if isinstance(colorcontour,np.ndarray) or isinstance(colorcontour,list): # A list of boundaries for color bar
if isinstance(colorcontour,list):
bounds = np.array(colorcontour)
else:
bounds = colorcontour
bounds = bounds[np.where(bounds < vmax)]
bounds = np.append(bounds,np.ceil(vmax))
mytks = np.append(bounds[bounds.nonzero()],np.ceil(vmax))
spacing='uniform'
elif isinstance(colorcontour,(int, float)): # Number of intervals for color bar
bounds = np.linspace(vmin,vmax,colorcontour+1)
mytks = np.arange(vmin,vmax+(vmax-vmin)/4.,(vmax-vmin)/4.)
#mytkslabel = [str(a) for a in mytks]
spacing='proportional'
else:
raise ValueError("Undefined colorcontour in globalmap; should be a numpy array, list or integer")
if np.all(ticklabels!=None): mytks=np.array(ticklabels) # modify to custom ticks
norm = mcolors.BoundaryNorm(bounds,cpalette.N)
#################################################################
# perform the analysis based on expanded nparray or xarray
if type(valarray).__name__ == 'ndarray':
# plot the model
for ii in np.arange(len(valarray['longitude'])):
if valarray['longitude'][ii] > 180.: valarray['longitude'][ii]=valarray['longitude'][ii]-360.
#numlon=len(np.unique(valarray['lon']))
#numlat=len(np.unique(valarray['lat']))
# grid spacing assuming a even grid
# Get the unique lat and lon spacing, avoiding repeated lat/lon
spacing_lat = np.ediff1d(np.sort(valarray['latitude']))
spacing_lat = np.unique(spacing_lat[spacing_lat != 0])
spacing_lon = np.ediff1d(np.sort(valarray['longitude']))
spacing_lon = np.unique(spacing_lon[spacing_lon != 0])
# Check if an unique grid spacing exists for both lat and lon
if len(spacing_lon)!=1 or len(spacing_lat)!=1 or np.any(spacing_lat!=spacing_lon):
print("Warning: spacing for latitude and longitude should be the same. Using nearest neighbor interpolation")
# compute native map projection coordinates of lat/lon grid.
#x, y = m(valarray['lon'], valarray['lat'])
rlatlon = np.vstack([np.ones(len(valarray['longitude'])),valarray['latitude'],valarray['longitude']]).transpose()
xyz = mapping.spher2cart(rlatlon)
# Create a grid
grid_spacing = 1.
lat = np.arange(-90.+grid_spacing/2.,90.+grid_spacing/2.,grid_spacing)
lon = np.arange(-180.+grid_spacing/2.,180.+grid_spacing/2.,grid_spacing)
lons,lats=np.meshgrid(lon,lat)
# evaluate in cartesian
rlatlon = np.vstack([np.ones_like(lons.flatten()),lats.flatten(),lons.flatten()]).transpose()
xyz_new = mapping.spher2cart(rlatlon)
# grid the data.
val = spint.griddata(xyz, valarray['value'], xyz_new, method='nearest').reshape(lons.shape)
#s = m.transform_scalar(val,lon,lat, 1000, 500)
#im = m.imshow(s, cmap=cpalette.name, vmin=vmin, vmax=vmax, norm=norm)
#im = m.contourf(lons, lats,val, norm=norm, cmap=cpalette.name, vmin=vmin, vmax=vmax,latlon=True,extend='both',levels=bounds)
X = lons; Y = lats
else:
grid_spacing = spacing_lat
# Create a grid
lat = np.arange(-90.+grid_spacing/2.,90.+grid_spacing/2.,grid_spacing)
lon = np.arange(-180.+grid_spacing/2.,180.+grid_spacing/2.,grid_spacing)
X,Y=np.meshgrid(lon,lat)
val = np.empty_like(X)
val[:] = np.nan;
for i in range(0, valarray['latitude'].size):
# Get the indices
try: # if unique values exist
ilon = np.where(X[0,:]==valarray['longitude'][i])[0][0]
ilat = np.where(Y[:,0]==valarray['latitude'][i])[0][0]
except IndexError: # take nearest points if unique lat/lon not available
# This is a case when converting pix to epix.
array = np.asarray(X[0,:])
ilon = (np.abs(array - valarray['longitude'][i])).argmin()
array = np.asarray(Y[:,0])
ilat = (np.abs(array - valarray['latitude'][i])).argmin()
val[ilat,ilon] = valarray['value'][i]
#s = m.transform_scalar(val,lon,lat, 1000, 500)
#im=m.pcolormesh(grid_x,grid_y,s,cmap=cpalette.name,vmin=vmin, vmax=vmax, norm=norm)
#im = m.contourf(X, Y,val, norm=norm, cmap=cpalette.name, vmin=vmin, vmax=vmax,latlon=True,extend='both',levels=bounds)
#im = m.imshow(s, cmap=cpalette.name, vmin=vmin, vmax=vmax, norm=norm)
elif type(valarray).__name__ == 'DataArray':
if valarray.dims[0] == 'longitude': valarray = valarray.T
val = valarray.data
X,Y = np.meshgrid(valarray['longitude'].data,valarray['latitude'].data)
else:
raise ValueError(type(valarray).__name__+' input type cannot be plotted by globalmap')
# plot based on shading option
if shading:
im = m.contourf(X, Y,val, norm=norm, cmap=cpalette.name, vmin=vmin, vmax=vmax,latlon=True,extend='both',levels=bounds,zorder=1)
# Illuminate the scene from the northwest
ls = LightSource(azdeg=315, altdeg=45)
plot = tools.readtopography(model=model,resolution=resolution,field=field,latitude_limits=[m.latmin,m.latmax],longitude_limits=[m.lonmin,m.lonmax])
#-- Optional dx and dy for accurate vertical exaggeration ----------------
# If you need topographically accurate vertical exaggeration, or you don't
# want to guess at what *vert_exag* should be, you'll need to specify the
# cellsize of the grid (i.e. the *dx* and *dy* parameters). Otherwise, any
# *vert_exag* value you specify will be relative to the grid spacing of
# your input data (in other words, *dx* and *dy* default to 1.0, and
# *vert_exag* is calculated relative to those parameters). Similarly, *dx*
# and *dy* are assumed to be in the same units as your input z-values.
# Therefore, we'll need to convert the given dx and dy from decimal degrees
# to meters.
dy = constants.deg2m.magnitude * np.mean(np.ediff1d(plot.lat.data))
dx = constants.deg2m.magnitude * np.mean(np.ediff1d(plot.lon.data))
data = ls.hillshade(plot.data, vert_exag=1000, dx=dx, dy=dy)
data_interp= m.transform_scalar(data, plot.lon.data, plot.lat.data, plot.shape[1], plot.shape[0])
m.imshow(data_interp, cmap='gray',interpolation='bilinear', alpha=.4,zorder=2)
else:
im = m.contourf(X, Y,val, norm=norm, cmap=cpalette.name, vmin=vmin, vmax=vmax,latlon=True,extend='both',levels=bounds)
# add plates and hotspots
dbs_path=tools.get_fullpath(dbs_path)
plot_plates(m, dbs_path=dbs_path, color='w', linewidth=1.5)
m.drawmapboundary(linewidth=1.5)
if hotspots: plot_hotspots(m, dbs_path=dbs_path, s=30, color='m', edgecolor='k')
# Add a colorbar
if colorlabel is not None:
# cb = plt.colorbar(im,orientation='vertical',fraction=0.05,pad=0.05)
# cb.set_label(colorlabel)
# Set colorbar, aspect ratio
cbar = plt.colorbar(im, ax=ax, alpha=0.05, aspect=12, shrink=0.5,norm=norm, spacing=spacing, ticks=bounds, boundaries=bounds,extendrect= False)
#cbar = m.colorbar(im, ax=ax,location='right',pad="2%", size='3%', norm=norm, spacing=spacing, ticks=bounds, boundaries=bounds,extendrect= False)
#cbar = plt.colorbar(im, cax=ax, alpha=0.05, aspect=12, shrink=0.5,norm=norm, spacing=spacing, ticks=bounds, boundaries=bounds,extendrect= False)
# Colorbar label, customize fontsize and distance to colorbar
cbar.solids.set_edgecolor("face")
cbar.set_label(colorlabel,rotation=90, labelpad=5)
# Remove colorbar container frame
# cbar.outline.set_visible(False)
# Fontsize for colorbar ticklabels
if colorticks:
# To change fontsize use updatefont
#cbar.ax.tick_params(labelsize=15)
# Customize colorbar tick labels
cbar.set_ticks(mytks)
mytkslabels = [str(int(a)) if isinstance(a, (int, np.integer)) else str(a) for a in mytks]
cbar.ax.set_yticklabels(mytkslabels)
else:
# Remove color bar tick lines, while keeping the tick labels
cbarytks = plt.getp(cbar.ax.axes, 'yticklines')
plt.setp(cbarytks, visible=False)
cbarytks = plt.getp(cbar.ax.axes, 'yticklabels')
plt.setp(cbarytks, visible=False)
return m
[docs]def backgroundmap(ax,
dbs_path: tp.Union[None,str] = None,
plates: str = 'r',oceans: str = 'w',
continents: str = 'darkgray', boundary: str = 'k',**kwargs):
"""Plots a background map of a 3D model on an axis handle.
Parameters
----------
ax
Axis handle number
dbs_path : tp.Union[None,str], optional
Path specified by user where database containing hotpot locations,
coastlines is located. If not found, defaults to downloading the files
from the AVNI server, by default None so uses :py:func:`tools.get_filedir()`.
plates : str, optional
Color of tectonic plates, by default 'r'
oceans : str, optional
Color of oceans, by default 'w'
continents : str, optional
Color of continents, by default 'darkgray'
boundary : str, optional
Color of background around the map, by default 'k'
**kwargs : dict
Optional arguments for Basemap
Returns
-------
m
Updated instance of :py:func:`mpl_toolkits.basemap` Class
:Authors:
Raj Moulik (moulik@caa.columbia.edu)
:Last Modified:
2023.02.16 5.00
"""
# Get the default path
if dbs_path is None: dbs_path = tools.get_filedir()
# Get the correct path
if dbs_path is None:
dbs_path = os.path.join(tools.get_filedir(),constants.dbsfolder)
# set up map
from mpl_toolkits.basemap import Basemap
if kwargs:
m = Basemap(ax=ax, **kwargs)
else:
m = Basemap(ax=ax,projection='robin', lat_0=0, lon_0=150, resolution='l')
# clip_path = m.drawmapboundary()
# draw coastlines.
# m.drawcoastlines(linewidth=1.)
# draw a boundary around the map, fill the background.
# this background will end up being the ocean color, since
# the continents will be drawn on top.
m.drawmapboundary(fill_color=oceans,color=boundary)
# fill continents, set lake color same as ocean color.
m.fillcontinents(color=continents,lake_color=oceans)
# add plates and hotspots
dbs_path=tools.get_fullpath(dbs_path)
plot_plates(m, dbs_path=dbs_path, color=plates, linewidth=1.)
return m
[docs]def insetgcpathmap(ax,
lat1: tp.Union[int,float], lon1: tp.Union[int,float],
azimuth: tp.Union[int,float], gcdelta: tp.Union[int,float],
projection: str = 'ortho', width: float = 50.,height: float = 50.,
dbs_path: tp.Union[None,str] = None,
numdegticks: int = 7, hotspots: bool = False):
"""Plots the great-circle path based on azimuth and delta from initial location.
Takes width/heght arguments in degrees if projection is Mercator, etc.
Parameters
----------
ax
Axis handle number
lat1 : tp.Union[int,float]
Initial location latitude
lon1 : tp.Union[int,float]
Initial location longitude
azimuth : tp.Union[int,float]
Azimuth to final location
gcdelta : tp.Union[int,float]
Distance in degrees to final location
projection : str, optional
Map projection, by default 'ortho'
width : float, optional
Width of the inset map, by default 50.
height : float, optional
Height of the inset map, by default 50.
dbs_path : tp.Union[None,str], optional
Path specified by user where database containing hotpot locations,
coastlines is located. If not found, defaults to downloading the files
from the AVNI server, by default None so uses :py:func:`tools.get_filedir()`.
numdegticks : int, optional
Number of ticks along great-circle path, by default 7
hotspots : bool, optional
Plot hotspots, by default False
Returns
-------
m
Updated instance of :py:func:`mpl_toolkits.basemap` Class
:Authors:
Raj Moulik (moulik@caa.columbia.edu)
:Last Modified:
2023.02.16 5.00
"""
# Get the default path
if dbs_path is None: dbs_path = tools.get_filedir()
# Get the correct path
if dbs_path is None:
dbs_path = os.path.join(tools.get_filedir(),constants.dbsfolder)
# Calculate intermediate points
lat2,lon2=mapping.getDestination(lat1,lon1,azimuth,gcdelta*constants.deg2m.magnitude)
if numdegticks != 0 :
interval=gcdelta*constants.deg2m.magnitude/(numdegticks-1) # interval in km
coords=np.array(mapping.getIntermediate(lat1,lon1,azimuth,gcdelta*constants.deg2m.magnitude,interval))
# Center lat lon based on azimuth
if gcdelta > 350.:
lat_0,lon_0=mapping.getDestination(lat1,lon1,azimuth-90.,90.*constants.deg2m)
elif gcdelta >= 180. and gcdelta <= 350.:
lat_0,lon_0=mapping.getDestination(lat1,lon1,azimuth,90.*constants.deg2m)
else:
lat_0,lon_0=mapping.getDestination(lat1,lon1,azimuth,gcdelta/2.*constants.deg2m)
# Choose what to do based on projection
if projection=='ortho':
if gcdelta == 360.:
boundary = 'w'
else:
boundary = 'k'
m=backgroundmap(ax,tools.get_fullpath(dbs_path),projection=projection, lat_0=lat_0, lon_0=lon_0, resolution='l',boundary=boundary)
else:
# center left lat/lon, then left crnr
latcenleft,loncenleft=mapping.getDestination(lat_0,lon_0,-90.,width*constants.deg2m/2.)
llcrnrlat,llcrnrlon=mapping.getDestination(latcenleft,loncenleft,180.,height*constants.deg2m/2.)
# center right lat/lon, then left crnr
latcenright,loncenright=mapping.getDestination(lat_0,lon_0,90.,width*constants.deg2m/2.)
urcrnrlat,urcrnrlon=mapping.getDestination(latcenright,loncenright,0.,height*constants.deg2m/2.)
m=backgroundmap(ax,tools.get_fullpath(dbs_path),projection=projection, lat_0=lat_0, lon_0=lon_0, resolution='l',llcrnrlon=llcrnrlon, llcrnrlat=llcrnrlat, urcrnrlon=urcrnrlon, urcrnrlat=urcrnrlat)
# draw parallels and meridians.
# label parallels on right and top
# meridians on bottom and left
parallels = np.arange(-90,91,10.)
# labels = [left,right,top,bottom]
m.drawparallels(parallels,labels=[1,0,0,0])
meridians = np.arange(0.,361.,20.)
m.drawmeridians(meridians,labels=[0,0,0,1])
if hotspots: plot_hotspots(m, dbs_path=tools.get_fullpath(dbs_path), s=30, color='lightgreen', edgecolor='k')
if numdegticks != 0 :
if gcdelta > 350.:
dotsstart_x,dotsstart_y=m(coords[0:1][:,1],coords[0:1][:,0])
m.scatter(dotsstart_x,dotsstart_y,s=50,zorder=10,facecolor='orange',edgecolor='k')
dotsstart_x,dotsstart_y=m(coords[1:2][:,1],coords[1:2][:,0])
dots_x,dots_y=m(coords[2:-1][:,1],coords[2:-1][:,0])
m.scatter(dots_x,dots_y,s=50,zorder=10,facecolor='w',edgecolor='k')
m.scatter(dotsstart_x,dotsstart_y,s=50,zorder=10,facecolor='m',edgecolor='k')
#dotsall_x,dotsall_y=m(coords[:,1],coords[:,0])
if gcdelta < 180.:
m.drawgreatcircle(lon1, lat1, lon2, lat2,color='k',linewidth=3.)
elif gcdelta == 180.:
latextent1,lonextent1=mapping.getDestination(lat1,lon1,azimuth,1.*constants.deg2m)
latextent2,lonextent2=mapping.getDestination(lat1,lon1,azimuth,178.*constants.deg2m)
# latextent2,lonextent2=mapping.getDestination(lat_0,lon_0,180.+azimuth,89.*constants.deg2m)
lonextent,latextent=m([lonextent1,lonextent2],[latextent1,latextent2])
m.plot(lonextent,latextent,color='k',linewidth=3.)
return m
[docs]def setup_axes(fig,
rect, theta: tp.Union[list,np.ndarray], radius: tp.Union[list,np.ndarray],
numdegticks: int = 7,
r_locs: list = [3480.,3871.,4371.,4871.,5371.,5871.,6346.6],
r_labels: list = ['CMB',' ','2000',' ','1000',' ','Moho'], fontsize: int = 12):
"""Setup the polar axis for a section plot
Parameters
----------
fig
A figure hand from :py:func:`plt.figure`
rect
A 3-digit number for axis on a plot. Obtained as
axis handle from :py:func:`gridspec.GridSpec` instance.
$gs = gridspec.GridSpec(1, 2)
$rect = gs[1]
theta : tp.Union[list,np.ndarray]
Range of degrees to plot
radius : tp.Union[list,np.ndarray]
Range of radius to plot
numdegticks : int, optional
Number of ticks along great-circle path, by default 7
r_locs : list, optional
Radius locations to plot as curves, by default [3480.,3871.,4371.,4871.,5371.,5871.,6346.6]
r_labels : list, optional
Labels for the radius locations, by default ['CMB',' ','2000',' ','1000',' ','Moho']
fontsize : int, optional
Tick font size, by default 12
Returns
-------
ax1, aux_ax
Axis and auxillary axis where the polar axis plot has been made
:Authors:
Raj Moulik (moulik@caa.columbia.edu)
:Last Modified:
2023.02.16 5.00
"""
# PolarAxes.PolarTransform takes radian. However, we want our coordinate
# system in degree
tr = Affine2D().scale(np.pi/180., 1.) + PolarAxes.PolarTransform()
# Find grid values appropriate for the coordinate (degree).
# The argument is an approximate number of grids.
# theta_grid_locator = angle_helper.LocatorD(numdegticks)
# Stopped using this as is not needed for the plots. Also gives error with some
# matplotlib versions
#theta_grid_locator=FixedLocator(np.arange(theta[0], theta[1], numdegticks))
# And also use an appropriate formatter:
#theta_tick_formatter = angle_helper.FormatterDMS()
# set up number of ticks for the r-axis
# r_grid_locator = MaxNLocator(7)
r_grid_locator=FixedLocator(r_locs)
# Plot the radius ticks
r_ticks = {loc : label for loc, label in zip(r_locs, r_labels)}
r_tick_formatter = DictFormatter(r_ticks)
# the extremes are passed to the function
grid_helper = floating_axes.GridHelperCurveLinear(tr,
extremes=(theta[0], theta[1], radius[0], radius[1]),
grid_locator2=r_grid_locator,
tick_formatter2=r_tick_formatter
#grid_locator1=theta_grid_locator,
#tick_formatter1=theta_tick_formatter
)
ax1 = floating_axes.FloatingSubplot(fig, rect, grid_helper=grid_helper)
fig.add_subplot(ax1)
if theta[1]-theta[0]>350.:
ax1.axis["bottom"].set_visible(False)
ax1.axis["top"].set_visible(False)
ax1.axis["left"].set_visible(False)
ax1.axis["right"].set_visible(False)
elif theta[1]-theta[0]>90. and theta[1]-theta[0]<=180. :
# Make ticks on outside
ax1.axis["left"].set_axis_direction("top")
ax1.axis["right"].set_axis_direction("bottom")
# Make ticks invisible on top and bottom axes
ax1.axis["bottom"].set_visible(False)
ax1.axis["top"].set_visible(False)
ax1.axis["left"].toggle(ticklabels=False, label=False)
ax1.axis["right"].toggle(ticklabels=False, label=False)
else:
# adjust axis
# the axis artist lets you call axis with
# "bottom", "top", "left", "right"
# Make ticks on outside
ax1.axis["left"].set_axis_direction("top")
ax1.axis["right"].set_axis_direction("top")
# Make ticks invisible on top and bottom axes
ax1.axis["bottom"].set_visible(False)
ax1.axis["top"].set_visible(False)
# ax1.axis["top"].set_axis_direction("bottom")
# ax1.axis["top"].toggle(ticklabels=False, label=False)
# ax1.axis["top"].major_ticklabels.set_axis_direction("top")
# ax1.axis["top"].label.set_axis_direction("top")
#
ax1.axis["left"].toggle(ticklabels=False, label=False)
ax1.axis["right"].toggle(ticklabels=True, label=True)
ax1.axis["right"].label.set_axis_direction("bottom")
ax1.axis["right"].major_ticklabels.set_axis_direction("bottom")
ax1.axis["right"].major_ticklabels.set_fontsize(fontsize)
ax1.axis["right"].label.set_text("Depth (km)")
ax1.axis["right"].label.set_fontsize(fontsize)
# ax1.axis["top"].label.set_text(ur"$\alpha$ [\u00b0]")
# create a parasite axes
aux_ax = ax1.get_aux_axes(tr)
aux_ax.patch = ax1.patch # for aux_ax to have a clip path as in ax
ax1.patch.zorder=0.9 # but this has a side effect that the patch is
# drawn twice, and possibly over some other
# artists. So, we decrease the zorder a bit to
# prevent this.
# plot the degree increments at 6371 km, always on top (large zorder)
degticks=np.linspace(theta[0],theta[1],numdegticks)
if theta[1]-theta[0]>350.:
degticks0=degticks[0:1] #color first tick in orange
aux_ax.scatter(degticks0,6346.6*np.ones(len(degticks0)),s=50,clip_on=False,zorder=10,facecolor='orange',edgecolor='k')
degticksstart=degticks[1:2] #color second tick in magenta
degticks=degticks[2:-1] # do not not plot the first and last 2 ticks
else:
degticksstart=degticks[1:2] #color second tick in magenta
degticks=degticks[2:-1] # do not not plot the first and last 2 ticks
aux_ax.scatter(degticks,6346.6*np.ones(len(degticks)),s=50,clip_on=False,zorder=10,facecolor='w',edgecolor='k')
aux_ax.scatter(degticksstart,6346.6*np.ones(len(degticksstart)),s=50,clip_on=False,zorder=10,facecolor='m',edgecolor='k')
# 410 and 650
theta_arr = np.linspace(theta[0],theta[1])
disc_arr=[5961.,5721.]
for disc in disc_arr:
aux_ax.plot(theta_arr, disc*np.ones(len(theta_arr)),linestyle='dashed',color = 'lightgrey',linewidth=1.2,zorder=10)
# Surface ICB CMB
# disc_arr=[6371.,3480.,1215.]
disc_arr=[6346.6,3480.]
for disc in disc_arr:
aux_ax.plot(theta_arr, disc*np.ones(len(theta_arr)), 'k', linewidth=1,zorder=9)
return ax1, aux_ax
[docs]def gettopotransect(lat1: tp.Union[int,float], lon1: tp.Union[int,float],
azimuth: tp.Union[int,float], gcdelta: tp.Union[int,float],
model: tp.Union[None,str] = None,
tree = None, dbs_path: tp.Union[None,str] = None,
numeval: int = 50, resolution: str = 'l',nearest: int = 1):
"""Get the topography transect based on azimuth and delta from initial location.
Parameters
----------
lat1 : tp.Union[int,float]
Initial location latitude
lon1 : tp.Union[int,float]
Initial location longitude
azimuth : tp.Union[int,float]
Azimuth to final location
gcdelta : tp.Union[int,float]
Distance in degrees to final location
model : tp.Union[None,str], optional
Name of the topography file in NETCDF4 format, by default None so :py:func:`constants.topography`
tree
A :py:func:`scipy.spatial.cKDTree` read from an earlier run, by default None
dbs_path : tp.Union[None,str], optional
Path specified by user where database containing hotpot locations,
coastlines is located. If not found, defaults to downloading the files
from the AVNI server, by default None so uses :py:func:`tools.get_filedir()`.
numeval : int, optional
Number of evaluations of topo/bathymetry along the transect, by default 50
resolution : str, optional
Resolution of boundary database to use in Basemap.
Can be c (crude), l (low), i (intermediate), h (high), f (full), by default 'l'
nearest : int, optional
Number of nearest values in the KD-tree to interpolated from, by default
1 so nearest
Returns
-------
valselect,model,tree
Values along selected transect, topography model values, and a :py:func:`scipy.spatial.cKDTree`
:Authors:
Raj Moulik (moulik@caa.columbia.edu)
:Last Modified:
2023.02.16 5.00
"""
#read topography file
if dbs_path is None: dbs_path = tools.get_fullpath(dbs_path)
if model is None: model = constants.topography
# Get KD tree
if tree == None and isinstance(model,string_types):
treefile = dbs_path+'/'+'.'.join(model.split('.')[:-1])+'.KDTree.'+resolution+'.pkl'
ncfile = dbs_path+'/'+model
if not os.path.isfile(ncfile): data.update_file(model,subdirectory=constants.topofolder)
tree = tools.ncfile2tree3D(ncfile,treefile,lonlatdepth = ['lon','lat',None],resolution=resolution,radius_in_km=constants.R.to('km').magnitude)
#read values
model = tools.readtopography(model=model,resolution=resolution,field = 'z', dbs_path=dbs_path)
vals = model.data.flatten(order='C')
else:
#read topography file
try:
vals = model.data.flatten(order='C')
except:
raise ValueError('model in gettopotransect not a string or xarray')
#find destination point
lat2,lng2=mapping.getDestination(lat1,lon1,azimuth,gcdelta*constants.deg2m.magnitude)
interval=gcdelta*constants.deg2m.magnitude/(numeval-1) # interval in m
coords=np.array(mapping.getIntermediate(lat1,lon1,azimuth,gcdelta*constants.deg2m.magnitude,interval))
#query tree for topography
evalpoints=np.column_stack((constants.R.to('km').magnitude*np.ones_like(coords[:,1]),coords[:,0],coords[:,1]))
# get the interpolation
valselect,_ = tools.querytree3D(tree,evalpoints[:,1],evalpoints[:,2],evalpoints[:,0],vals,nearest=nearest)
#print 'THE SHAPE OF qpts_rlatlon is', qpts_rlatlon.shape
return valselect,model,tree
[docs]def plottopotransect(ax,
theta_range: np.ndarray, elev,
vexaggerate: int = 150):
"""Plot a topographic transect on an axis
Parameters
----------
ax
Axis handle number
theta_range : np.ndarray
Range of angles
elev : _type_
Elevation from topgraphy file, usually in :py:func:`sparse.csc_matrix` format.
vexaggerate : int, optional
Vertical exxageration to make the plot visible, by default 150
Returns
-------
ax
Axis handle where the plot has been made
:Authors:
Raj Moulik (moulik@caa.columbia.edu)
:Last Modified:
2023.02.16 5.00
"""
elevplot1=elev.toarray().ravel()
elevplot2=elev.toarray().ravel()
# Blue for areas below sea level
if elev.min()<0.:
lowerlimit=constants.R.to('km').magnitude-elev.min()/1000.*vexaggerate
elevplot2[elevplot2>0.]=0.
ax.fill_between(theta_range, lowerlimit*np.ones(len(theta_range)),lowerlimit*np.ones(len(theta_range))+elevplot2/1000.*vexaggerate,facecolor='aqua', alpha=0.5)
ax.plot(theta_range,lowerlimit*np.ones(len(theta_range))+elevplot2/1000.*vexaggerate,'k',linewidth=0.5)
else:
lowerlimit=constants.R.to('km').magnitude
# Grey for areas above sea level
elevplot1[elevplot1<0.]=0.
ax.fill_between(theta_range, lowerlimit*np.ones(len(theta_range)), lowerlimit*np.ones(len(theta_range))+elevplot1/1000.*vexaggerate, facecolor='grey', alpha=0.5)
ax.plot(theta_range,lowerlimit*np.ones(len(theta_range))+elevplot1/1000.*vexaggerate,'k',linewidth=0.5)
# title(phase, fontsize=20,loc='left')
return ax
[docs]def getmodeltransect(lat1: tp.Union[int,float], lon1: tp.Union[int,float],
azimuth: tp.Union[int,float], gcdelta: tp.Union[int,float],
model: str = 'S362ANI+M.BOX25km_PIX1X1.avni.nc4',
tree = None, parameter: str = 'vs',
radii: list = [3480.,6346.6], dbs_path: tp.Union[None,str] = None,
numevalx: int = 200, numevalz: int = 200,
distnearthreshold: float = 500., nearest: int = 10):
"""Get the tomography slice from a AVNI NETCDF4 file
Parameters
----------
lat1 : tp.Union[int,float]
Initial location latitude
lon1 : tp.Union[int,float]
Initial location longitude
azimuth : tp.Union[int,float]
Azimuth to final location
gcdelta : tp.Union[int,float]
Distance in degrees to final location
model : str, optional
Name of the tomographic model file in NETCDF4 format, by default 'S362ANI+M.BOX25km_PIX1X1.avni.nc4'
tree
A :py:func:`scipy.spatial.cKDTree` read from an earlier run, by default None
parameter : str, optional
Physical parameter field in the NETCDF4 file, by default 'vs'
radii : list, optional
Range of radius to plot on the slice, by default [3480.,6346.6]
dbs_path : tp.Union[None,str], optional
Path specified by user where database containing hotpot locations,
coastlines is located. If not found, defaults to downloading the files
from the AVNI server, by default None so uses :py:func:`tools.get_filedir()`.
numevalx : int, optional
Number of model evaluations along the horizontal direction, by default 200
numevalz : int, optional
Number of model evaluations along the vertical direction, by default 200
distnearthreshold : float, optional
Threshold points that are up to a distance away [NOT IMPLEMENTED], by default 500.
nearest : int, optional
Number of nearest values in the KD-tree to interpolated from, by default
10 so averages nearest 10 points
Returns
-------
xsec,model,tree
Values along selected section, toography model values, and a :py:func:`scipy.spatial.cKDTree`
:Authors:
Raj Moulik (moulik@caa.columbia.edu)
:Last Modified:
2023.02.16 5.00
"""
#defaults
if dbs_path is None: dbs_path = tools.get_filedir()
#get full path
dbs_path=tools.get_fullpath(dbs_path)
# Get KD tree
if tree == None and isinstance(model,string_types):
ncfile = dbs_path+'/'+model
if not os.path.isfile(ncfile):
print('... Downloading Earth model '+model+' from AVNI servers')
data.update_file(model)
print('... Download completed.')
#read values
if os.path.isfile(ncfile):
ds = xr.open_dataset(ncfile)
else:
raise ValueError("Error: Could not find file "+ncfile)
treefile = dbs_path+'/'+constants.planetpreferred+'.'+ds.attrs['kerstr']+'.KDTree.3D.pkl'
tree = tools.ncfile2tree3D(ncfile,treefile,lonlatdepth = ['longitude','latitude','depth'])
model = ds.variables[parameter]
ds.close() #close netcdf file
vals = model.data.flatten(order='C')
else:
#read topography file
try:
vals = model.data.flatten(order='C')
except:
raise ValueError('model in gettopotransect not a string or xarray')
#lat2,lng2=mapping.getDestination(lat1,lon1,azimuth,gcdelta*constants.deg2m.magnitude)
interval=gcdelta*constants.deg2m.magnitude/(numevalx-1) # interval in km
radevalarr=np.linspace(radii[0],radii[1],numevalz) #radius arr in km
coords=np.array(mapping.getIntermediate(lat1,lon1,azimuth,gcdelta*constants.deg2m.magnitude,interval))
if(len(coords) != numevalx):
raise ValueError("Error: The number of intermediate points is not accurate. Decrease it?")
evalpoints=np.column_stack((radevalarr[0]*np.ones_like(coords[:,1]),coords[:,0],coords[:,1]))
for radius in radevalarr[1:]:
pointstemp = np.column_stack((radius*np.ones_like(coords[:,1]),coords[:,0],coords[:,1]))
evalpoints = np.row_stack((evalpoints,pointstemp))
# get the interpolation
npts_surf = len(coords)
tomovals,_ = tools.querytree3D(tree,evalpoints[:,1],evalpoints[:,2],evalpoints[:,0],vals,nearest=nearest)
xsec = tomovals.reshape(npts_surf,len(radevalarr),order='F')
return xsec.T,model,tree
[docs]def section(fig,
lat1: tp.Union[int,float], lon1: tp.Union[int,float],
azimuth: tp.Union[int,float], gcdelta: tp.Union[int,float],
model: str, parameter: str,
vmin: tp.Union[float,int], vmax: tp.Union[float,int],
dbs_path: tp.Union[None,str] = None,
modeltree = None ,
colorlabel: tp.Union[None,str] = None, colorpalette: str = 'avni',
colorcontour: int = 20, nelevinter : int = 100,
radii: list = [3480.,6346.6],vexaggerate: int = 50,
width_ratios: list = [1,3],
numevalx: int = 200, numevalz: int = 300, nearest: int = 10,
topo: tp.Union[None,str] = None, resolution: str = 'l', topotree = None,
hotspots: bool = False, xsec_data = None):
"""Plot one section across a pair of points based on azimuth and delta from initial location.
Parameters
----------
fig
A figure hand from :py:func:`plt.figure`
lat1 : tp.Union[int,float]
Initial location latitude
lon1 : tp.Union[int,float]
Initial location longitude
azimuth : tp.Union[int,float]
Azimuth to final location
gcdelta : tp.Union[int,float]
Distance in degrees to final location
model : str
Name of the tomographic model file in NETCDF4 format e.g. 'S362ANI+M.BOX25km_PIX1X1.avni.nc4'
parameter : str
Physical parameter field in the NETCDF4 file, by default 'vs'
vmin,vmax : tp.Union[float,int]
Minimum and maximum value of the color scale
dbs_path : tp.Union[None,str], optional
Path specified by user where database containing hotpot locations,
coastlines is located. If not found, defaults to downloading the files
from the AVNI server, by default None so uses :py:func:`tools.get_filedir()`.
modeltree, optional
A :py:func:`scipy.spatial.cKDTree` of tomograhic model read from an earlier run, by default None
colorlabel : tp.Union[None,str], optional
Label to use for the colorbar. If None, no colorbar is plotted, by default None
colorpalette : str, optional
Matplotlib color scales or the AVNI one, by default 'avni'
colorcontour : int, optional
Number of contours for colors in the plot. Maximum is 520 and odd values
are preferred so that mid value is at white/yellow or other neutral colors, by default 20
nelevinter : int, optional
Number of evaluations of topo/bathymetry along the transect, by default 100
radii : list, optional
Range of radius to plot on the slice, by default [3480.,6346.6]
vexaggerate : int, optional
Vertical exxageration to make the plot visible, by default 50
width_ratios : list, optional
Width ratios of the great circle and section subplots, by default [1,3]
numevalx : int, optional
Number of model evaluations along the horizontal direction, by default 200
numevalz : int, optional
Number of model evaluations along the vertical direction, by default 300
nearest : int, optional
Number of nearest values in the KD-tree to interpolated from, by default
10 so averages nearest 10 points
topo : tp.Union[None,str], optional
Name of the topography file in NETCDF4 format, by default None so :py:func:`constants.topography`
resolution : str, optional
Resolution of boundary database to use in Basemap.
Can be c (crude), l (low), i (intermediate), h (high), f (full), by default 'l'
topotree : _type_, optional
A :py:func:`scipy.spatial.cKDTree` of topography read from an earlier run, by default None
hotspots : bool, optional
Plot hotspots on top of the plot [NOT IMPLEMENTED], by default False
xsec_data, optional
Interpolated data along a section found from an earlier run, by default None
Returns
-------
fig,topo,topotree,model,modeltree
Figure handle, topography values, tomographic model values and the corresponding :py:func:`scipy.spatial.cKDTree`
:Authors:
Raj Moulik (moulik@caa.columbia.edu)
:Last Modified:
2023.02.16 5.00
"""
#defaults
if dbs_path is None: dbs_path = tools.get_filedir()
if topo is None: topo = constants.topography
#get full path
dbs_path=tools.get_fullpath(dbs_path)
# only sample the data if it's not here.
if xsec_data is None:
interp_values,model,modeltree = getmodeltransect(lat1,lon1,azimuth,gcdelta,model=model,tree=modeltree,parameter=parameter,radii=radii,dbs_path=dbs_path,numevalx=numevalx,numevalz=numevalz,nearest=nearest)
else:
interp_values = xsec_data
numevalx = interp_values.shape[0]
numevalz = interp_values.shape[1]
model = None
modeltree = None
if vmin is None:
vmin = interp_values.min()
if vmax is None:
vmax = interp_values.max()
# Specify theta such that it is symmetric
#lat2,lng2=mapping.getDestination(lat1,lon1,azimuth,gcdelta*constants.deg2m.magnitude)
if gcdelta==180.:
theta=[0.,gcdelta]
elif gcdelta==360.:
# if the start point in (0,0), ortho plot decides orientation based on quadrant
if lat1==0 and lon1==0:
if azimuth < 0.: azimuth = 360. + azimuth
if azimuth < 90 or azimuth == 360.:
quadrant = 0
elif azimuth >= 90 and azimuth < 180.:
quadrant = 1
elif azimuth >= 180 and azimuth < 270.:
quadrant = 2
elif azimuth >= 270 and azimuth < 360.:
quadrant = 3
delta = quadrant*90.
else:
intersection,antipode = mapping.intersection([lat1,lon1],azimuth,[0.,0.],90.)
# shift the plot by the distance between equator and antipode
# This shift is needed to sync with the inset figure in ortho projection
delta_i,_,_ = mapping.get_distaz(lat1,lon1,intersection[0],intersection[1])
delta_a,_,_ = mapping.get_distaz(lat1,lon1,antipode[0],antipode[1])
# ortho projection usually takes the nearest point as the rightmost point
delta = min(delta_i,delta_a)
theta=[delta,gcdelta+delta]
else:
theta=[90.-gcdelta/2.,90.+gcdelta/2.]
theta_range=np.linspace(theta[0],theta[1],nelevinter)
# default is not to extend radius unless vexaggerate!=0
extend_radius=0.
if vexaggerate != 0:
elev,topo,topotree=gettopotransect(lat1,lon1,azimuth,gcdelta,model=topo,tree=topotree, dbs_path=dbs_path,numeval=nelevinter,resolution=resolution,nearest=1)
# hot fix: some combinations of gcdelta, lat,lon result in elev array being
# 1 element shorter. Not sure why.
if theta_range.size - elev.size == 1:
theta_range = theta_range[:-1]
if elev.min()< 0.:
extend_radius=(elev.max()-elev.min())*vexaggerate/1000.
else:
extend_radius=elev.max()*vexaggerate/1000.
# Start plotting
if gcdelta < 360.0:
gs = gridspec.GridSpec(1, 2, width_ratios=width_ratios,figure=fig)
fig.subplots_adjust(wspace=0.01, left=0.05, right=0.95)
ax=fig.add_subplot(gs[0])
elif gcdelta == 360.0:
#ax=fig.add_axes([0.268,0.307,0.375,0.375])
gs = gridspec.GridSpec(1, 1,figure=fig)
#ax = fig.add_subplot(gs[0])
ax=fig.add_axes([0.307,0.29,0.41,0.41])
ax.set_aspect('equal')
else:
raise ValueError("gcdelta > 360.0")
#fig.patch.set_facecolor('white')
####### Inset map
if gcdelta == 360.:
# do not plot ticks on a 360 degree plot,so numdegticks=0. But do so for main plot
insetgcpathmap(ax,lat1,lon1,azimuth,gcdelta,projection='ortho',dbs_path=dbs_path,numdegticks=0)
numdegticks=13
else:
if gcdelta > 270.:
numdegticks=13
insetgcpathmap(ax,lat1,lon1,azimuth,gcdelta,projection='ortho',dbs_path=dbs_path,numdegticks=numdegticks)
elif gcdelta >= 30. and gcdelta <=270:
numdegticks=7
insetgcpathmap(ax,lat1,lon1,azimuth,gcdelta,projection='ortho',dbs_path=dbs_path,numdegticks=numdegticks)
else:
numdegticks=7
width=gcdelta*1.4
height=gcdelta*1.4
insetgcpathmap(ax,lat1,lon1,azimuth,gcdelta,projection='aea',dbs_path=dbs_path,width=width,height=height,numdegticks=numdegticks)
###### Actual cross-section
if gcdelta < 360.0:
ax1, aux_ax1 = setup_axes(fig, gs[1], theta, radius=[3480., 6371.+extend_radius],numdegticks=numdegticks)
elif gcdelta == 360.0:
ax1, aux_ax1 = setup_axes(fig, gs[0], theta, radius=[3480., 6371.+extend_radius],numdegticks=numdegticks)
ax1.set_aspect('equal')
aux_ax1.set_aspect('equal')
#ax1, aux_ax1 = setup_axes(fig, fig.add_axes([0,1,0,1]), theta, radius=[3480., 6371.+extend_radius],numdegticks=numdegticks)
# plot hotspots if within a distance threshold
#if hotspots:
if vexaggerate != 0:
aux_ax1=plottopotransect(aux_ax1,theta_range,elev,vexaggerate=vexaggerate)
# Plot the model section
# grid_x, grid_y = np.mgrid[theta[0]:theta[1]:200j,3480.:6346.6:200j]
#grid_x, grid_y = np.meshgrid(np.linspace(theta[0],theta[1],n3dmodelinter),np.linspace(radii[0],radii[1],n3dmodelinter))
grid_x, grid_y = np.meshgrid(np.linspace(theta[0],theta[1],numevalx),np.linspace(radii[0],radii[1],numevalz))
#zoom = 10
#grid_x_zoom, grid_y_zoom = np.meshgrid(np.linspace(theta[0],theta[1],numevalx*zoom),np.linspace(radii[0],radii[1],numevalz*zoom))
# Get the color map
cpalette = initializecolor(colorpalette)
# interp_values,model,modeltree = getmodeltransect(lat1,lon1,azimuth,gcdelta,model=model,tree=modeltree,parameter=parameter,radii=radii,dbs_path=dbs_path,numevalx=numevalx,numevalz=numevalz,nearest=nearest)
# define the 10 bins and normalize
bounds = np.linspace(vmin,vmax,colorcontour+1)
norm = mcolors.BoundaryNorm(bounds,cpalette.N)
# conversion to numpy arrays to work with pcolormesh
if isinstance(interp_values,xr.DataArray): interp_values = interp_values.toarray()
if issparse(interp_values): interp_values = interp_values.todense()
im=aux_ax1.pcolormesh(grid_x,grid_y,interp_values,cmap=cpalette.name, norm=norm)
# add a colorbar
#levels = MaxNLocator(nbins=colorcontour).tick_values(interp_values.min(), interp_values.max())
#dx = (theta[1]-theta[0])/(numevalx-1); dy = (radii[1]-radii[0])/(numevalz-1)
# tried controurf below but did not make things better than pcolormesh
# xy = mapping.polar2cart(np.vstack([grid_y.flatten(),grid_x.flatten()]).T)
# xy_zoom = mapping.polar2cart(np.vstack([grid_y_zoom.flatten(),grid_x_zoom.flatten()]).T)
#
# grid_x_plot = xy_zoom[:,1].reshape(grid_x_zoom.shape)
# grid_y_plot = xy_zoom[:,0].reshape(grid_y_zoom.shape)
# interp_zoom = spint.griddata(xy,interp_values.flatten(),(grid_y_plot, grid_x_plot), method='cubic')
# im = aux_ax1.contourf(grid_x_zoom,
# grid_y_zoom, interp_zoom, levels=bounds,
# cmap=cpalette.name,vmin=vmin, vmax=vmax)
#
if colorlabel is not None:
# cb = plt.colorbar(im,orientation='vertical',fraction=0.05,pad=0.05)
# cb.set_label(colorlabel)
# Set colorbar, aspect ratio
if gcdelta == 360.0:
cbaxes = fig.add_axes([0.75, 0.3, 0.015, 0.4])
cbar = plt.colorbar(im, alpha=0.05, aspect=12, shrink=0.4,norm=norm, spacing='proportional', ticks=bounds, boundaries=bounds,extendrect=True,cax=cbaxes)
else:
cbar = plt.colorbar(im, alpha=0.05, aspect=12, shrink=0.4,norm=norm, spacing='proportional', ticks=bounds, boundaries=bounds,extendrect=True)
cbar.solids.set_edgecolor('face')
# Remove colorbar container frame
# cbar.outline.set_visible(False)
# Fontsize for colorbar ticklabels
cbar.ax.tick_params(labelsize=12)
# Customize colorbar tick labels
mytks = np.arange(vmin,vmax+(vmax-vmin)/4.,(vmax-vmin)/4.)
cbar.set_ticks(mytks)
cbar.ax.set_yticklabels([str(a) for a in mytks])
# Colorbar label, customize fontsize and distance to colorbar
cbar.set_label(colorlabel,rotation=90, fontsize=14, labelpad=5)
# Remove color bar tick lines, while keeping the tick labels
# cbarytks = plt.getp(cbar.ax.axes, 'yticklines')
# plt.setp(cbarytks, visible=False)
return fig,topo,topotree,model,modeltree
[docs]def plot1section(latitude: tp.Union[int,float], longitude: tp.Union[int,float],
azimuth: tp.Union[int,float], gcdelta: tp.Union[int,float],
model: str, parameter: str,
vmin: tp.Union[float,int], vmax: tp.Union[float,int],
figuresize: tp.Union[list, np.ndarray] = [8,4],
outfile: str = None,**kwargs):
"""Plot one section across a pair of points based on azimuth and delta from initial location..
Parameters
----------
latitude : tp.Union[int,float]
Initial location latitude
longitude : tp.Union[int,float]
Initial location longitude
azimuth : tp.Union[int,float]
Azimuth to final location
gcdelta : tp.Union[int,float]
Distance in degrees to final location
model : str
Name of the tomographic model file in NETCDF4 format e.g. 'S362ANI+M.BOX25km_PIX1X1.avni.nc4'
parameter : str
Physical parameter field in the NETCDF4 file, by default 'vs'
vmin,vmax : tp.Union[float,int]
Minimum and maximum value of the color scale
figuresize : tp.Union[list, np.ndarray], optional
Figure size, by default [8,4]
outfile : str, optional
Output file to use in :py:func:`fig.savefig`, by default None
**kwargs : dict
Optional arguments for Basemap
Returns
-------
topo,topotree,model,modeltree
Topography values, tomographic model values and the corresponding :py:func:`scipy.spatial.cKDTree`
:Authors:
Raj Moulik (moulik@caa.columbia.edu)
:Last Modified:
2023.02.16 5.00
"""
fig = plt.figure(figsize=(figuresize[0],figuresize[1]))
if kwargs:
fig,topo,topotree,model,modeltree = section(fig,latitude,longitude,azimuth,gcdelta,model,parameter,vmin=vmin,vmax=vmax,**kwargs)
else:
fig,topo,topotree,model,modeltree = section(fig,latitude,longitude,azimuth,gcdelta,model,parameter,vmin=vmin,vmax=vmax)
if outfile is not None:
fig.savefig(outfile,dpi=300)
else:
plt.show()
plt.close('all')
return topo,topotree,model,modeltree
[docs]def plot1globalmap(epixarr: np.ndarray,
vmin: tp.Union[float,int], vmax: tp.Union[float,int],
dbs_path: tp.Union[None,str] = None,
colorpalette: str = 'rainbow2', projection: str = 'robin',
colorlabel: str = "Anomaly (%)",
lat_0: tp.Union[int,float] = 0,lon_0: tp.Union[int,float] = 150,
outfile: str = None, shading: bool = False):
"""Plot one global map
Parameters
----------
epixarr : np.ndarray
Array containing (`latitude`, `longitude`, `pixel_size`, `value`)
vmin,vmax : tp.Union[float,int]
Minimum and maximum value of the color scale
dbs_path : tp.Union[None,str], optional
Path specified by user where database containing hotpot locations,
coastlines is located. If not found, defaults to downloading the files
from the AVNI server, by default None so uses :py:func:`tools.get_filedir()`.
colorpalette : str, optional
Matplotlib color scales or the AVNI one, by default 'rainbow2'
projection : str, optional
Map projection, by default 'robin'
colorlabel : str, optional
Label to use for the colorbar. If None, no colorbar is plotted, by default "Anomaly (%)"
lat_0 : tp.Union[int,float], optional
Center latitude for the plot, by default 0
lon_0 : tp.Union[int,float], optional
Center longitude for the plot, by default 150
outfile : str, optional
Output file to use in :py:func:`fig.savefig`, by default None
shading : bool, optional
Shade the plot based on topography, by default False
"""
#defaults
if dbs_path is None: dbs_path = tools.get_fullpath(tools.get_filedir())
fig=plt.figure()
ax=fig.add_subplot(1,1,1)
if projection=='ortho':
globalmap(ax,epixarr,vmin,vmax,dbs_path,colorlabel,grid=[30.,30.],gridwidth=1,projection=projection,lat_0=lat_0, lon_0=lon_0,colorpalette=colorpalette,shading=shading)
else:
globalmap(ax,epixarr,vmin,vmax,dbs_path,colorlabel,grid=[30.,90.],gridwidth=0,projection=projection,lat_0=lat_0, lon_0=lon_0,colorpalette=colorpalette,shading=shading)
if outfile==None:
plt.show()
else:
fig.savefig(outfile,dpi=300)
return
[docs]def plot1hitmap(hitfile: str,
dbs_path: tp.Union[None,str] = None,
projection: str = 'robin',
lat_0: tp.Union[int,float] = 0,lon_0: tp.Union[int,float] = 150,
colorcontour: list = [0,25,100,250,400,600,800,1000,1500,2500,\
5000,7500,10000,15000,20000,25000,30000,35000,40000,45000,50000],
colorpalette: str = 'Blues',
outformat: str = '.pdf',ifshow: bool = True):
"""Plot one hit count map
Parameters
----------
hitfile : str
A file containg named columns - "latitude", "longitude", "value"
dbs_path : tp.Union[None,str], optional
Path specified by user where database containing hotpot locations,
coastlines is located. If not found, defaults to downloading the files
from the AVNI server, by default None so uses :py:func:`tools.get_filedir()`.
projection : str, optional
Map projection, by default 'robin'
lat_0 : tp.Union[int,float], optional
Center latitude for the plot, by default 0
lon_0 : tp.Union[int,float], optional
Center longitude for the plot, by default 150
colorcontour : list, optional
Number of contours for colors in the plot,
by default [0,25,100,250,400,600,800,1000,1500,2500,5000,7500,10000,15000,20000,25000,30000,35000,40000,45000,50000]
colorpalette : str, optional
Matplotlib color scales or the AVNI one, by default 'Blues'
outformat : str, optional
Output file format, by default '.pdf'
ifshow : bool, optional
Display the plot before writing a file, by default True
"""
#defaults
if dbs_path is None: dbs_path = tools.get_fullpath(tools.get_filedir())
fig=plt.figure()
ax=fig.add_subplot(1,1,1)
header_list = ["latitude", "longitude", "value"]
hit_array = np.genfromtxt(hitfile,names=header_list,delimiter=None)
grids = np.delete(np.unique(np.ediff1d(hit_array['latitude'])),[0])
if len(grids) != 1: raise ValueError('len(grids) != 1')
grid_spacing=grids[0]
maxhit=max(hit_array['value'])
colorcontour=np.array(colorcontour)
idx = (np.abs(colorcontour - maxhit)).argmin() # find nearest index to upper centile
colorcontour = colorcontour[:idx+1]
if maxhit > 1500:
maxlog10 = int(np.log10(maxhit))
ticklabels=np.logspace(0,maxlog10,maxlog10+1).astype(int)
colorcontour=np.logspace(0,maxlog10,2*maxlog10+1).astype(int)
remaining=np.logspace(maxlog10,np.log10(maxhit),3).astype(int)[1:]
colorcontour = np.concatenate((colorcontour,remaining))
ticklabels = np.append(ticklabels,int(maxhit))
maxhit=int(maxhit)
#hit_array['val']=np.log10(hit_array['val'])
if isinstance(grid_spacing, (int, np.integer)): grid_spacing=int(grid_spacing)
colorlabel="# "+"$Rays$"+" "+"$(%s$"%grid_spacing+"$^\circ bins)$"
if projection=='ortho':
globalmap(ax,hit_array,0.,int(maxhit),dbs_path,ticklabels=ticklabels,colorlabel=colorlabel,colorcontour=colorcontour,grid=[30.,30.],gridwidth=1,projection=projection,lat_0=lat_0, lon_0=lon_0,colorpalette=colorpalette)
else:
globalmap(ax,hit_array,0.,int(maxhit),dbs_path,ticklabels=ticklabels,colorlabel=colorlabel,colorcontour=colorcontour,grid=[30.,90.],gridwidth=0,projection=projection,lat_0=lat_0, lon_0=lon_0,colorpalette=colorpalette)
ax.set_title(hitfile)
if ifshow: plt.show()
fig.savefig(hitfile+outformat,dpi=300)
return
[docs]def plotreference1d(ref1d,
figuresize: list = [7,12],
height_ratios: list = [2, 2, 1],
ifshow: bool = True, format: str = '.eps',
isotropic: bool = False, zoomdepth: list = [0.,1000.]):
"""Plot the ref1d object array in a PREM like plot
Parameters
----------
ref1d
An instance of the :py:class:`Reference1D` class.
figuresize : list, optional
Figure size, by default [7,12]
height_ratios : list, optional
Height ratios of the three subplots, by default [2, 2, 1]
ifshow : bool, optional
Display the plot before writing a file, by default True
format : str, optional
Output file format, by default '.eps'
isotropic : bool, optional
Whether model is isotropic so seperate curves for Vsh and Vsv, by default False
zoomdepth : list, optional
Zoom into a depth extent in km, by default [0.,1000.]
"""
# extract values
depthkmarr = (constants.R-ref1d.data['radius']).pint.to('km').values.quantity.magnitude
rho = ref1d.data['rho'].pint.to('g/cm^3').values.quantity.magnitude
vs = ref1d.data['vs'].pint.to('km/s').values.quantity.magnitude
vp = ref1d.data['vp'].pint.to('km/s').values.quantity.magnitude
vsv = ref1d.data['vsv'].pint.to('km/s').values.quantity.magnitude
vsh = ref1d.data['vsh'].pint.to('km/s').values.quantity.magnitude
vpv = ref1d.data['vpv'].pint.to('km/s').values.quantity.magnitude
vph = ref1d.data['vph'].pint.to('km/s').values.quantity.magnitude
eta = ref1d.data['eta'].values.quantity.magnitude
qmu = ref1d.data['qmu'].values.quantity.magnitude
#Set default fontsize for plots
updatefont(10)
fig = plt.figure(1, figsize=(figuresize[0],figuresize[1]))
gs = gridspec.GridSpec(3, 1, height_ratios=height_ratios)
fig.patch.set_facecolor('white')
ax01=plt.subplot(gs[0])
ax01.plot(depthkmarr,rho,'k')
ax01.plot(depthkmarr,vsv,'b')
ax01.plot(depthkmarr,vsh,'b:')
ax01.plot(depthkmarr,vpv,'r')
ax01.plot(depthkmarr,vph,'r:')
mantle=np.where( depthkmarr < 2891.)
ax01.plot(depthkmarr[mantle],eta[mantle],'g')
ax01.set_xlim([0., constants.R.to('km').magnitude])
ax01.set_ylim([0, 14])
majorLocator = MultipleLocator(2)
majorFormatter = FormatStrFormatter('%d')
minorLocator = MultipleLocator(1)
ax01.yaxis.set_major_locator(majorLocator)
ax01.yaxis.set_major_formatter(majorFormatter)
# for the minor ticks, use no labels; default NullFormatter
ax01.yaxis.set_minor_locator(minorLocator)
majorLocator = MultipleLocator(2000)
majorFormatter = FormatStrFormatter('%d')
minorLocator = MultipleLocator(1000)
ax01.xaxis.set_major_locator(majorLocator)
ax01.xaxis.set_major_formatter(majorFormatter)
# for the minor ticks, use no labels; default NullFormatter
ax01.xaxis.set_minor_locator(minorLocator)
ax01.set_ylabel('Velocity (km/sec), density (g/cm'+'$^3$'+') or '+'$\eta$')
for para,color,xloc,yloc in [("$\eta$",'g',1500.,2.),("$V_S$",'b',1500.,7.8),("$V_P$",'r',1500.,13.5),("$\\rho$",'k',1500.,4.5),("$V_P$",'r',4000.,9.2),("$\\rho$",'k',4000.,12.5),("$V_S$",'b',5500.,4.5)]:
ax01.annotate(para,color=color,
xy=(3, 1), xycoords='data',
xytext=(xloc/constants.R.to('km').magnitude, yloc/14.), textcoords='axes fraction',
horizontalalignment='left', verticalalignment='top')
ax11=plt.subplot(gs[1])
depthselect=np.intersect1d(np.where( depthkmarr >= zoomdepth[0]),np.where( depthkmarr <= zoomdepth[1]))
ax11.plot(depthkmarr[depthselect],rho[depthselect],'k')
if isotropic:
ax11.plot(depthkmarr[depthselect],vs[depthselect],'b')
else:
ax11.plot(depthkmarr[depthselect],vsv[depthselect],'b')
ax11.plot(depthkmarr[depthselect],vsh[depthselect],'b:')
ax12 = ax11.twinx()
if isotropic:
ax12.plot(depthkmarr[depthselect],vp[depthselect],'r')
else:
ax12.plot(depthkmarr[depthselect],vpv[depthselect],'r')
ax12.plot(depthkmarr[depthselect],vph[depthselect],'r:')
ax11.plot(depthkmarr[depthselect],eta[depthselect],'g')
ax11.set_xlim(zoomdepth)
ax11.set_ylim([0, 7])
ax12.set_xlim(zoomdepth)
ax12.set_ylim([-2, 12])
ax11.set_ylabel('Shear velocity (km/sec), density (g/cm'+'$^3$'+') or '+'$\eta$')
ax12.set_ylabel('Compressional velocity (km/sec)')
for para,color,xloc,yloc in [("$\eta$",'g',150.,1.),("$V_{S}$",'b',150.,4.3),("$V_{P}$",'r',120.,5.5),("$\\rho$",'k',150.,3.8)]:
ax11.annotate(para,color=color,
xy=(3, 1), xycoords='data',
xytext=(xloc/1000., yloc/7.), textcoords='axes fraction',
horizontalalignment='left', verticalalignment='top')
ax12.set_yticks(np.arange(6, 14, step=2))
majorLocator = MultipleLocator(200)
majorFormatter = FormatStrFormatter('%d')
minorLocator = MultipleLocator(100)
ax11.xaxis.set_major_locator(majorLocator)
ax11.xaxis.set_major_formatter(majorFormatter)
# for the minor ticks, use no labels; default NullFormatter
ax11.xaxis.set_minor_locator(minorLocator)
ax21=plt.subplot(gs[2], sharex=ax11)
with np.errstate(divide='ignore', invalid='ignore'): # Ignore warning about dividing by zero
anisoVs=(vsh-vsv)*200./(vsh+vsv)
anisoVp=(vph-vpv)*200./(vph+vpv)
ax21.plot(depthkmarr[depthselect],anisoVs[depthselect],'b')
ax21.plot(depthkmarr[depthselect],anisoVp[depthselect],'r')
ax21.set_ylim([0, 5])
ax21.set_xlim(zoomdepth)
majorLocator = MultipleLocator(1)
majorFormatter = FormatStrFormatter('%d')
minorLocator = MultipleLocator(0.5)
ax21.yaxis.set_major_locator(majorLocator)
ax21.yaxis.set_major_formatter(majorFormatter)
# for the minor ticks, use no labels; default NullFormatter
ax21.yaxis.set_minor_locator(minorLocator)
for para,color,xloc,yloc in [('Q'+'$_{\mu}$','k',400.,2.5),("$a_{S}$",'b',150.,3.7),("$a_{P}$",'r',100.,1.8)]:
ax21.annotate(para,color=color,
xy=(3, 1), xycoords='data',
xytext=(xloc/1000., yloc/4.), textcoords='axes fraction',
horizontalalignment='left', verticalalignment='top')
ax22 = ax21.twinx()
ax22.plot(depthkmarr[depthselect],qmu[depthselect],'k')
ax21.set_xlabel('Depth (km)')
ax21.set_ylabel("$V_P$"+' or '+"$V_S$"+' anisotropy (%)')
ax22.set_ylabel('Shear attenuation Q'+'$_{\mu}$')
ax22.set_ylim([0, 400])
ax22.set_xlim(zoomdepth)
majorLocator = MultipleLocator(100)
majorFormatter = FormatStrFormatter('%d')
minorLocator = MultipleLocator(50)
ax22.yaxis.set_major_locator(majorLocator)
ax22.yaxis.set_major_formatter(majorFormatter)
# for the minor ticks, use no labels; default NullFormatter
ax22.yaxis.set_minor_locator(minorLocator)
if ifshow:
plt.show()
else:
plt.savefig(ref1d.name+format)
[docs]def plotmodel3d(model3d,
dbs_path: tp.Union[None,str] = None,
x: int = 0,
percent_or_km: str = '%',
colormin: tp.Union[int,float] = -6.,colormax: tp.Union[int,float] = 6.,
depth: tp.Union[None,list,np.ndarray] = None,
resolution: int = 0,realization: int = 0):
"""Plots interactively a model slice of a variable at a given depth till an invalid depth is input by the user
Parameters
----------
model3d
An instance of the :py:class:`Model3D` class.
dbs_path : tp.Union[None,str], optional
Path specified by user where database containing hotpot locations,
coastlines is located. If not found, defaults to downloading the files
from the AVNI server, by default None so uses :py:func:`tools.get_filedir()`.
x : int, optional
Index for variable to plot, by default 0
percent_or_km : str, optional
Plot in percent (relative) or km/s (absolute) [NOT IMPLEMENTED], by default '%'
colormin, colormax : tp.Union[int,float], optional
Minimum and maximum value of the color scale, by default -6 and 6.
depth : tp.Union[None,list,np.ndarray], optional
Depth to plot, by default None
resolution : int, optional
Resolution index in the :py:class:`Model3D` instance, by default 0
realization : int, optional
Realization index in the :py:class:`Model3D` instance, by default 0
"""
#defaults
if dbs_path is None: dbs_path = tools.get_fullpath(tools.get_filedir())
if not isinstance(resolution, int): raise TypeError('resolution must be an integer, not %s' % type(resolution))
if not isinstance(realization, int): raise TypeError('realization must be an integer, not %s' % type(realization))
typehpar = model3d.metadata['resolution_'+str(resolution)]['typehpar']
if len(typehpar) != 1 or typehpar[0] != 'PIXELS': raise ValueError('Slices can only be made for pixel paramterization')
# Select appropriate arrays from projection matrix, read from file
lat = model3d.metadata['resolution_'+str(resolution)]['xlapix'][0]
lon = model3d.metadata['resolution_'+str(resolution)]['xlopix'][0]
refstrarr = model3d.metadata['resolution_'+str(resolution)]['varstr']
# select models based on parameter and depth desired
new_figure='y' # flag for done
while (new_figure =='y' or new_figure == 'Y'):
plt.ion()
fig=plt.figure()
try:
subplotstr = input("Provide rows and colums of subplots - default is 1 1:")
subploty,subplotx = int(subplotstr.split()[0]),int(subplotstr.split()[1])
except (ValueError,IndexError,SyntaxError,EOFError):
subploty = 1; subplotx=1
flag=0 # flag for depth
while (flag < subploty*subplotx):
flag=flag+1
ifplot =True
try:
for ii in np.arange(len(refstrarr)): print(ii,refstrarr[ii])
try:
x = int(input("Select variable to plot - default is 0:"))
except (ValueError,EOFError):
x = x
if 'topo' in refstrarr[x]:
#find the radial kernels for this paramter
kerfind = np.where(model3d.metadata['resolution_'+str(resolution)]['ivarkern']==x+1)[0]
if len(kerfind) == 1:
modelarray = model3d.data['resolution_'+str(resolution)]['realization_'+str(realization)]['coef'].iloc[kerfind[0]]
else:
flag=flag-1
ifplot =False
else:
# get the depths available for this parameter
deptharr = model3d.getpixeldepths(resolution,refstrarr[x])
#depth differences and get depth extents
depdiff = np.ediff1d(deptharr)
deptop = np.copy(deptharr)
depbottom = np.copy(deptharr)
for ii in range(len(depdiff)-2):
deptop[ii] = deptop[ii] - (2.*depdiff[ii]-depdiff[ii+1])/2.
depbottom[ii] = depbottom[ii] + (2.*depdiff[ii]-depdiff[ii+1])/2.
for ii in range(len(depdiff),len(depdiff)-3,-1):
deptop[ii] = deptop[ii] - (2.*depdiff[ii-1]-depdiff[ii-2])/2.
depbottom[ii] = depbottom[ii]+ (2.*depdiff[ii-1]-depdiff[ii-2])/2.
try:
depth = float(input("Select depth - select any value for topography ["+str(round(min(deptop),2))+"-"+str(round(max(depbottom),2))+"] :"))
except (ValueError,EOFError):
if depth is None:
depth = min(deptharr)
else:
depth = depth
if depth < min(deptop) or depth > max(depbottom):
flag=flag-1
ifplot =False
else:
#find the radial kernels for this paramter
kerfind = np.where(model3d.metadata['resolution_'+str(resolution)]['ivarkern']==x+1)[0]
#evaluate at all points
ind = np.where(np.logical_and(depth>deptop, depth<=depbottom))[0][0]
modelarray = model3d.data['resolution_'+str(resolution)]['realization_'+str(realization)]['coef'].iloc[kerfind[ind]]
if ifplot:
# Get limits for colorbar
try:
colorstr = input("Input two values for minimum and maximum values of colorbar - default is "+str(colormin)+" "+str(colormax)+":")
colormin,colormax = float(colorstr.split()[0]),float(colorstr.split()[1])
except (ValueError,IndexError,EOFError):
colormin = colormin; colormax=colormax
# Plot the model
test = np.vstack((lat,lon,modelarray)).transpose()
dt = {'names':['lat', 'lon', 'val'], 'formats':[float, float, float]}
plotmodel = np.zeros(len(test), dtype=dt)
plotmodel['lat'] = test[:,0]; plotmodel['lon'] = test[:,1]; plotmodel['val'] = test[:,2]
ax=fig.add_subplot(subploty,subplotx,flag)
globalmap(ax,plotmodel,colormin,colormax,dbs_path=dbs_path, colorlabel='Anomaly', grid=[30.,90.],gridwidth=0,projection='robin',lat_0=0, lon_0=150., colorpalette='avni',colorcontour=21)
ax.set_title(refstrarr[x]+' at '+str(depth)+' km.' if 'Topo' not in refstrarr[x] and 'topo' not in refstrarr[x] else refstrarr[x])
fig.canvas.draw()
except SyntaxError:
flag=flag-1
try:
new_figure = input("Another figure? y/n:")
except (EOFError):
new_figure = 'n'
return