Acceso a ECCOv4 por medio de EarthData in the Cloud de la NASA#
Este tutorial demuestra acceso al producto ECCOv4 de un modelo numerico global. Para mayor informacion del producto de ECCO, puede ir aqui.
Requisitos
Tener una cuenta de Earth Data Login por medio de la NASA.
Tener un Token valido.
Tambien se puede utilizar el metodo de Nombre de Usuario / Contrasena descrito en el tutorial de Autenticacion
Objectivos
Utilizar pydap para demostrar
Acceso to archivos cientificos de la NASA por medio del uso de
tokens
y EarthData como metodo de autenticacion.
Algunas variables de interes son:
Autor
: Miguel Jimenez-Urias, ‘24
import matplotlib.pyplot as plt
import numpy as np
from pydap.net import create_session
from pydap.client import open_url
import xarray as xr
import pydap
print("xarray version: ", xr.__version__)
print("pydap version: ", pydap.__version__)
xarray version: 2025.4.0
pydap version: 3.5.5
Acceso a EARTHDATA#
El listado de las variables de este producto pueden encontrarse aqui. En este caso, accederemos a las variables en su malla original, el LL90.
Grid_url = 'https://opendap.earthdata.nasa.gov/providers/POCLOUD/collections/ECCO%20Geometry%20Parameters%20for%20the%20Lat-Lon-Cap%2090%20(llc90)%20Native%20Model%20Grid%20(Version%204%20Release%204)/granules/GRID_GEOMETRY_ECCO_V4r4_native_llc0090'
Autenticacion via .netrc
#
Las credenciales son recuperadas automaticamente por pydap
.
my_session = create_session()
Alternativa: El use the tokens#
session_extra = {"token": "YourToken"}
# initialize a requests.session object with the token headers. All handled by pydap.
my_session = create_session(session_kwargs=session_extra)
Accesso a los metadatos solamente, por medio de pydap#
pydap
aprovecha el protocolo de OPeNDAP, el cual permite la separacion de los metadatos de los valores numericos. Esto permite una inspeccion remota de los datasets.
ds_grid = open_url(Grid_url, session=my_session, protocol="dap4")
ds_grid.tree()
.GRID_GEOMETRY_ECCO_V4r4_native_llc0090.nc
├──dxC
├──PHrefF
├──XG
├──dyG
├──rA
├──hFacS
├──Zp1
├──Zl
├──rAw
├──dxG
├──maskW
├──YC
├──XC
├──maskS
├──YG
├──hFacC
├──drC
├──drF
├──XC_bnds
├──Zu
├──Z_bnds
├──YC_bnds
├──PHrefC
├──rAs
├──Depth
├──dyC
├──SN
├──rAz
├──maskC
├──CS
├──hFacW
├──Z
├──i
├──i_g
├──j
├──j_g
├──k
├──k_l
├──k_p1
├──k_u
├──nb
├──nv
└──tile
Note
PyDAP accesa solo a los metadatos de archivo en el servidor de OPeNDAP, y ningun arreglo numerico ha sido descargardo hasta este punto!
Para descargar los arreglos numericos hay que indexar la variable
Esto es, de la siguiente manera:
# this fetches remote data into a pydap object container
pydap_Array = dataset[<VarName>][:]
donde <VarName>
es el nombre de una de las variables. El producto sera una representation “en memoria” de tipo pydap.model.BaseType
, el cual permite el acceso a los arreglos de numpy
.
Para extraer los valores de la variable remota, hay que ejecutar el siguiente comando
# The `.data` command allows direct access to the Numpy array (e.g. for manipulation)
pydap_Array.data
# lets download some data
Depth = ds_grid['Depth'][:]
print(type(Depth))
<class 'pydap.model.BaseType'>
Depth.attributes
{'_FillValue': 9.969209968e+36,
'long_name': 'model seafloor depth below ocean surface at rest',
'units': 'm',
'coordinate': 'XC YC',
'coverage_content_type': 'modelResult',
'standard_name': 'sea_floor_depth_below_geoid',
'comment': "Model sea surface height (SSH) of 0m corresponds to an ocean surface at rest relative to the geoid. Depth corresponds to seafloor depth below geoid. Note: the MITgcm used by ECCO V4r4 implements 'partial cells' so the actual model seafloor depth may differ from the seafloor depth provided by the input bathymetry file.",
'coordinates': 'YC XC',
'origname': 'Depth',
'fullnamepath': '/Depth',
'Maps': (),
'checksum': array([950678499], dtype=uint32)}
Depth.shape, Depth.dims
((13, 90, 90), ['/tile', '/j', '/i'])
Visualizando el fondo oceanico Depth
en la malla original del modelo
En este caso, el producto ECCO
esta definido en una malla con topologia de un Cubo Esferico
. De esta manera, la malla horizontal contiene una dimension extra llamada: tile
o face
. A continuacion visualizamos la variable en su topologia original
Variable = [Depth[i].data for i in range(13)]
clevels = np.linspace(0, 6000, 100)
cMap = 'Greys_r'
fig, axes = plt.subplots(nrows=5, ncols=5, figsize=(8, 8), gridspec_kw={'hspace':0.01, 'wspace':0.01})
AXES = [
axes[4, 0], axes[3, 0], axes[2, 0], axes[4, 1], axes[3, 1], axes[2, 1],
axes[1, 1],
axes[1, 2], axes[1, 3], axes[1, 4], axes[0, 2], axes[0, 3], axes[0, 4],
]
for i in range(len(AXES)):
AXES[i].contourf(Variable[i], clevels, cmap=cMap)
for ax in np.ravel(axes):
ax.axis('off')
plt.setp(ax.get_xticklabels(), visible=False)
plt.setp(ax.get_yticklabels(), visible=False)
plt.show()

Fig. 1. La variable Depth
visualizada en una malla horitonzal. Tile
s con valores 0-5
tienen un ordenamiento de valores (indiciales) C-ordering
, mientras que cualquier arreglo numerico tile
s 7-13
siguen el F-ordering
. Cualquier arreglo numerico en el arctic cap
, tiene un comportamiento de una coordenada polar.
Visualizacion con topologia corregida
fig, axes = plt.subplots(nrows=4, ncols=4, figsize=(8, 8), gridspec_kw={'hspace':0.01, 'wspace':0.01})
AXES_NR = [
axes[3, 0], axes[2, 0], axes[1, 0], axes[3, 1], axes[2, 1], axes[1, 1],
]
AXES_CAP = [axes[0, 0]]
AXES_R = [
axes[1, 2], axes[2, 2], axes[3, 2], axes[1, 3], axes[2, 3], axes[3, 3],
]
for i in range(len(AXES_NR)):
AXES_NR[i].contourf(Variable[i], clevels, cmap=cMap)
for i in range(len(AXES_CAP)):
AXES_CAP[i].contourf(Variable[6].transpose()[:, ::-1], clevels, cmap=cMap)
for i in range(len(AXES_R)):
AXES_R[i].contourf(Variable[7+i].transpose()[::-1, :], clevels, cmap=cMap)
for ax in np.ravel(axes):
ax.axis('off')
plt.setp(ax.get_xticklabels(), visible=False)
plt.setp(ax.get_yticklabels(), visible=False)
plt.show()

Fig. 2. Visualizacion de la variable Depth
en una malla horizontal que approxima una malla horizontal uniforme con ejes lat-lon
. Sin embargo, cualquier arreglo numerico en el arctic cap
, continua en una malla que approxima una malla en coordenadas polares
Utilizando xarray
#
pydap
se puede llamar dentro de xarray
para acceder a los archivos expuestos mediante el servidor de OPeNDAP. En particular, xarray
permite aggregar multiples archivos remotos de opendap. A continuacion demostramos un pequeno ejemplo.
Un URL individual –> un archivo remoto
Below we access remote Temperature and Salinity ECCO data with xarray
via (internally) pydap
.
baseURL = 'https://opendap.earthdata.nasa.gov/providers/POCLOUD/collections/'
Temp_Salt = "ECCO%20Ocean%20Temperature%20and%20Salinity%20-%20Monthly%20Mean%20llc90%20Grid%20(Version%204%20Release%204)/granules/OCEAN_TEMPERATURE_SALINITY_mon_mean_"
year = '2017-'
month = '01'
end_ = '_ECCO_V4r4_native_llc0090'
Temp_2017 = baseURL + Temp_Salt +year + month + end_
# Convertimos el URL en un DAP4 opendap url
Temp_2017 = Temp_2017.replace("https", "dap4")
Temp_2017
'dap4://opendap.earthdata.nasa.gov/providers/POCLOUD/collections/ECCO%20Ocean%20Temperature%20and%20Salinity%20-%20Monthly%20Mean%20llc90%20Grid%20(Version%204%20Release%204)/granules/OCEAN_TEMPERATURE_SALINITY_mon_mean_2017-01_ECCO_V4r4_native_llc0090'
pyds = open_url(Temp_2017, session=my_session)
pyds.tree()
.OCEAN_TEMPERATURE_SALINITY_mon_mean_2017-01_ECCO_V4r4_native_llc0090.nc
├──XG
├──Zp1
├──Zl
├──YC
├──XC
├──SALT
├──YG
├──XC_bnds
├──Zu
├──THETA
├──Z_bnds
├──YC_bnds
├──time_bnds
├──Z
├──i
├──i_g
├──j
├──j_g
├──k
├──k_l
├──k_p1
├──k_u
├──nb
├──nv
├──tile
└──time
Para evitar descargas variables que no sean necesariamente de interest, se le puede instruir al Servidor Hyrax que variables requiere uno.
Para esto utilizaremos las Expresiones de Restriccion
(CE, por sus siglas en ingles). Este metodo nos ayudara a construir datasets mucho mas simples,
y evitar descargar N veces, informacion que no es requerida.
A continuacion demostramos el caso de solo requerir la variable THETA
y sus dimensiones.
dims = pyds['/THETA'].dims
Vars = ['/THETA'] + dims
# Below construct Contraint Expression
CE = "?dap4.ce="+(";").join(Vars)
print("Constraint Expression: ", CE)
Constraint Expression: ?dap4.ce=/THETA;/time;/k;/tile;/j;/i
Temp_2017 = baseURL + Temp_Salt +year + month + end_ + CE
Temp_2017
'https://opendap.earthdata.nasa.gov/providers/POCLOUD/collections/ECCO%20Ocean%20Temperature%20and%20Salinity%20-%20Monthly%20Mean%20llc90%20Grid%20(Version%204%20Release%204)/granules/OCEAN_TEMPERATURE_SALINITY_mon_mean_2017-01_ECCO_V4r4_native_llc0090?dap4.ce=/THETA;/time;/k;/tile;/j;/i'
Importante#
Ahora aplicamos la CE a todos los possibles URLs the nuestro dataset.
Temp_2017 = [baseURL.replace("https", "dap4") + Temp_Salt + year + f'{i:02}' + end_ + CE for i in range(1, 13)]
Temp_2017[:3]
['dap4://opendap.earthdata.nasa.gov/providers/POCLOUD/collections/ECCO%20Ocean%20Temperature%20and%20Salinity%20-%20Monthly%20Mean%20llc90%20Grid%20(Version%204%20Release%204)/granules/OCEAN_TEMPERATURE_SALINITY_mon_mean_2017-01_ECCO_V4r4_native_llc0090?dap4.ce=/THETA;/time;/k;/tile;/j;/i',
'dap4://opendap.earthdata.nasa.gov/providers/POCLOUD/collections/ECCO%20Ocean%20Temperature%20and%20Salinity%20-%20Monthly%20Mean%20llc90%20Grid%20(Version%204%20Release%204)/granules/OCEAN_TEMPERATURE_SALINITY_mon_mean_2017-02_ECCO_V4r4_native_llc0090?dap4.ce=/THETA;/time;/k;/tile;/j;/i',
'dap4://opendap.earthdata.nasa.gov/providers/POCLOUD/collections/ECCO%20Ocean%20Temperature%20and%20Salinity%20-%20Monthly%20Mean%20llc90%20Grid%20(Version%204%20Release%204)/granules/OCEAN_TEMPERATURE_SALINITY_mon_mean_2017-03_ECCO_V4r4_native_llc0090?dap4.ce=/THETA;/time;/k;/tile;/j;/i']
#
Debajo inicializaremos una session que persista, cada vez que descargue datos de servidores de OPeNDAP. En este caso utilizaremost la libraria
requests_cache
. El comportamiento es casi identico excepto la siguiente celda:
cache_session = create_session(use_cache=True) # caching the session
cache_session.cache.clear() # clear all cache to demonstrate the behavior
Tip
Ahora se puede usar el metodo consolidated_metadata
para que pydap descarge todos los metadatos, y reusarlos.
Para usar consolidated_metadata
se requiere installar una version de pydap >= 3.5.5
.
from pydap.client import consolidate_metadata
%%time
consolidate_metadata(Temp_2017, cache_session)
datacube has dimensions {'j[0:1:89]', 'i[0:1:89]', 'tile[0:1:12]', 'time[0:1:0]', 'k[0:1:49]'}
CPU times: user 367 ms, sys: 207 ms, total: 575 ms
Wall time: 9.69 s
%%time
theta_salt_ds = xr.open_mfdataset(
Temp_2017,
engine='pydap',
session=cache_session,
parallel=True,
combine='nested',
concat_dim='time',
decode_times=False
)
CPU times: user 392 ms, sys: 235 ms, total: 627 ms
Wall time: 468 ms
theta_salt_ds
<xarray.Dataset> Size: 253MB Dimensions: (time: 12, k: 50, tile: 13, j: 90, i: 90) Coordinates: * i (i) int32 360B 0 1 2 3 4 5 6 7 8 9 ... 81 82 83 84 85 86 87 88 89 * j (j) int32 360B 0 1 2 3 4 5 6 7 8 9 ... 81 82 83 84 85 86 87 88 89 * k (k) int32 200B 0 1 2 3 4 5 6 7 8 9 ... 41 42 43 44 45 46 47 48 49 * tile (tile) int32 52B 0 1 2 3 4 5 6 7 8 9 10 11 12 * time (time) int32 48B 219528 219528 219528 ... 219528 219528 219528 Data variables: THETA (time, k, tile, j, i) float32 253MB dask.array<chunksize=(1, 50, 13, 90, 90), meta=np.ndarray> Attributes: (12/62) acknowledgement: This research was carried out by the Jet... author: Ian Fenty and Ou Wang cdm_data_type: Grid comment: Fields provided on the curvilinear lat-l... Conventions: CF-1.8, ACDD-1.3 coordinates_comment: Note: the global 'coordinates' attribute... ... ... time_coverage_duration: P1M time_coverage_end: 2017-12-31T12:00:00 time_coverage_resolution: P1M time_coverage_start: 2017-12-01T00:00:00 title: ECCO Ocean Temperature and Salinity - Mo... uuid: fba67542-4181-11eb-97c8-0cc47a3f47df
Finalmente, visualizamos THETA
#
Variable = [theta_salt_ds['THETA'][0, 0, i, :, :] for i in range(13)]
clevels = np.linspace(-5, 30, 100)
cMap='RdBu_r'
fig, axes = plt.subplots(nrows=4, ncols=4, figsize=(8, 8), gridspec_kw={'hspace':0.01, 'wspace':0.01})
AXES_NR = [
axes[3, 0], axes[2, 0], axes[1, 0], axes[3, 1], axes[2, 1], axes[1, 1],
]
AXES_CAP = [axes[0, 0]]
AXES_R = [
axes[1, 2], axes[2, 2], axes[3, 2], axes[1, 3], axes[2, 3], axes[3, 3],
]
for i in range(len(AXES_NR)):
AXES_NR[i].contourf(Variable[i], clevels, cmap=cMap)
for i in range(len(AXES_CAP)):
AXES_CAP[i].contourf(Variable[6].transpose()[:, ::-1], clevels, cmap=cMap)
for i in range(len(AXES_R)):
AXES_R[i].contourf(Variable[7+i].transpose()[::-1, :], clevels, cmap=cMap)
for ax in np.ravel(axes):
ax.axis('off')
plt.setp(ax.get_xticklabels(), visible=False)
plt.setp(ax.get_yticklabels(), visible=False)
plt.show()

Fig. 3. Visualizacion de la variable Surface temperature
, similar al metodo utilizado en la Figura 2.