Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pycarto++ #119

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
1f3b37f
cartopy replaces basemap
bblay Sep 4, 2012
c96a699
safe image changes
bblay Sep 26, 2012
3480056
cautious changes
bblay Sep 26, 2012
8b21acb
Fixes to use renamed as_cartopy_crs/as_cartopy_projection methods.
esc24 Sep 27, 2012
b6f980e
Visual test updates following migration to cartopy. Note the longitud…
esc24 Sep 27, 2012
a6e3af2
Fixed extent calculation. General improvements.
pelson Sep 27, 2012
2c66d0f
2 more tests passing.
pelson Sep 27, 2012
15d3bd4
Merge pull request #2 from pelson/mapping_fix
esc24 Sep 27, 2012
558d0b4
Fixed extent issue with infinite values.
pelson Sep 27, 2012
10c84de
Initial fixes to extests. Issues around bounds and polygons remain.
esc24 Sep 27, 2012
0bb2366
Visual test updates following shift and margin fixes.
esc24 Sep 27, 2012
cf69468
More minor fixes to mapping and cartography.
esc24 Sep 27, 2012
2789ca0
Fixed further extent calculation issues (by removing the map_setup an…
pelson Sep 27, 2012
7bdea1e
Merge branch 'mapping_fix' of https://github.com/pelson/iris into pel…
esc24 Sep 27, 2012
37a0224
Scatter plot is OK
rhattersley Sep 28, 2012
bcb51b0
Update passing tests.
rhattersley Sep 28, 2012
23a9e28
Remove duplicate method.
rhattersley Sep 28, 2012
16f67e9
Revert scatter workaround - fixed in cartopy PR 58.
rhattersley Sep 28, 2012
304f660
Accept coloured outlines - it's MPL's problem.
rhattersley Sep 28, 2012
7ef4f36
Accept "baggy" limits - it's what MPL does for scatter.
rhattersley Sep 28, 2012
63f9d04
Test xy_range with global and local GeogCS
rhattersley Sep 28, 2012
6bbecb9
Revert unnecessary change.
rhattersley Sep 28, 2012
8b2fc53
circular grib lons
bblay Sep 28, 2012
7abf2b9
Simplify rotated pole example and accept TEC plot
rhattersley Sep 28, 2012
e221228
review actions and minor tidy
bblay Oct 1, 2012
2679f3b
Merge pull request #2 from bblay/gribcirc2
rhattersley Oct 1, 2012
19f05c0
Accept new COP/ensemble maps
rhattersley Oct 1, 2012
4160064
Update to work with the Cartopy wrap fix
rhattersley Oct 2, 2012
7434e1a
More tweakage
rhattersley Oct 3, 2012
3bec31c
Remove obsolete test
rhattersley Oct 3, 2012
c1e8c50
Tidy
rhattersley Oct 3, 2012
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
Release 1.0
===========

Incompatible changes
--------------------
* With the change to cartopy, the function iris.plot.gcm() is obsolete
and has been removed.
* Three functions have been removed from iris.analysis.cartography:
lat_lon_range(), get_lat_lon_grids(), and
get_lat_lon_contiguous_grids(). They have been replaced with
generalised versions: xy_range(), get_xy_grids(),
and get_xy_contiguous_bounded_grids().


Release 0.9 (14 Aug, 2012)
==========================

Expand Down
6 changes: 2 additions & 4 deletions docs/iris/example_code/graphics/COP_maps.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,17 +86,15 @@ def main():
plt.subplot(121)
plt.title('HadGEM2 E1 Scenario', fontsize=10)
iplt.contourf(delta_e1, levels, colors=colors, linewidth=0, extend='both')
current_map = iplt.gcm()
current_map.drawcoastlines()
plt.gca().coastlines()
# get the current axes' subplot for use later on
plt1_ax = plt.gca()

# Add the second subplot showing the A1B scenario
plt.subplot(122)
plt.title('HadGEM2 A1B-Image Scenario', fontsize=10)
contour_result = iplt.contourf(delta_a1b, levels, colors=colors, linewidth=0, extend='both')
current_map = iplt.gcm()
current_map.drawcoastlines()
plt.gca().coastlines()
# get the current axes' subplot for use later on
plt2_ax = plt.gca()

Expand Down
4 changes: 2 additions & 2 deletions docs/iris/example_code/graphics/TEC.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ def main():
plt.title('Total Electron Content')
plt.xlabel('longitude / degrees')
plt.ylabel('latitude / degrees')
iplt.gcm().bluemarble(zorder=-1)
iplt.gcm().drawcoastlines()
plt.gca().bluemarble()
plt.gca().coastlines()
plt.show()


Expand Down
12 changes: 4 additions & 8 deletions docs/iris/example_code/graphics/custom_file_loading.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,15 +215,11 @@ def main():
# Callback shown as None to illustrate where a cube-level callback function would be used if required
cube = iris.load_strict(fname, boundary_volc_ash_constraint, callback=None)

map = iplt.map_setup(lon_range=[-70, 20], lat_range=[20, 75], resolution='i')

map.drawcoastlines()

iplt.contourf(cube,
levels=(0.0002, 0.002, 0.004, 1),
iplt.map_setup(xlim=(-70, 20), ylim=(20, 75))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the reason I want to get rid of map_setup. As a function it is completely redundant other than for setting up a map given a cube (3 cases: cube projection any limits; cube limits any projection; cube limits and cube projection;), which I feel could be done better if we had an interface such as:

projection, extent = iplt.crs_and_extent(cube)
ax = plt.axes(projection=projection)
ax.set_extent(extent, projection)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, with the change to cartopy map_setup is now an unwanted "appendix". I'd rather remove it in a separate PR though.

plt.gca().coastlines()
iplt.contourf(cube, levels=(0.0002, 0.002, 0.004, 1),
colors=('#80ffff', '#939598', '#e00404'),
extend='max'
)
extend='max')

time = cube.coord('time')
time_date = time.units.num2date(time.points[0]).strftime(UTC_format)
Expand Down
2 changes: 1 addition & 1 deletion docs/iris/example_code/graphics/global_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def main():
temperature = iris.load_strict(fname)

qplt.contourf(temperature, 15)
iplt.gcm().drawcoastlines()
plt.gca().coastlines()
plt.show()


Expand Down
3 changes: 1 addition & 2 deletions docs/iris/example_code/graphics/lagged_ensemble.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ def main():
cf = iplt.contourf(cube, contour_levels)

# add coastlines
m = iplt.gcm()
m.drawcoastlines()
plt.gca().coastlines()

# make an axes to put the shared colorbar in
colorbar_axes = plt.gcf().add_axes([0.35, 0.1, 0.3, 0.05])
Expand Down
20 changes: 5 additions & 15 deletions docs/iris/example_code/graphics/rotated_pole_mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,36 +23,27 @@ def main():
fname = iris.sample_data_path('rotated_pole.nc')
temperature = iris.load_strict(fname)

# Calculate the lat lon range and buffer it by 10 degrees
lat_range, lon_range = iris.analysis.cartography.lat_lon_range(temperature)
lat_range = lat_range[0] - 10, lat_range[1] + 10
lon_range = lon_range[0] - 10, lon_range[1] + 10


# Plot #1: Point plot showing data values & a colorbar
plt.figure()
iplt.map_setup(temperature, lat_range=lat_range, lon_range=lon_range)
points = qplt.points(temperature, c=temperature.data)
cb = plt.colorbar(points, orientation='horizontal')
cb.set_label(temperature.units)
iplt.gcm().drawcoastlines()
plt.gca().coastlines()
plt.show()


# Plot #2: Contourf of the point based data
plt.figure()
iplt.map_setup(temperature, lat_range=lat_range, lon_range=lon_range)
qplt.contourf(temperature, 15)
iplt.gcm().drawcoastlines()
plt.gca().coastlines()
plt.show()


# Plot #3: Contourf overlayed by coloured point data
plt.figure()
iplt.map_setup(temperature, lat_range=lat_range, lon_range=lon_range)
qplt.contourf(temperature)
iplt.points(temperature, c=temperature.data)
iplt.gcm().drawcoastlines()
plt.gca().coastlines()
plt.show()


Expand All @@ -64,10 +55,9 @@ def main():

# Plot #4: Block plot
plt.figure()
iplt.map_setup(temperature, lat_range=lat_range, lon_range=lon_range)
iplt.pcolormesh(temperature)
iplt.gcm().bluemarble()
iplt.gcm().drawcoastlines()
plt.gca().bluemarble()
plt.gca().coastlines()
plt.show()


Expand Down
1 change: 0 additions & 1 deletion docs/iris/src/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,6 @@
'numpy': ('http://docs.scipy.org/doc/numpy/', None),
'scipy': ('http://docs.scipy.org/doc/scipy/reference/', None),
'matplotlib': ('http://matplotlib.sourceforge.net/', None),
'basemap': ('http://matplotlib.github.com/basemap/', None),
}


Expand Down
12 changes: 6 additions & 6 deletions docs/iris/src/userguide/plotting_a_cube.rst
Original file line number Diff line number Diff line change
Expand Up @@ -207,15 +207,15 @@ Plotting 2-dimensional cubes

Creating maps
-------------
Whenever a 2D plot is created and the x and y coordinates are longitude and latitude a
:class:`mpl_toolkits.basemap.Basemap` instance is created which can be accessed with the :func:`iris.plot.gcm` function.
Whenever a 2D plot is created using an :class:`iris.coord_systems.CoordSystem` a
cartopy :class:`~cartopy.mpl_integration.GenericProjectionAxes` instance is created
which can be accessed with the :func:`matplotlib.pyplot.gca` function.

Given the current map, you can draw meridians, parallels and coastlines amongst other things.
Given the current map, you can draw gridlines and coastlines amongst other things.

.. seealso::
:meth:`Basemap.drawmeridians() <mpl_toolkits.basemap.Basemap.drawmeridians>`,
:meth:`Basemap.drawparallels() <mpl_toolkits.basemap.Basemap.drawparallels>` and
:meth:`Basemap.drawcoastlines() <mpl_toolkits.basemap.Basemap.drawcoastlines>`.
:meth:`cartopy's gridlines() <cartopy.mpl_integration.GenericProjectionAxes.gridlines>`,
:meth:`cartopy's coastlines() <cartopy.mpl_integration.GenericProjectionAxes.coastlines>`.


Cube contour
Expand Down
7 changes: 2 additions & 5 deletions docs/iris/src/userguide/plotting_examples/cube_blockplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@
# Draw the contour with 25 levels
qplt.pcolormesh(temperature_cube)

# Get the map created by pcolormesh
current_map = iplt.gcm()

# Add coastlines to the map
current_map.drawcoastlines()
# Add coastlines to the map created by pcolormesh
plt.gca().coastlines()

plt.show()
7 changes: 2 additions & 5 deletions docs/iris/src/userguide/plotting_examples/cube_contour.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,8 @@
# Add a contour, and put the result in a variable called contour.
contour = qplt.contour(temperature_cube)

# Get the map created by contourf
current_map = iplt.gcm()

# Add coastlines to the map
current_map.drawcoastlines()
# Add coastlines to the map created by contour
plt.gca().coastlines()

# Add contour labels based on the contour we have just created
plt.clabel(contour)
Expand Down
7 changes: 2 additions & 5 deletions docs/iris/src/userguide/plotting_examples/cube_contourf.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@
# Draw the contour with 25 levels
qplt.contourf(temperature_cube, 25)

# Get the map created by contourf
current_map = iplt.gcm()

# Add coastlines to the map
current_map.drawcoastlines()
# Add coastlines to the map created by contourf
plt.gca().coastlines()

plt.show()
118 changes: 63 additions & 55 deletions lib/iris/analysis/cartography.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@
import math
import warnings

from mpl_toolkits.basemap import pyproj
import pyproj
import numpy
import cartopy.crs

import iris.analysis
import iris.coords
Expand Down Expand Up @@ -95,92 +96,106 @@ def _get_lat_lon_coords(cube):
lat_coords = filter(lambda coord: "latitude" in coord.name(), cube.coords())
lon_coords = filter(lambda coord: "longitude" in coord.name(), cube.coords())
if len(lat_coords) > 1 or len(lon_coords) > 1:
raise ValueError("Calling lat_lon_range() with multiple lat or lon coords is currently disallowed")
raise ValueError("Calling _get_lat_lon_coords() with multiple lat or lon coords is currently disallowed")
lat_coord = lat_coords[0]
lon_coord = lon_coords[0]
return (lat_coord, lon_coord)


def lat_lon_range(cube, mode=None):
def xy_range(cube, mode=None, projection=None):
"""
Return the lat & lon range of this Cube.
Return the x & y range of this Cube.

If the coordinate has both points & bounds, the mode keyword can be set to determine which should be
used in the min/max calculation. (Must be one of iris.coords.POINT_MODE or iris.coords.BOUND_MODE)
Args:

* cube - The cube for which to calculate xy extents.

Kwargs:

* mode - If the coordinate has bounds, use the mode keyword to specify the
min/max calculation (iris.coords.POINT_MODE or iris.coords.BOUND_MODE).

* projection - Calculate the xy range in an alternative projection.

"""
# Helpful error if we have an inappropriate CoordSystem
cs = cube.coord_system("CoordSystem")
if cs is not None and not isinstance(cs, (iris.coord_systems.GeogCS, iris.coord_systems.RotatedGeogCS)):
raise ValueError("Latlon coords cannot be found with {0}.".format(type(cs)))

# get the lat and lon coords (might have "grid_" at the start of the name, if rotated).
lat_coord, lon_coord = _get_lat_lon_coords(cube)
x_coord, y_coord = cube.coord(axis="X"), cube.coord(axis="Y")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yet more use of axis... (I think I wrote this bit in the first place). Getting the x and y coordinates is by far the biggest use case for "axis" usage. Is it worth looking into providing a more elegant solution?

cs = cube.coord_system('CoordSystem')

if lon_coord.has_bounds() != lat_coord.has_bounds():
raise ValueError('Cannot get the range of the latitude and longitude coordinates if they do '
if x_coord.has_bounds() != x_coord.has_bounds():
raise ValueError('Cannot get the range of the x and y coordinates if they do '
'not have the same presence of bounds.')

if lon_coord.has_bounds():
if x_coord.has_bounds():
if mode not in [iris.coords.POINT_MODE, iris.coords.BOUND_MODE]:
raise ValueError('When the coordinate has bounds, please specify "mode".')
_mode = mode
else:
_mode = iris.coords.POINT_MODE

# Get the x and y grids
if isinstance(cs, iris.coord_systems.RotatedGeogCS):
if _mode == iris.coords.POINT_MODE:
lats, lons = get_lat_lon_grids(cube)
x, y = get_xy_grids(cube)
else:
lats, lons = get_lat_lon_contiguous_bounded_grids(cube)
x, y = get_xy_contiguous_bounded_grids(cube)
else:
if _mode == iris.coords.POINT_MODE:
lons = lon_coord.points
lats = lat_coord.points
x = x_coord.points
y = y_coord.points
else:
lons = lon_coord.bounds
lats = lat_coord.bounds
x = x_coord.bounds
y = y_coord.bounds

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This api change should be documented as a non-compatible change.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Similarly for lat_lon_range() and get_lat_lon_contiguous_bounded_grids(). Will need to revisit if we move the "xy" equivalents to the plot module.

if projection:
# source projection
source_cs = cube.coord_system("CoordSystem")
if source_cs is not None:
source_proj = source_cs.as_cartopy_projection()
else:
#source_proj = cartopy.crs.PlateCarree()
raise Exception('Unknown source coordinate system')

if source_proj != projection:
# TODO: Ensure there is a test for this
x, y = projection.transform_points(x=x, y=y, src_crs=source_proj)

if getattr(lon_coord, 'circular', False):
lon_range = (numpy.min(lons), numpy.min(lons) + lon_coord.units.modulus)
# Get the x and y range
if getattr(x_coord, 'circular', False):
x_range = (numpy.min(x), numpy.min(x) + x_coord.units.modulus)
else:
lon_range = (numpy.min(lons), numpy.max(lons))
return ( (numpy.min(lats), numpy.max(lats)), lon_range )
x_range = (numpy.min(x), numpy.max(x))

y_range = (numpy.min(y), numpy.max(y))

return (x_range, y_range)

def get_lat_lon_grids(cube):

def get_xy_grids(cube):
"""
Return 2d lat and lon points in the requested coordinate system.
Return 2d x and y points in the native coordinate system.
::

lats, lons = get_lat_lon_grids(cube)
x, y = get_xy_grids(cube)

"""
# get the lat and lon coords (might have "grid_" at the start of the name, if rotated).
lat_coord, lon_coord = _get_lat_lon_coords(cube)
x_coord, y_coord = cube.coord(axis="X"), cube.coord(axis="Y")
cs = cube.coord_system('CoordSystem')

if lon_coord.units != 'degrees':
lon_coord = lon_coord.unit_converted('degrees')
if lat_coord.units != 'degrees':
lat_coord = lat_coord.unit_converted('degrees')

lons = lon_coord.points
lats = lat_coord.points
x = x_coord.points
y = y_coord.points

# Convert to 2 x 2d grid of data
lons, lats = numpy.meshgrid(lons, lats)

# if the pole was rotated, then un-rotate it
if isinstance(cs, iris.coord_systems.RotatedGeogCS):
lons, lats = unrotate_pole(lons, lats, cs.grid_north_pole_longitude, cs.grid_north_pole_latitude)

return (lats, lons)
x, y = numpy.meshgrid(x, y)

return (x, y)

def get_lat_lon_contiguous_bounded_grids(cube):

def get_xy_contiguous_bounded_grids(cube):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should look at how many places use these functions in "analysis/cartography" - they aren't doing any analysis/cartography, more like utility functions for working with a cube. If it is the case that they are limited to plotting, then I think we should look at moving them there and making them private (or inline).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So... get_xy_grids() and get_xy_contiguous_bounded_grids() are only used (in Iris) by:

  • iris.analysis.cartography.xy_range()
  • iris.plot._map_common()

And xy_range() is only used in iris.plot.map_setup()... which we'd like to replace with iris.plot.crs_and_extent() or similar.

So right now these are just mapping support functions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forked issue as #129.

"""
Return 2d lat and lon bounds.

Expand All @@ -190,21 +205,14 @@ def get_lat_lon_contiguous_bounded_grids(cube):
lats, lons = cs.get_lat_lon_bounded_grids()

"""
# get the lat and lon coords (might have "grid_" at the start of the name, if rotated).
lat_coord, lon_coord = _get_lat_lon_coords(cube)
x_coord, y_coord = cube.coord(axis="X"), cube.coord(axis="Y")
cs = cube.coord_system('CoordSystem')

if lon_coord.units != 'degrees':
lon_coord = lon_coord.unit_converted('degrees')
if lat_coord.units != 'degrees':
lat_coord = lat_coord.unit_converted('degrees')

lons = lon_coord.contiguous_bounds()
lats = lat_coord.contiguous_bounds()
lons, lats = numpy.meshgrid(lons, lats)
if isinstance(cs, iris.coord_systems.RotatedGeogCS):
lons, lats = iris.analysis.cartography.unrotate_pole(lons, lats, cs.grid_north_pole_longitude, cs.grid_north_pole_latitude)
return (lats, lons)
x = x_coord.contiguous_bounds()
y = y_coord.contiguous_bounds()
x, y = numpy.meshgrid(x, y)

return (x, y)


def _quadrant_area(radian_colat_bounds, radian_lon_bounds, radius_of_earth):
Expand Down
Loading