Cartopy and the Shuttle Radar Topography Map data

Cartopy and the Shuttle Radar Topography Map data

The SRTM project provides a global land elevation model at a resolution of (roughly) 30m. That’s a useful thing to be able to access - either as raw data or simply for plotting into maps. Here are some examples of methods to access, process and display this data using the cartopy interfaces.

For more information on the project: http://www2.jpl.nasa.gov/srtm/

%pylab inline

from osgeo import gdal
from osgeo import gdal_array

import cartopy.crs as ccrs
from cartopy.io import srtm
import matplotlib.pyplot as plt

from cartopy.io import PostprocessedRasterSource, RasterSourceContainer, LocatedImage
from cartopy.io.srtm import SRTM3Source

import cartopy.feature as cfeature
from cloudstor import cloudstor
teaching_data = cloudstor(url="L93TxcmtLQzcfbk", password='')
teaching_data.download_file_if_distinct("color_etopo1_ice_low.tif", "Resources/color_etopo1_ice_low.tif")
from http.cookiejar import CookieJar
# from urllib import urlencode
import urllib.request


# The user credentials that will be used to authenticate access to the data

username = "lmoresi"
password = "hpX-AnW-xrT-5D6"


# The url of the file we wish to retrieve

url = "http://e4ftl01.cr.usgs.gov/MOLA/MYD17A3H.006/2009.01.01/MYD17A3H.A2009001.h12v05.006.2015198130546.hdf.xml"


# Create a password manager to deal with the 401 reponse that is returned from
# Earthdata Login

password_manager = urllib.request.HTTPPasswordMgrWithDefaultRealm()
password_manager.add_password(None, "https://urs.earthdata.nasa.gov", username, password)


# Create a cookie jar for storing cookies. This is used to store and return
# the session cookie given to use by the data server (otherwise it will just
# keep sending us back to Earthdata Login to authenticate).  Ideally, we
# should use a file based cookie jar to preserve cookies between runs. This
# will make it much more efficient.

cookie_jar = CookieJar()


# Install all the handlers.

opener = urllib.request.build_opener(
    urllib.request.HTTPBasicAuthHandler(password_manager),
    #urllib2.HTTPHandler(debuglevel=1),    # Uncomment these two lines to see
    #urllib2.HTTPSHandler(debuglevel=1),   # details of the requests/responses
    urllib.request.HTTPCookieProcessor(cookie_jar))
urllib.request.install_opener(opener)
# Region of interest

map_extent = [ -120, -117, 33, 36]

lon0 = map_extent[0]
lat0 = map_extent[2]

# High res coastline

coastline = cfeature.NaturalEarthFeature('physical', 'coastline', '10m',
                           edgecolor=(1.0,0.8,0.0),
                           facecolor="none")

ocean = cfeature.NaturalEarthFeature('physical', 'ocean', '50m',
                           edgecolor="green",
                           facecolor="blue")
# SRTM - how to grab the data and plot it as an image (in the usual way)

# There may be some "download" warnings but this is simply a result of the way that the SRTM 
# module caches data the first time that it needs a tile. Replotting will be a lot quicker.
 
elev, crs, extent = SRTM3Source().combined(lon0, lat0, 3, 3)

# Take out low points 
#elev = np.ma.masked_less_equal(elev, -100, copy=False)

print (elev.min(), elev.max(), elev.mean())
plt.figure(figsize=(15, 10))
ax = plt.subplot(111, projection=ccrs.PlateCarree())
ax.add_feature(coastline, edgecolor="black", linewidth=1, zorder=3)

plt.imshow(elev, extent=extent, transform=crs,
           cmap='terrain', origin='lower', vmin=-400., vmax=2000.)

cb = plt.colorbar(orientation='vertical')
cb.set_label('Altitude')
plt.title("SRTM Map")
gl = ax.gridlines(draw_labels=True)
gl.xlabels_top = False
gl.ylabels_left = False

Patching data

# Patching holes in the data by a smoothing / interpolation routine from gdal

elev, crs, extent = SRTM3Source().combined(lon0, lat0, 3, 3)

old_elev = elev.copy()

src_ds = gdal_array.OpenArray(elev)

srcband = src_ds.GetRasterBand(1)
dstband = srcband
maskband = srcband 

smoothing_iterations = 0   # iterations applied after patching
options = []
max_distance = 15           # distance in pixels used to find data to patch
result = gdal.FillNodata(dstband, maskband,
                         max_distance, smoothing_iterations, options,
                         callback=None)

elev2 = dstband.ReadAsArray()

# It's probably worth looking at these figures to see what is going on 

print ("Number of points previously out of range ", np.count_nonzero( old_elev < -12000))
print ("Number of points currently out of range ", np.count_nonzero( elev < -12000))
print ("Number of points changed: ", np.count_nonzero(elev-old_elev))
print ("Range of changed points: ",  elev[elev != old_elev].min(), " ", elev[elev != old_elev].max())
# And plotting

plt.figure(figsize=(10, 10))
ax = plt.subplot(111, projection=ccrs.PlateCarree())

plt.imshow(elev-elev2, extent=extent, transform=crs, cmap='terrain', origin='lower')
ax.add_feature(coastline, edgecolor="black", linewidth=1, zorder=3)

cb = plt.colorbar(orientation='vertical')
cb.set_label('Altitude')
plt.title("SRTM Map")
gl = ax.gridlines(draw_labels=True,)
gl.xlabels_top = False
gl.ylabels_left = False
plt.figure(figsize=(15, 10))
ax = plt.subplot(111, projection=ccrs.PlateCarree())

plt.imshow(elev, extent=extent, transform=crs,
           cmap='terrain', origin='lower', vmin=-200.0, vmax=3000)

ax.add_feature(coastline, edgecolor="black", linewidth=1, zorder=3)

cb = plt.colorbar(orientation='vertical')
cb.set_label('Altitude')
plt.title("SRTM Map")
gl = ax.gridlines(draw_labels=True)
gl.xlabels_top = False
gl.ylabels_left = False
# Here is a completely different way to do this !! 

# Rather than downloading data, we can make use of on-demand downloading
# through interfaces provided by cartopy. This might save us managing a
# huge amount of data but the manipulations we used routinely above now
# have to be handled by helper functions handed into the cartopy classes.


# The "container" is like a placeholder for what will need to be called 
# when the plotting routines need to grab data

srtm = RasterSourceContainer(SRTM3Source())

plt.figure(figsize=(15, 10))
ax = plt.subplot(111, projection=ccrs.PlateCarree())
ax.set_extent(map_extent)

data_norm = matplotlib.colors.Normalize(vmin=-300, vmax=2000)
ax.add_raster(srtm, cmap='terrain', norm=data_norm)
ax.add_feature(coastline)
# Shuttle radar shaded relief map 

from cartopy.io import srtm as csrtm

globaletopo       = gdal.Open("Resources/color_etopo1_ice_low.tif")
globaletopo_img   = globaletopo.ReadAsArray().transpose(1,2,0)

def shade(located_elevations):
    """
    Given an array of elevations in a LocatedImage, fill any holes in
    the data and add a relief (shadows) to give a realistic 3d appearance.

    """

    new_img = csrtm.add_shading(located_elevations.image, azimuth=135, altitude=33)
    return LocatedImage(new_img, located_elevations.extent)


# Define a raster source which uses the SRTM3 data and applies the
# fill_and_shade function when the data is retrieved.

shaded_srtm = PostprocessedRasterSource(SRTM3Source(), shade)

fig = plt.figure(figsize=(10, 10))
ax  = plt.subplot(111, projection=ccrs.PlateCarree())
ax.set_extent(map_extent)

# Add the shaded SRTM source to our map with a grayscale colormap.

ax.add_raster(shaded_srtm, cmap='Greys', zorder=1)

plt.imshow(globaletopo_img, zorder=2, transform=ccrs.PlateCarree(), 
            extent = [-180.0, 180.0, -90.0, 90.0], interpolation='bicubic', alpha=0.333)

ax.add_feature(coastline, edgecolor="black", linewidth=1, zorder=3)
ax.add_feature(ocean,  zorder=4, alpha = 0.5)

# This data is high resolution, so pick a small area which has some
# interesting orography.

plt.title("SRTM Shaded Relief Map")

gl = ax.gridlines(draw_labels=True)
gl.xlabels_top = False
gl.ylabels_left = False

fig.savefig("LA_Region_ShadedRelief", dpi=600)
# Shuttle radar shaded relief map 

from cartopy.io import srtm as csrtm

# globaletopo       = gdal.Open("../../Data/Resources/color_etopo1_ice_low.tif")
# globaletopo_img   = globaletopo.ReadAsArray().transpose(1,2,0)

map_extent = [ -118.5, -117.6, 33.5, 34.5]


def shade(located_elevations):
    """
    Given an array of elevations in a LocatedImage, fill any holes in
    the data and add a relief (shadows) to give a realistic 3d appearance.

    """
    new_img = csrtm.add_shading(located_elevations.image, azimuth=135, altitude=33)
    return LocatedImage(new_img, located_elevations.extent)


# Define a raster source which uses the SRTM3 data and applies the
# fill_and_shade function when the data is retrieved.

shaded_srtm = PostprocessedRasterSource(SRTM3Source(), shade)

fig = plt.figure(figsize=(10, 10))
ax  = plt.subplot(111, projection=ccrs.PlateCarree())
ax.set_extent(map_extent)

# Add the shaded SRTM source to our map with a grayscale colormap.

ax.add_raster(shaded_srtm, cmap='Greys', zorder=1)

plt.imshow(globaletopo_img, zorder=2, transform=ccrs.PlateCarree(), 
           extent = [-180.0, 180.0, -90.0, 90.0], interpolation='bicubic', alpha=0.333)

ax.add_feature(coastline, edgecolor="black", linewidth=1, zorder=3)
ax.add_feature(ocean,  zorder=4, alpha = 0.25)


# This data is high resolution, so pick a small area which has some
# interesting orography.


plt.title("SRTM Shaded Relief Map")

gl = ax.gridlines(draw_labels=True)
gl.xlabels_top = False
gl.ylabels_left = False

fig.savefig("LA_Basin_ShadedRelief", dpi=600)