WMS (Web Map Service) is an OGC (Open Geospatial Consortium) standard for requesting geo-located map images over HTTP. The OGC provide a detailed overview of the WMS standard.
A WMS request to a WMS server contains layer (a map layer containing a given geographic phenomenon) and area of interest information. The information in the request defines the response from the WMS server to which the request was made. The response is composed of geo-located map images that can be displayed by the application making the request.
The map images in a WMS response are raster images, typically provided in PNG
or JPEG
format.
An important part of OGC standards services is the get capabilities
request. This returns an XML document that specifies the capabilities (metadata) about the service. Information included in the get capabilities response includes:
This notebook demonstrates using Cartopy to:
Let's demonstrate making an WMS request with Cartopy and plotting the map image contained in the response. Let's use data from the CONUS GOES IR Satellite (see http://www.goes.noaa.gov/sat-explanation.html for more detail) exposed as a WMS service.
To make a WMS request in Cartopy, as with every WMS request, we provide a URL to a WMS server. This server provides a web map service, which may provide one or more layers of geographic phenomena. To complete the request we need to also provide the name of the layer we're interested in. To find the names of the available layers we need to refer to the get capabilities response from the WMS server.
We can use the Python library OWSLib
to retrieve this information from the get capabilities response from the WMS server:
from owslib.wms import WebMapService
url = 'http://mesonet.agron.iastate.edu/cgi-bin/wms/goes/conus_ir.cgi?'
wms = WebMapService(url)
layers = list(wms.contents)
print 'Request type: {}'.format(wms.identification.type)
print 'Request title: {}'.format(wms.identification.title)
print 'Available layers: {}'.format(layers)
Request type: OGC:WMS Request title: IEM GOES IR WMS Service Available layers: ['ir_4km_900913', 'goes_conus_ir', 'conus_ir_4km_900913', 'conus_ir_4km']
Let's take a more in-depth look at a layer of interest:
layer = layers[1]
print 'Layer title: {}'.format(wms[layer].title)
print 'Bounding box: {}'.format(wms[layer].boundingBoxWGS84)
print 'Projection: {}'.format(wms[layer].crsOptions)
Layer title: IEM GOES IR WMS Service Bounding box: (-126.0, 24.0, -66.0, 50.0) Projection: ['EPSG:4326']
Making a WMS request is completely handled by Cartopy behind the scenes. The above examples are just to demonstrate finding layer names and other useful information contained in a WMS response.
With this information from the get capabilities to hand we can construct a WMS request. Let's import some more useful libraries and define a convenience function that returns a common set of axes onto which to plot our WMS layer and other data.
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
us_extent = [-126.0, -66.0, 24.0, 50.0]
def set_up_axes(proj, coast_colour='y', extent=us_extent):
fig = plt.figure(figsize=(14, 9))
ax = plt.axes(projection=proj)
ax.coastlines(color=coast_colour)
ax.set_extent(extent, crs=ccrs.PlateCarree())
return ax
Let's set up some axes and use Cartopy to plot the image response from a WMS request to the axes.
proj = ccrs.PlateCarree()
ax = set_up_axes(proj)
ax.add_wms(url, layer)
plt.show()
We can add value to our plot above by adding data to it. For example, we can add the location of major US cities to the plot and add air temperature data taken from an Iris cube.
Let's add the locations of three US cities to the plot, along with a text label of the city's name:
import matplotlib.patheffects as mpath
# Set up the axes and add a WMS layer, as above.
proj = ccrs.PlateCarree()
ax = set_up_axes(proj)
ax.add_wms(url, layer)
# Add the locations of our three cities.
cities = {'Washington DC': [38.928, -76.981],
'Austin': [30.308, -97.753],
'San Francisco': [37.758, -122.438],}
city_red = '#b80000'
for name, latlon in cities.iteritems():
plt.scatter(latlon[1], latlon[0], c=city_red, s=50, linewidths=0, marker='*')
plt.text(latlon[1]-1.0, latlon[0]+1.0, name, color='w', size=12,
path_effects=[mpath.withStroke(linewidth=2, foreground='k')])
plt.show()
The Iris sample data contains data on the predicted yearly average air temperature over the US according to the A1B scenario. Let's add the predicted air temperature for 2015 to the plot:
import iris
import iris.quickplot as qplt
year_2015 = iris.Constraint(time=lambda cell: cell.point.year == 2015)
with iris.FUTURE.context(cell_datetime_objects=True):
cube = iris.load_cube(iris.sample_data_path('A1B_north_america.nc'), year_2015)
For more information on constrained loading of Iris cubes, see http://scitools.org.uk/iris/docs/latest/userguide/loading_iris_cubes.html#constrained-loading.
# Set up the axes, add a WMS layer and city locations; all as above.
proj = ccrs.PlateCarree()
ax = set_up_axes(proj, coast_colour='k')
ax.add_wms(url, layer)
for name, latlon in cities.iteritems():
plt.scatter(latlon[1], latlon[0], c=city_red, s=50, linewidths=0, marker='*')
plt.text(latlon[1]-1.0, latlon[0]+1.0, name, color='w', size=12,
path_effects=[mpath.withStroke(linewidth=2, foreground='k')])
# Add the average air temperature data to the plot as a filled contour.
qplt.contourf(cube, alpha=0.25)
plt.show()
Iris can re-project WMS layers into a projection that is not the native projection of the WMS response images. To demonstrate this, let's take the example from above of the WMS image; the cities; and the Iris air temperature data, and re-project it.
We know from the get capabilities request for this layer that its native CRS is EPSG-4326 (WGS-84), or Plate Carrée. Let's use Cartopy to transform it into the Lambert Conformal Conic projection.
proj = ccrs.LambertConformal()
ax = set_up_axes(proj, coast_colour='k')
ax.add_wms(url, layer)
# Now that we've changed projection we need to specify the native projection
# of our city locations.
for name, latlon in cities.iteritems():
plt.scatter(latlon[1], latlon[0],
c=city_red, s=50, linewidths=0, marker='*',
transform=ccrs.PlateCarree())
plt.text(latlon[1]-1.0, latlon[0]+1.0, name, color='w', size=12,
path_effects=[mpath.withStroke(linewidth=2, foreground='k')],
transform=ccrs.PlateCarree())
qplt.contourf(cube, alpha=0.25)
plt.show()