Get global topographic data at the resolution of the grids used by GEOS-Chem

A recurrent problem, when analysing GEOS-Chem outputs and/or comparing it with other data sources, is to get the altitude values at the vertical levels of a GEOS-Chem grid. GEOS-Chem outputs include grid-box heights (in meters) but don't provide the altitude a.s.l of the base levels (i.e., the model topography).

This notebooks shows how to load global topographic elevation data (any raster format supported by the GDAL library) and then resample it on a given GEOS-Chem grid. The original, high resolution elevation data is downsampled to a GC grid using the regridding methods available in the Iris package.

NOTE: Both data loading and regridding operations performed here are not optimized for very high resolution datasets in terms of memory consumption and computing efficiency!

Author: B. Bovy (Nov. 2014)

Required packages


In [1]:
import numpy as np
import gdal, osr
import iris

import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import iris.plot as iplt

import pygchem

plt.style.use('ggplot')
%matplotlib inline

In [2]:
iris.__version__


Out[2]:
'1.7.2'

In [3]:
pygchem.__version__


Out[3]:
'0.3.0dev'

Load the topographic data

The example below uses the USGS/NGA's GMTED2010 global elevation data (Mean Statistic, 30 arc-seconds version). It is a high quality, multi-resolution product for global and continental applications. More info here: http://topotools.cr.usgs.gov/gmted_viewer/. The dataset can be downloaded in the ESRI's ArcGIS/Info binary grid format.

NOTE: The elevation raster must have lat/lon coordinates (no projection)! It is also assumed that the geographic coordinate system of the elevation raster is equivalent to the coordinate system used by the GEOS-Chem grids (GMAO Met Fields). No datum/ellipsoid/spheroid transformation is performed here.

  • Set the Digital Elevation Model (DEM) file or dataset name and set metadata that is not provided with the dataset

In [4]:
dem_file = "/home/bovy/GISData/Elevation/GMTED2010/mn30_grd"
dem_name = "GMTED2010_mn30"
dem_units = "m"
  • Print some information about the DEM dataset (available metadata)

In [5]:
!gdalinfo $dem_file -norat -noct -nogcp


Driver: AIG/Arc/Info Binary Grid
Files: /home/bovy/GISData/Elevation/GMTED2010/mn30_grd
       /home/bovy/GISData/Elevation/GMTED2010/mn30_grd.aux.xml
       /home/bovy/GISData/Elevation/GMTED2010/mn30_grd/metadata.xml
       /home/bovy/GISData/Elevation/GMTED2010/mn30_grd/z001001x.adf
       /home/bovy/GISData/Elevation/GMTED2010/mn30_grd/dblbnd.adf
       /home/bovy/GISData/Elevation/GMTED2010/mn30_grd/vat.adf
       /home/bovy/GISData/Elevation/GMTED2010/mn30_grd/w001001x.adf
       /home/bovy/GISData/Elevation/GMTED2010/mn30_grd/w001000.adf
       /home/bovy/GISData/Elevation/GMTED2010/mn30_grd/w001000x.adf
       /home/bovy/GISData/Elevation/GMTED2010/mn30_grd/sta.adf
       /home/bovy/GISData/Elevation/GMTED2010/mn30_grd/z001001.adf
       /home/bovy/GISData/Elevation/GMTED2010/mn30_grd/w001001.adf
       /home/bovy/GISData/Elevation/GMTED2010/mn30_grd/prj.adf
       /home/bovy/GISData/Elevation/GMTED2010/mn30_grd/hdr.adf
Size is 43200, 20880
Coordinate System is:
GEOGCS["WGS 84",
    DATUM["WGS_1984",
        SPHEROID["WGS 84",6378137,298.257223563,
            AUTHORITY["EPSG","7030"]],
        TOWGS84[0,0,0,0,0,0,0],
        AUTHORITY["EPSG","6326"]],
    PRIMEM["Greenwich",0,
        AUTHORITY["EPSG","8901"]],
    UNIT["degree",0.0174532925199433,
        AUTHORITY["EPSG","9108"]],
    AUTHORITY["EPSG","4326"]]
Origin = (-180.000138888888927,83.999860415111328)
Pixel Size = (0.008333333300000,-0.008333333300000)
Corner Coordinates:
Upper Left  (-180.0001389,  83.9998604) (180d 0' 0.50"W, 83d59'59.50"N)
Lower Left  (-180.0001389, -90.0001389) (180d 0' 0.50"W, 90d 0' 0.50"S)
Upper Right ( 179.9998597,  83.9998604) (179d59'59.49"E, 83d59'59.50"N)
Lower Right ( 179.9998597, -90.0001389) (179d59'59.49"E, 90d 0' 0.50"S)
Center      (  -0.0001396,  -3.0001392) (  0d 0' 0.50"W,  3d 0' 0.50"S)
Band 1 Block=256x16 Type=Int16, ColorInterp=Undefined
  Min=-430.000 Max=8625.000 
  NoData Value=-32768
  • Load the DEM and get its raster values into a Numpy array.

NOTE: Since all data is loaded into RAM, it can consume a lot of memory when using high resolution data!


In [6]:
dem_rst = gdal.Open(dem_file)
dem_data = dem_rst.ReadAsArray().astype('d')
  • Make sure that the elevation raster's coordinate system is geographic (lat/lon). The code below will raise an error if the raster's coordinate system is not geographic.

In [7]:
dem_cs = osr.SpatialReference()
dem_cs.ImportFromWkt(dem_rst.GetProjection())

if not dem_cs.IsGeographic():
    raise ValueError("The raster's coordinate system must be geographic!")
  • Create an Iris's GeogCS object from the raster's coord system.

In [8]:
cube_cs = iris.coord_systems.GeogCS(
    semi_major_axis=dem_cs.GetSemiMajor(),
    semi_minor_axis=dem_cs.GetSemiMinor(),
    longitude_of_prime_meridian=dem_cs.GetProjParm('PRIMEM')
)

print(cube_cs)


GeogCS(semi_major_axis=6378137.0, semi_minor_axis=6356752.31425)
  • Retreive the lat, lon coordinates values of the top/left corners of the DEM grid cells, then compute arrays for the latitude and longitude of the centers and bounds of the grid cells.

In [9]:
dem_gt = dem_rst.GetGeoTransform()
nrows, ncols = dem_data.shape

y_ind = np.arange(0, nrows + 1)
x_ind = np.arange(0, ncols + 1)

dem_lon_left = dem_gt[0] + (x_ind * dem_gt[1])
dem_lat_top = dem_gt[3] + (y_ind * dem_gt[5])

dem_lon_bounds = np.column_stack((dem_lon_left[:-1], dem_lon_left[1:]))
dem_lat_bounds = np.column_stack((dem_lat_top[:-1], dem_lat_top[1:]))

dem_lon_centers = (dem_lon_left[0:-1] + dem_lon_left[1:]) / 2.
dem_lat_centers = (dem_lat_top[0:-1] + dem_lat_top[1:]) / 2.
  • Finally, create an Iris cube for the global topography (elevation + coordinates)

In [10]:
clon = iris.coords.DimCoord(
    dem_lon_centers,
    bounds=dem_lon_bounds,
    standard_name='longitude',
    units='degrees_east',
    coord_system=cube_cs
)
clat = iris.coords.DimCoord(
    dem_lat_centers,
    bounds=dem_lat_bounds,
    standard_name='latitude',
    units='degrees_north',
    coord_system=cube_cs
)

dem_cube = iris.cube.Cube(
    dem_data,
    var_name=dem_name,
    units=dem_units,
    dim_coords_and_dims=((clat, 0), (clon, 1))
)

print(dem_cube)


GMTED2010_mn30 / (m)                (latitude: 20880; longitude: 43200)
     Dimension coordinates:
          latitude                           x                 -
          longitude                          -                 x
  • Show a map of the topographic data (Europe)

In [11]:
lon_extent = (-12, 30)
lat_extent = (35, 65)

lat_subset = iris.Constraint(
    latitude=lambda cell: lat_extent[0] <= cell <= lat_extent[1]
)
lon_subset = iris.Constraint(
    longitude=lambda cell: lon_extent[0] <= cell <= lon_extent[1]
)

dem_subset = dem_cube.extract(lat_subset & lon_subset)

plt.figure(figsize=(10, 6))
ax = plt.axes(projection=ccrs.PlateCarree())
topo_map = ax.imshow(dem_subset.data,
                     origin='upper',
                     extent=lon_extent + lat_extent,
                     transform=ccrs.PlateCarree(),
                     interpolation='nearest',
                     cmap=plt.cm.terrain,
                     vmin=-1000, vmax=4500)
ax.coastlines(resolution='50m')
plt.colorbar(topo_map,
             ax=ax, orientation='horizontal',
             pad=0.05, shrink=0.65,
             label='m')
plt.title(dem_cube.name())
plt.show()


Set the GEOS-Chem grid

The pygchem.grid module can be used to get one of the standard grids used in GEOS-Chem simulations.


In [12]:
import pygchem.grid as grid

Grid setup is available for the following grids:


In [13]:
grid.get_supported_models()


Out[13]:
('GEOSFP_REDUCED',
 'MERRA_NATIVE',
 'GEOS5_REDUCED',
 'GEOSFP_47L',
 'GEOSFP_NATIVE',
 'GENERIC',
 'GEOS5_NATIVE',
 'GEOS_STRAT_46L',
 'GEOS4_30L',
 'GEOS',
 'GEOS_STRAT',
 'MERRA_47L',
 'GEOSFP',
 'GEOS5',
 'GEOS4',
 'GEOS3',
 'GEOS2',
 'GEOS1',
 'GEOS57_REDUCED',
 'GEOS5_47L',
 'GEOS57',
 'GEOS2_70L',
 'FVDAS',
 'MERRA',
 'GEOS57_47L',
 'GEOS3_30L',
 'GEOS3_REDUCED',
 'GEOS4_REDUCED',
 'MERRA_REDUCED',
 'GEOS57_NATIVE')
  • Set the grid name and resolution

In [14]:
gcgrid_model = 'GEOSFP'
gcgrid_resolution = 5, 4    # horizontal grid resolution (lon, lat) in decimal degrees

gcgrid_name = "{model}_{rlat}x{rlon}".format(
    model=gcgrid_model,
    rlat=gcgrid_resolution[1],
    rlon=gcgrid_resolution[0]
)

gcgrid_name


Out[14]:
'GEOSFP_4x5'
  • Create a CTMGrid object from the grid name and resolution given here above

In [15]:
gcgrid = grid.grid_from_model(gcgrid_model,
                              resolution=gcgrid_resolution)
  • Create Iris longitude and latitude coordinates (DimCoord objects) from the grid defined here above. The pygchem.tools.irisutils module provides handful routines for creating or handling such Iris objects.

In [16]:
import pygchem.tools.irisutil as irisutil

gclon, gclat = irisutil.coord_from_grid(
    gcgrid, coord=('longitude', 'latitude')
)
  • Assign the same coordinate system than the raster topographic data to the GEOS-Chem grid (needed for regridding). As no coordinate conversion is performed, any difference between the two coordinate systems is simply ignored!

In [17]:
gclon.coord_system = cube_cs
gclat.coord_system = cube_cs
  • Finally, create an Iris cube for the defined grid

In [18]:
gc_cube = iris.cube.Cube(
    np.empty(gclat.shape + gclon.shape),
    var_name=gcgrid_name,
    units=dem_units,
    dim_coords_and_dims=((gclat, 0), (gclon, 1))
)

print(gc_cube)


GEOSFP_4x5 / (m)                    (latitude: 46; longitude: 72)
     Dimension coordinates:
          latitude                           x              -
          longitude                          -              x

Regrid the topographic data onto the GEOS-Chem grid

See the Iris user guide about cube regridding and interpolation for more info. Here the area weighted conservative scheme is used to properly resample the high-resolution elevation data down to the low-resolution GEOS-Chem grid.

NOTE: the execution of the cell below may take a while, depending on the size of the elevation data (it may take > 1 hour for the elevation data used here!). This regridding implementation is not optimized for processing huge sized arrays!


In [19]:
regrid_scheme = iris.analysis.AreaWeighted(mdtol=0.5)
dem_regrid_cube = dem_cube.regrid(gc_cube, regrid_scheme)
  • Rename the cube and add some CF-compliant metadata

In [20]:
dem_regrid_cube.var_name = "TOPO_{}".format(gc_cube.name())
dem_regrid_cube.standard_name = "height"
dem_regrid_cube.attributes['topo_data_src'] = dem_cube.name()
dem_regrid_cube.attributes['regrid_scheme'] = "area-weighted mean"
dem_regrid_cube.add_cell_method(
    iris.coords.CellMethod("mean")
)

print(dem_regrid_cube)


height / (m)                        (latitude: 46; longitude: 72)
     Dimension coordinates:
          latitude                           x              -
          longitude                          -              x
     Attributes:
          regrid_scheme: area-weighted mean
          topo_data_src: GMTED2010_mn30
     Cell methods:
          mean: 

Save and plot the regridded topography

  • Save the regridded topography as a CF-NetCDF dataset

In [21]:
iris.save(
    dem_regrid_cube,
    '/home/bovy/Grids/topo_{gc_name}.nc'.format(gc_name=gc_cube.name())
)
  • Map of the regridded global topography

In [22]:
plt.figure(figsize=(9, 5))
ax = plt.axes(projection=ccrs.PlateCarree())
iplt.pcolormesh(dem_regrid_cube,
                cmap=plt.cm.terrain,
                vmin=-1000, vmax=4500)
ax.figure.set_facecolor('white')
ax.coastlines()
ax.set_global()
plt.colorbar(ax=ax, orientation='horizontal',
             pad=0.05, shrink=0.7,
             label='m')
plt.title(dem_regrid_cube.var_name)
plt.show()



In [ ]: