diff --git a/docs/make_projection.py b/docs/make_projection.py index 4beb985fb..14be1ca88 100644 --- a/docs/make_projection.py +++ b/docs/make_projection.py @@ -35,7 +35,7 @@ def plate_carree_plot(): for i in range(0, nplots): central_longitude = 0 if i == 0 else 180 ax = fig.add_subplot( - nplots, 1, i+1, + nplots, 1, i + 1, projection=ccrs.PlateCarree(central_longitude=central_longitude)) ax.coastlines(resolution='110m') ax.gridlines() @@ -69,8 +69,8 @@ def utm_plot(): fig = plt.figure(figsize=(10, 3)) for i in range(0, nplots): - ax = fig.add_subplot(1, nplots, i+1, - projection=ccrs.UTM(zone=i+1, + ax = fig.add_subplot(1, nplots, i + 1, + projection=ccrs.UTM(zone=i + 1, southern_hemisphere=True)) ax.coastlines(resolution='110m') ax.gridlines() diff --git a/docs/source/conf.py b/docs/source/conf.py index 3a2de7e0c..3ba9b8892 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -19,7 +19,6 @@ from datetime import datetime import os import sys - import cartopy from distutils.version import LooseVersion import matplotlib @@ -31,7 +30,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -# -- General configuration ----------------------------------------------------- +# -- General configuration ----------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. needs_sphinx = '1.6' @@ -130,7 +129,8 @@ # directories to ignore when looking for source files. exclude_patterns = [] -# The reST default role (used for this markup: `text`) to use for all documents. +# The reST default role (used for this markup: `text`) to use for all +# documents. default_role = 'py:obj' # Handle subclasses of Matplotlib using an :rc: context in documentation @@ -156,7 +156,7 @@ # modindex_common_prefix = [] -# -- Options for HTML output --------------------------------------------------- +# -- Options for HTML output --------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. @@ -239,26 +239,26 @@ htmlhelp_basename = 'cartopydoc' -# -- Options for LaTeX output -------------------------------------------------- +# -- Options for LaTeX output -------------------------------------------- latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # 'papersize': 'letterpaper', + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). - # 'pointsize': '10pt', + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. - # 'preamble': '', + # Additional stuff for the LaTeX preamble. + # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'cartopy.tex', 'Cartopy Introduction', - 'Philip Elson, Richard Hattersley', 'manual', False), - ('introductory_examples/index', 'cartopy_examples.tex', 'Cartopy examples', - 'Philip Elson, Richard Hattersley', 'manual', True) + ('index', 'cartopy.tex', 'Cartopy Introduction', + 'Philip Elson, Richard Hattersley', 'manual', False), + ('introductory_examples/index', 'cartopy_examples.tex', 'Cartopy examples', + 'Philip Elson, Richard Hattersley', 'manual', True) ] # The name of an image file (relative to this directory) to place at the top of @@ -282,7 +282,7 @@ # latex_domain_indices = True -# -- Options for manual page output -------------------------------------------- +# -- Options for manual page output -------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). @@ -295,16 +295,16 @@ # man_show_urls = False -# -- Options for Texinfo output ------------------------------------------------ +# -- Options for Texinfo output ------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'cartopy', 'cartopy Documentation', - 'Philip Elson, Richard Hattersley', 'cartopy', - 'One line description of project.', - 'Miscellaneous'), + ('index', 'cartopy', 'cartopy Documentation', + 'Philip Elson, Richard Hattersley', 'cartopy', + 'One line description of project.', + 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. @@ -317,7 +317,7 @@ # texinfo_show_urls = 'footnote' -# -- Options for Epub output --------------------------------------------------- +# -- Options for Epub output --------------------------------------------- # Bibliographic Dublin Core info. epub_title = 'cartopy' @@ -371,13 +371,12 @@ ############ extlinks extension ############ extlinks = {'issues': ('https://github.com/SciTools/cartopy/labels/%s', - 'issues labeled with '), + 'issues labeled with '), 'issue': ('https://github.com/SciTools/cartopy/issues/%s', 'Issue #'), 'pull': ('https://github.com/SciTools/cartopy/pull/%s', 'PR #'), } - ############ plot directive ############## plot_html_show_formats = False diff --git a/docs/source/contributors.rst b/docs/source/contributors.rst index 2ab85ac60..781947307 100644 --- a/docs/source/contributors.rst +++ b/docs/source/contributors.rst @@ -40,6 +40,7 @@ the package wouldn't be as rich or diverse as it is today: * Greg Lucas * Sadie Bartholomew * Kacper Makuch + * Philipe Riskalla Leal * Stephane Raynaud * John Krasting diff --git a/examples/gridlines_and_labels/gridliner.py b/examples/gridlines_and_labels/gridliner.py old mode 100755 new mode 100644 index 3d8e413d2..972b6fae5 --- a/examples/gridlines_and_labels/gridliner.py +++ b/examples/gridlines_and_labels/gridliner.py @@ -1,20 +1,16 @@ """ Gridlines and tick labels ------------------------- - These examples demonstrate how to quickly add longitude and latitude gridlines and tick labels on a non-rectangular projection. - As you can see on the first example, longitude labels may be drawn on left and right sides, and latitude labels may be drawn on bottom and top sides. Thanks to the ``dms`` keyword, minutes are used when appropriate to display fractions of degree. - In the second example, labels are still drawn at the map edges despite its complexity, and some others are also drawn within the map boundary. - In the third example, labels are drawn only on the left and bottom sides. """ import cartopy.crs as ccrs diff --git a/examples/lines_and_polygons/always_circular_stereo.py b/examples/lines_and_polygons/always_circular_stereo.py index 9b59074f0..cc4117988 100644 --- a/examples/lines_and_polygons/always_circular_stereo.py +++ b/examples/lines_and_polygons/always_circular_stereo.py @@ -41,7 +41,7 @@ def main(): # Compute a circle in axes coordinates, which we can use as a boundary # for the map. We can pan/zoom as much as we like - the boundary will be # permanently circular. - theta = np.linspace(0, 2*np.pi, 100) + theta = np.linspace(0, 2 * np.pi, 100) center, radius = [0.5, 0.5], 0.5 verts = np.vstack([np.sin(theta), np.cos(theta)]).T circle = mpath.Path(verts * radius + center) diff --git a/examples/miscellanea/un_flag.py b/examples/miscellanea/un_flag.py index f2730d2cc..1b5a749d9 100644 --- a/examples/miscellanea/un_flag.py +++ b/examples/miscellanea/un_flag.py @@ -29,8 +29,8 @@ def olive_path(): """ olives_verts = np.array( - [[0, 2, 6, 9, 30, 55, 79, 94, 104, 117, 134, 157, 177, - 188, 199, 207, 191, 167, 149, 129, 109, 87, 53, 22, 0, 663, + [[0, 2, 6, 9, 30, 55, 79, 94, 104, 117, 134, 157, 177, + 188, 199, 207, 191, 167, 149, 129, 109, 87, 53, 22, 0, 663, 245, 223, 187, 158, 154, 150, 146, 149, 154, 158, 181, 184, 197, 181, 167, 153, 142, 129, 116, 119, 123, 127, 151, 178, 203, 220, 237, 245, 663, 280, 267, 232, 209, 205, 201, 196, 196, 201, 207, @@ -47,14 +47,14 @@ def olive_path(): 249, 242, 231, 214, 208, 208, 227, 244, 252, 258, 262, 262, 261, 262, 264, 265, 252, 663, 185, 197, 206, 215, 223, 233, 242, 237, 237, 230, 220, 202, 185, 663], - [8, 5, 3, 0, 22, 46, 46, 46, 35, 27, 16, 10, 18, - 22, 28, 38, 27, 26, 33, 41, 52, 52, 52, 30, 8, 595, - 77, 52, 61, 54, 53, 52, 53, 55, 55, 57, 65, 90, 106, - 96, 81, 68, 58, 54, 51, 50, 51, 50, 44, 34, 43, 48, - 61, 77, 595, 135, 104, 102, 83, 79, 76, 74, 74, 79, 84, - 90, 109, 135, 156, 145, 133, 121, 100, 77, 62, 69, 67, 80, + [8, 5, 3, 0, 22, 46, 46, 46, 35, 27, 16, 10, 18, + 22, 28, 38, 27, 26, 33, 41, 52, 52, 52, 30, 8, 595, + 77, 52, 61, 54, 53, 52, 53, 55, 55, 57, 65, 90, 106, + 96, 81, 68, 58, 54, 51, 50, 51, 50, 44, 34, 43, 48, + 61, 77, 595, 135, 104, 102, 83, 79, 76, 74, 74, 79, 84, + 90, 109, 135, 156, 145, 133, 121, 100, 77, 62, 69, 67, 80, 92, 113, 135, 595, 198, 171, 156, 134, 129, 124, 120, 123, 126, - 129, 138, 149, 161, 175, 188, 202, 177, 144, 116, 110, 105, 99, + 129, 138, 149, 161, 175, 188, 202, 177, 144, 116, 110, 105, 99, 108, 116, 126, 136, 147, 162, 173, 186, 198, 595, 249, 255, 261, 267, 241, 222, 200, 192, 183, 175, 175, 175, 175, 199, 221, 240, 245, 250, 256, 245, 233, 222, 207, 194, 180, 172, 162, 153, 154, diff --git a/examples/scalebar/scalebar_usage_examples.py b/examples/scalebar/scalebar_usage_examples.py new file mode 100644 index 000000000..46ca6277b --- /dev/null +++ b/examples/scalebar/scalebar_usage_examples.py @@ -0,0 +1,61 @@ +# Copyright Cartopy Contributors +# +# This file is part of Cartopy and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. + +import matplotlib.pyplot as plt +import cartopy.crs as ccrs +from cartopy.mpl.scalebar import fancy_scalebar + + +def scalebar_invoqued_as_an_independent_object_geoaxes(): + + fig, axes = plt.subplots(1, 2, + subplot_kw={'projection': + ccrs.Mercator()}) + + projections = [ccrs.Mercator(), ccrs.PlateCarree()] + + axes = axes.ravel() + + for proj, ax in zip(projections, axes): + ax.projection = proj + fancy_scalebar(ax, + location=(0.5, 0.2), + length=1000_000, + unit_name='km', + angle=0, + max_stripes=5, + fontsize=8, + dy=0.05) + + ax.gridlines(draw_labels=True) + ax.stock_img() + ax.coastlines() + + +def scalebar_from_within_geoaxes(): + + fig, axes = plt.subplots(1, 2, + subplot_kw={'projection': + ccrs.Mercator()}) + + projections = [ccrs.Mercator(), + ccrs.PlateCarree()] + + axes = axes.ravel() + + for proj, ax in zip(projections, axes): + ax.projection = proj + + ax.set_extent([-60, -35, -40, 10]) + ax.gridlines(draw_labels=True) + ax.add_scalebar(location=(0.5, 0.5), + length=250_000, + dy=5, + max_stripes=3) + ax.stock_img() + ax.coastlines() + + fig.show() diff --git a/lib/cartopy/crs.py b/lib/cartopy/crs.py index 1505819e2..761a10a0c 100644 --- a/lib/cartopy/crs.py +++ b/lib/cartopy/crs.py @@ -640,6 +640,7 @@ class RotatedGeodetic(CRS): central_rotated_longitude value. """ + def __init__(self, pole_longitude, pole_latitude, central_rotated_longitude=0.0, globe=None): """ @@ -1289,6 +1290,7 @@ class _RectangularProjection(Projection, metaclass=ABCMeta): is symmetric about the origin. """ + def __init__(self, proj4_params, half_width, half_height, globe=None): self._half_width = half_width self._half_height = half_height @@ -1427,6 +1429,7 @@ class TransverseMercator(Projection): A Transverse Mercator projection. """ + def __init__(self, central_longitude=0.0, central_latitude=0.0, false_easting=0.0, false_northing=0.0, scale_factor=1.0, globe=None, approx=None): @@ -1556,6 +1559,7 @@ class UTM(Projection): Universal Transverse Mercator projection. """ + def __init__(self, zone, southern_hemisphere=False, globe=None): """ Parameters @@ -1590,13 +1594,13 @@ def boundary(self): def x_limits(self): easting = 5e5 # allow 50% overflow - return (0 - easting/2, 2 * easting + easting/2) + return (0 - easting / 2, 2 * easting + easting / 2) @property def y_limits(self): northing = 1e7 # allow 50% overflow - return (0 - northing, 2 * northing + northing/2) + return (0 - northing, 2 * northing + northing / 2) class EuroPP(UTM): @@ -1606,6 +1610,7 @@ class EuroPP(UTM): Ellipsoid is International 1924, Datum is ED50. """ + def __init__(self): globe = Globe(ellipse='intl') super().__init__(32, globe=globe) @@ -2674,6 +2679,7 @@ class Geostationary(_Satellite): the satellite. """ + def __init__(self, central_longitude=0.0, satellite_height=35785831, false_easting=0, false_northing=0, globe=None, sweep_axis='y'): diff --git a/lib/cartopy/feature/__init__.py b/lib/cartopy/feature/__init__.py index eba0493c9..1f07318c9 100644 --- a/lib/cartopy/feature/__init__.py +++ b/lib/cartopy/feature/__init__.py @@ -113,6 +113,7 @@ class Scaler: """ General object for handling the scale of the geometries used in a Feature. """ + def __init__(self, scale): self._scale = scale @@ -140,6 +141,7 @@ class AdaptiveScaler(Scaler): """ Automatically select scale of geometries based on extent of plotted axes. """ + def __init__(self, default_scale, limits): """ Parameters @@ -198,6 +200,7 @@ class ShapelyFeature(Feature): shapely geometries. """ + def __init__(self, geometries, crs, **kwargs): """ Parameters @@ -227,6 +230,7 @@ class NaturalEarthFeature(Feature): See https://www.naturalearthdata.com/ """ + def __init__(self, category, name, scale, **kwargs): """ Parameters @@ -345,6 +349,7 @@ class GSHHSFeature(Feature): instantiating multiple GSHHS artists, by reducing repeated file IO. """ + def __init__(self, scale='auto', levels=None, **kwargs): super().__init__(cartopy.crs.PlateCarree(), **kwargs) @@ -424,6 +429,7 @@ class WFSFeature(Feature): This feature requires additional dependencies. If installed via pip, try ``pip install cartopy[ows]``. """ + def __init__(self, wfs, features, **kwargs): """ Parameters diff --git a/lib/cartopy/feature/nightshade.py b/lib/cartopy/feature/nightshade.py index b8a613474..9e1f42244 100644 --- a/lib/cartopy/feature/nightshade.py +++ b/lib/cartopy/feature/nightshade.py @@ -61,9 +61,9 @@ def __init__(self, date=None, delta=0.1, refraction=-0.83, pole_longitude=pole_lon, central_rotated_longitude=central_lon) - npts = int(180/delta) - x = np.empty(npts*2) - y = np.empty(npts*2) + npts = int(180 / delta) + x = np.empty(npts * 2) + y = np.empty(npts * 2) # Solve the equation for sunrise/sunset: # https://en.wikipedia.org/wiki/Sunrise_equation#Generalized_equation @@ -72,7 +72,7 @@ def __init__(self, date=None, delta=0.1, refraction=-0.83, # Therefore, the max/min latitude is +/- (90+refraction) # Fill latitudes up and then down - y[:npts] = np.linspace(-(90+refraction), 90+refraction, npts) + y[:npts] = np.linspace(-(90 + refraction), 90 + refraction, npts) y[npts:] = y[:npts][::-1] # Solve the generalized equation for omega0, which is the @@ -127,9 +127,9 @@ def _julian_day(date): year -= 1 B = 2 - year // 100 + (year // 100) // 4 - C = ((second/60 + minute)/60 + hour)/24 + C = ((second / 60 + minute) / 60 + hour) / 24 - JD = (int(365.25*(year + 4716)) + int(30.6001*(month+1)) + + JD = (int(365.25 * (year + 4716)) + int(30.6001 * (month + 1)) + day + B - 1524.5 + C) return JD @@ -158,20 +158,20 @@ def _solar_position(date): # so we need to convert the values from deg2rad when taking sin/cos # Centuries from J2000 - T_UT1 = (_julian_day(date) - 2451545.0)/36525 + T_UT1 = (_julian_day(date) - 2451545.0) / 36525 # solar longitude (deg) - lambda_M_sun = (280.460 + 36000.771*T_UT1) % 360 + lambda_M_sun = (280.460 + 36000.771 * T_UT1) % 360 # solar anomaly (deg) - M_sun = (357.5277233 + 35999.05034*T_UT1) % 360 + M_sun = (357.5277233 + 35999.05034 * T_UT1) % 360 # ecliptic longitude - lambda_ecliptic = (lambda_M_sun + 1.914666471*np.sin(np.deg2rad(M_sun)) + - 0.019994643*np.sin(np.deg2rad(2*M_sun))) + lambda_ecliptic = (lambda_M_sun + 1.914666471 * np.sin(np.deg2rad(M_sun)) + + 0.019994643 * np.sin(np.deg2rad(2 * M_sun))) # obliquity of the ecliptic (epsilon in Vallado's notation) - epsilon = 23.439291 - 0.0130042*T_UT1 + epsilon = 23.439291 - 0.0130042 * T_UT1 # declination of the sun delta_sun = np.rad2deg(np.arcsin(np.sin(np.deg2rad(epsilon)) * @@ -179,11 +179,11 @@ def _solar_position(date): # Greenwich mean sidereal time (seconds) theta_GMST = (67310.54841 + - (876600*3600 + 8640184.812866)*T_UT1 + - 0.093104*T_UT1**2 - - 6.2e-6*T_UT1**3) + (876600 * 3600 + 8640184.812866) * T_UT1 + + 0.093104 * T_UT1**2 - + 6.2e-6 * T_UT1**3) # Convert to degrees - theta_GMST = (theta_GMST % 86400)/240 + theta_GMST = (theta_GMST % 86400) / 240 # Right ascension calculations numerator = (np.cos(np.deg2rad(epsilon)) * @@ -196,7 +196,7 @@ def _solar_position(date): # longitude is opposite of Greenwich Hour Angle (GHA) # GHA == theta_GMST - alpha_sun - lon = -(theta_GMST-alpha_sun) + lon = -(theta_GMST - alpha_sun) if lon < -180: lon += 360 diff --git a/lib/cartopy/io/__init__.py b/lib/cartopy/io/__init__.py index fe11c9a96..d00dee4f3 100644 --- a/lib/cartopy/io/__init__.py +++ b/lib/cartopy/io/__init__.py @@ -323,6 +323,7 @@ class RasterSource: .. _raster-source-interface: """ + def validate_projection(self, projection): """ Raise an error if this raster source cannot provide images in the @@ -370,6 +371,7 @@ class RasterSourceContainer(RasterSource): contained :class:`RasterSource`. """ + def __init__(self, contained_source): """ Parameters @@ -394,6 +396,7 @@ class PostprocessedRasterSource(RasterSourceContainer): post-processing step on the raster fetched from the contained source. """ + def __init__(self, contained_source, img_post_process): """ Parameters diff --git a/lib/cartopy/io/img_nest.py b/lib/cartopy/io/img_nest.py index ecef0727f..c1ca9dfbb 100644 --- a/lib/cartopy/io/img_nest.py +++ b/lib/cartopy/io/img_nest.py @@ -176,12 +176,12 @@ def world_file_extent(worldfile_handle, im_shape): 'supported.') ul_corner = (float(lines[4]), float(lines[5])) - min_x, max_x = (ul_corner[0] - pix_size[0]/2., - ul_corner[0] + pix_size[0]*im_shape[0] - - pix_size[0]/2.) - min_y, max_y = (ul_corner[1] - pix_size[1]/2., - ul_corner[1] + pix_size[1]*im_shape[1] - - pix_size[1]/2.) + min_x, max_x = (ul_corner[0] - pix_size[0] / 2., + ul_corner[0] + pix_size[0] * im_shape[0] - + pix_size[0] / 2.) + min_y, max_y = (ul_corner[1] - pix_size[1] / 2., + ul_corner[1] + pix_size[1] * im_shape[1] - + pix_size[1] / 2.) return (min_x, max_x, min_y, max_y), pix_size diff --git a/lib/cartopy/io/img_tiles.py b/lib/cartopy/io/img_tiles.py index 2e855f467..431743f75 100644 --- a/lib/cartopy/io/img_tiles.py +++ b/lib/cartopy/io/img_tiles.py @@ -347,6 +347,7 @@ class Stamen(GoogleWTS): attribute this imagery. """ + def __init__(self, style='toner', desired_tile_form='RGB', cache=False): super().__init__(desired_tile_form=desired_tile_form, @@ -383,6 +384,7 @@ class StamenTerrain(Stamen): """ + def __init__(self, cache=False): warnings.warn( "The StamenTerrain class was deprecated in v0.17. " @@ -404,6 +406,7 @@ class MapboxTiles(GoogleWTS): For terms of service, see https://www.mapbox.com/tos/. """ + def __init__(self, access_token, map_id, cache=False): """ Set up a new Mapbox tiles instance. @@ -447,6 +450,7 @@ class MapboxStyleTiles(GoogleWTS): For terms of service, see https://www.mapbox.com/tos/. """ + def __init__(self, access_token, username, map_id, cache=False): """ Set up a new instance to retrieve tiles from a Mapbox style. @@ -488,6 +492,7 @@ class QuadtreeTiles(GoogleWTS): where the length of the quatree is the zoom level in Google Tile terms. """ + def _image_url(self, tile): return ('http://ecn.dynamic.t1.tiles.virtualearth.net/comp/' f'CompositionHandler/{tile}?mkt=en-' @@ -579,6 +584,7 @@ class OrdnanceSurvey(GoogleWTS): https://developer.ordnancesurvey.co.uk/os-api-framework-agreement. """ # API Documentation: https://apidocs.os.uk/docs/os-maps-wmts + def __init__(self, apikey, layer='Road', diff --git a/lib/cartopy/io/shapereader.py b/lib/cartopy/io/shapereader.py index ae063b171..7398db9bb 100644 --- a/lib/cartopy/io/shapereader.py +++ b/lib/cartopy/io/shapereader.py @@ -54,6 +54,7 @@ class Record: their associated geometry. """ + def __init__(self, shape, attributes, fields): self._shape = shape @@ -108,6 +109,7 @@ class FionaRecord(Record): with the FionaReader. """ + def __init__(self, geometry, attributes): self._geometry = geometry self.attributes = attributes @@ -122,6 +124,7 @@ class BasicReader: :meth:`~Reader.records` and :meth:`~Reader.geometries`. """ + def __init__(self, filename): # Validate the filename/shapefile self._reader = reader = shapefile.Reader(filename) @@ -175,6 +178,7 @@ class FionaReader: :meth:`~Reader.records` and :meth:`~Reader.geometries`. """ + def __init__(self, filename, bbox=None): self._data = [] diff --git a/lib/cartopy/io/srtm.py b/lib/cartopy/io/srtm.py index 10b41cafa..50f3e5f13 100644 --- a/lib/cartopy/io/srtm.py +++ b/lib/cartopy/io/srtm.py @@ -31,6 +31,7 @@ class _SRTMSource(RasterSource): interface `. """ + def __init__(self, resolution, downloader, max_nx, max_ny): """ Parameters @@ -172,6 +173,7 @@ class SRTM3Source(_SRTMSource): interface `. """ + def __init__(self, downloader=None, max_nx=3, max_ny=3): """ Parameters @@ -199,6 +201,7 @@ class SRTM1Source(_SRTMSource): interface `. """ + def __init__(self, downloader=None, max_nx=3, max_ny=3): """ Parameters @@ -250,12 +253,12 @@ def add_shading(elevation, azimuth, altitude): azimuth = np.deg2rad(azimuth) altitude = np.deg2rad(altitude) x, y = np.gradient(elevation) - slope = np.pi/2. - np.arctan(np.sqrt(x*x + y*y)) + slope = np.pi / 2. - np.arctan(np.sqrt(x * x + y * y)) # -x here because of pixel orders in the SRTM tile aspect = np.arctan2(-x, y) shaded = np.sin(altitude) * np.sin(slope)\ + np.cos(altitude) * np.cos(slope)\ - * np.cos((azimuth - np.pi/2.) - aspect) + * np.cos((azimuth - np.pi / 2.) - aspect) return shaded @@ -395,6 +398,7 @@ class SRTMDownloader(Downloader): available to download. """ + def __init__(self, target_path_template, pre_downloaded_path_template='', diff --git a/lib/cartopy/mpl/clip_path.py b/lib/cartopy/mpl/clip_path.py index 335fccf56..0233636df 100644 --- a/lib/cartopy/mpl/clip_path.py +++ b/lib/cartopy/mpl/clip_path.py @@ -30,10 +30,13 @@ def intersection_point(p0, p1, p2, p3): raise ValueError('Lines are parallel and cannot ' 'intersect at any one point.') - x = ((x_1 * y_2 - y_1 * x_2) * (x_3 - x_4) - (x_1 - x_2) * (x_3 * - y_4 - y_3 * x_4)) / div - y = ((x_1 * y_2 - y_1 * x_2) * (y_3 - y_4) - (y_1 - y_2) * (x_3 * - y_4 - y_3 * x_4)) / div + x = ((x_1 * y_2 - y_1 * x_2) * + (x_3 - x_4) - (x_1 - x_2) * (x_3 * y_4 - y_3 * x_4) + ) / div + + y = ((x_1 * y_2 - y_1 * x_2) * (y_3 - y_4) - + (y_1 - y_2) * (x_3 * y_4 - y_3 * x_4) + ) / div return x, y diff --git a/lib/cartopy/mpl/feature_artist.py b/lib/cartopy/mpl/feature_artist.py index bf0f823e4..6a84d7e12 100644 --- a/lib/cartopy/mpl/feature_artist.py +++ b/lib/cartopy/mpl/feature_artist.py @@ -32,6 +32,7 @@ class _GeomKey: A workaround for Shapely polygons no longer being hashable as of 1.5.13. """ + def __init__(self, geom): self._id = id(geom) diff --git a/lib/cartopy/mpl/geoaxes.py b/lib/cartopy/mpl/geoaxes.py index 8c47583bb..0f3eca0e8 100644 --- a/lib/cartopy/mpl/geoaxes.py +++ b/lib/cartopy/mpl/geoaxes.py @@ -30,7 +30,6 @@ import numpy as np import numpy.ma as ma import shapely.geometry as sgeom - from cartopy import config import cartopy.crs as ccrs import cartopy.feature @@ -42,6 +41,7 @@ from cartopy.mpl.slippy_image_artist import SlippyImageArtist from cartopy.vector_transform import vector_scalar_to_grid +from cartopy.mpl.scalebar import add_scalebar assert mpl.__version__ >= '3.1', \ 'Cartopy is only supported with Matplotlib 3.1 or greater.' @@ -373,6 +373,13 @@ def __init__(self, *args, **kwargs): self.img_factories = [] self._done_img_factory = False + def scale_bar(*args, **kwargs): + return add_scalebar(ax=self, *args, **kwargs) + + scale_bar.__doc__ = add_scalebar.__doc__ + + self.add_scalebar = scale_bar + @property def outline_patch(self): """ @@ -664,7 +671,7 @@ def tissot(self, rad_km=500, lons=None, lats=None, n_samples=80, **kwargs): raise ValueError('lons and lats must have the same shape.') for lon, lat in zip(lons, lats): - circle = geod.circle(lon, lat, rad_km*1e3, n_samples=n_samples) + circle = geod.circle(lon, lat, rad_km * 1e3, n_samples=n_samples) geoms.append(sgeom.Polygon(circle)) feature = cartopy.feature.ShapelyFeature(geoms, ccrs.Geodetic(), @@ -1509,7 +1516,9 @@ def gridlines(self, crs=None, draw_labels=False, """ if crs is None: crs = ccrs.PlateCarree() + from cartopy.mpl.gridliner import Gridliner + gl = Gridliner( self, crs=crs, draw_labels=draw_labels, xlocator=xlocs, ylocator=ylocs, collection_kwargs=kwargs, dms=dms, @@ -1854,7 +1863,7 @@ def _pcolormesh_patched(self, *args, **kwargs): # projection which will help with curved boundaries size_limit = (abs(self.projection.x_limits[1] - self.projection.x_limits[0]) / - (2*np.sqrt(2))) + (2 * np.sqrt(2))) to_mask = (np.isnan(diagonal0_lengths) | (diagonal0_lengths > size_limit) | np.isnan(diagonal1_lengths) | diff --git a/lib/cartopy/mpl/scalebar.py b/lib/cartopy/mpl/scalebar.py new file mode 100644 index 000000000..ab383e020 --- /dev/null +++ b/lib/cartopy/mpl/scalebar.py @@ -0,0 +1,559 @@ +# Copyright Cartopy Contributors +# +# This file is part of Cartopy and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. + +import cartopy.crs as ccrs +import cartopy.geodesic as cgeo +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.patches import Rectangle +from matplotlib.offsetbox import (AuxTransformBox, VPacker, HPacker, + TextArea) +from matplotlib.offsetbox import AnchoredOffsetbox +import matplotlib.transforms as transforms + + +def sbs_to_patch(sbs, transform, unit, padding=2, + bbox_transform='axes fraction', + bbox_to_anchor=(0.2, 0.3)): + + # First create a single Patch for all Sbs + of1 = HPacker(width=2, + height=1, + pad=1, + sep=0, + align="center", + mode="expand", children=sbs) + + t = AnchoredOffsetbox("upper left", + pad=0.4, frameon=False, + bbox_transform=bbox_transform, + bbox_to_anchor=bbox_to_anchor, + child=of1) + + return t + + +def get_unit_converter(unit): + + lookuptable = {'km': 1000, + 'mi': 1.60934 * 1000, # Miles to Km + 'dm': 1e-1, + 'cm': 1e-2, + 'mm': 1e-3, + 'um': 1e-6, + 'nm': 1e-9} + + return lookuptable.get(unit, 'km') + + +def _point_along_line(ax, start, distance, projected=False, verbose=False): + """Point at a given distance from start at a given angle. + + Args: + ax: CartoPy axes. + start: Starting point for the line in data coordinates. + distance: Positive physical distance to travel in meters. + angle: Anti-clockwise angle for the bar, in degrees. Default: 0 + + Returns: + (lon,lat) coords of a point (a (2, 1)-shaped NumPy array) + """ + + # Direction vector of the line in axes coordinates. + + if not projected: + + geodesic = cgeo.Geodesic() + + Direct_R = geodesic.direct(start, 90, distance) + + target_longitude, target_latitude, forw_azi = Direct_R.T + + target_point = ([target_longitude[0], target_latitude[0]]) + + actual_dist = geodesic.inverse(start, + target_point).ravel()[0] + if verbose: + + print('Starting point', start) + + print('target point', target_point) + print('Expected distance between points: ', distance) + + print('Actual distance between points: ', actual_dist) + + if projected: + + longitude, latitude = start + + target_longitude = longitude + distance + + target_point = (target_longitude, latitude) + + if verbose: + print('Axes is projected? ', projected) + print('Expected distance between points: ', distance) + + print('Actual distance between points: ', + target_longitude - longitude) + + return start, target_point + + +class AnchoredScaleBar(AnchoredOffsetbox): + def __init__(self, ax, + transform, + width, + height, + zorder, + xlabel, + fc, + ylabels=None, + loc=4, + fontsize=5, + pad=0.1, + borderpad=0.1, + sep=2, + prop=None, + add_ruler=False, + ruler_unit='Km', + ruler_unit_fontsize=7, + ruler_fontweight='bold', + tick_fontweight='light', + **kwargs): + """ + Draw a horizontal and/or vertical bar with the size in + data coordinate of the give axes. A label will be drawn + underneath (center-aligned). + + - transform : the coordinate frame (typically axes.transData) + + - sizex,sizey : width of x,y bar, in data units. 0 to omit + + - labelx,labely : labels for x,y bars; None to omit + + - loc : position in containing axes + + - pad, borderpad : padding, in fraction of the legend + font size (or prop) + + - sep : separation between labels and bars in points. + + - **kwargs : additional arguments passed to base class + + constructor + """ + + if ruler_unit_fontsize is None: + ruler_unit_fontsize = fontsize * 1.5 + + Rect = Rectangle((0, 0), + width, height, fc=fc, + edgecolor='k', + zorder=zorder,) + + ATB = AuxTransformBox(transform) + + ATB.add_artist(Rect) + + Txt_xlabel = TextArea(s=xlabel, + textprops=dict(fontsize=fontsize, + fontweight=tick_fontweight) + ) + + # vertically packing a single stripe with respective label + + child = VPacker(children=[Txt_xlabel, + ATB], + align="right", pad=5, sep=0) + + if add_ruler: + + Text = TextArea(s=ruler_unit, + textprops=dict(fontsize=ruler_unit_fontsize, + fontweight=ruler_fontweight)) + + child = VPacker(children=[child, Text], + align="center", pad=5, sep=0) + + else: + + Text = TextArea(s='', + textprops=dict(fontsize=ruler_unit_fontsize)) + + child = VPacker(children=[child, Text], + align="right", pad=5, sep=0) + + # horizontally packing all child packs in a single offsetBox + + AnchoredOffsetbox.__init__(self, + loc='center left', + borderpad=borderpad, + child=child, + prop=prop, + frameon=False, + **kwargs) + + +def _add_scalebar(ax, + projected, + xcoords, + height, + xlabels=None, + ylabels=None, + loc=4, + bbox_to_anchor=(0.2, 0.5), + bbox_transform='axes fraction', + frameon=False, + fontsize=5, + tick_fontweight='light', + ruler_unit_fontsize=None, + ruler_unit='Km', + ruler_fontweight='bold', + **kwargs): + """ Add scalebars to axes + Adds a set of scale bars to *ax*, matching the size + to the ticks of the plot + and optionally hiding the x and y axes + - ax : the axis to attach ticks to + - matchx,matchy : if True, set size of scale bars to spacing + between ticks + if False, size should be set using sizex and + sizey params + + - hidex,hidey : if True, hide x-axis and y-axis of parent + + - **kwargs : additional arguments passed to AnchoredScaleBars + + Returns + created scalebar object + """ + + if not projected: + proj = ax.projection._as_mpl_transform(ax) + else: + proj = ax.transData + blended_transform = transforms.blended_transform_factory( + proj, ax.get_figure().dpi_scale_trans) + + SBs = [] + + average_nticks = min(len(xlabels) - 1, len(xcoords) - 1) + + for enum, (xcor, xlabel) in enumerate(zip(xcoords[1:], + xlabels)): + width = xcor - xcoords[0] + if enum % 2 == 0: + fc = 'white' + else: + fc = 'black' + + xlabel = int(xlabel) + + zorder = 999 - enum + + if enum == average_nticks: + add_ruler = True + + else: # odd number + add_ruler = False + + sb = AnchoredScaleBar(ax, + blended_transform, + width, + height, + zorder, + xlabel, + fc=fc, + ylabels=ylabels, + loc=loc, + fontsize=fontsize, + bbox_transform=ax.transAxes, + bbox_to_anchor=bbox_to_anchor, + add_ruler=add_ruler, + ruler_unit=ruler_unit, + ruler_unit_fontsize=ruler_unit_fontsize, + ruler_fontweight=ruler_fontweight, + tick_fontweight=tick_fontweight, + **kwargs) + + sb.set_clip_on(False) + sb.set_zorder(zorder) + SBs.append(sb) + + # SB = sbs_to_patch(SBs, + # blended_transform, + # ruler_unit, padding=2, + # bbox_transform=ax.transAxes, + # bbox_to_anchor=bbox_to_anchor,) + # ax.add_artist(SB) + for sb in SBs: + ax.add_artist(sb) + + return sb + + +def add_scalebar(ax, + bbox_to_anchor, + length, + ruler_unit='km', + ruler_fontweight='bold', + tick_fontweight='light', + ruler_unit_fontsize=10, + dy=5, + max_stripes=5, + ytick_label_margins=0.25, + fontsize=8, + + frameon=False, + font_weight='bold', + rotation=45, + zorder=999, + paddings={'xmin': 0.1, + 'xmax': 0.1, + 'ymin': 0.3, + 'ymax': 0.8}, + + bbox_kwargs={'facecolor': 'w', + 'edgecolor': 'k', + 'alpha': 0.7}, + numeric_scale_bar=True, + numeric_scale_bar_kwgs={'x_text_offset': 0, + 'y_text_offset': -40, + 'box_x_coord': 0.5, + 'box_y_coord': 0.01}, + verbose=False): + ''' + Description + + ---------- + This function draws a scalebar in the given geoaxes. + + Parameters + ---------- + ax (geoaxes): the geoaxes that the scalebar wil be plotted on. + + + bbox_to_anchor (length 2 tuple): + It sets where the scalebar will be drawn + in axes fraction units. + + + length (float): + The distance in geodesic meters that will be used + for generating the scalebar. + + + ruler_unit (str): the unit scale that will be used + for the geodesic distance. + Standard: km + + Options available are presented within the lookuptable dictionary + of the "get_unit_converter" function, and are presented below. + lookuptable = {'km': 1000, + 'mi': 1.60934 * 1000, # Miles to Km + 'dm': 1e-1, + 'cm': 1e-2, + 'mm': 1e-3, + 'um': 1e-6, + 'nm': 1e-9} + + + ruler_fontweight (str): the fontweight that will be used for + the ruler unit that will be plotted on the scalebar + Standard: 'bold' + + + ruler_unit_fontsize (float or int): the size that will be + used for the ruler_unit within the scalebar + Standard: 10 + + + tick_fontweight (str): the fontweight that will be applied on + the ticks of the scalebar + Standard:'light' + + + dy (int or float): + The hight of the scalebar in axes fraction. + + + max_stripes (int): + The number of stripes present in the scalebar. + + + ytick_label_margins (int or float): + The size of the margins for drawing the scalebar ticklabels. + + + fontsize (int or float): + The fontsize used for drawing the scalebar ticklabels. + + + font_weight (str): + the fontweight used for drawing the scalebar ticklabels. + + + rotation (int or float): + the rotation used for drawing the scalebar ticklabels. + + + zorder(int): + The zorder used for drawing the scalebar. + + + paddings (dict): + A dictionary defining the padding to draw a background box + around the scalebar. + + Example of allowed arguments for padding: + {'xmin': 0.3, + 'xmax': 0.3, + 'ymin': 0.3, + 'ymax': 0.3} + + + bbox_kwargs (dict): + A dictionary defining the background box + around the scalebar. + + Example of allowed arguments for padding: + {'facecolor': 'w', + 'edgecolor': 'k', + 'alpha': 0.7} + + + numeric_scale_bar(bool): + whether or not to draw a number scalebar along side the + graphic scalebar. Notice that this option can drastically + vary in value, depending on the geoaxes projection used. + + + numeric_scale_bar_kwgs (dict): + A dictionary defining the numeric scale bar. + + Example of allowed arguments: + {'x_text_offset': 0, + 'y_text_offset': -40, + 'box_x_coord': 0.5, + 'box_y_coord': 0.01} + + + Returns + ---------- + None + ''' + + proj_units = ax.projection.proj4_params.get('units', 'degrees') + if proj_units.startswith('deg'): + projected = False + + elif proj_units.startswith('m'): + projected = True + + # getting the basic unit converter for labeling the xticks + unit_converter = get_unit_converter(ruler_unit) + + if verbose: + print('Axes is projected? ', projected) + + # Map central XY data coordinates + x0, x1, y0, y1 = ax.get_extent() + + central_coord_map = np.mean([[x0, x1], [y0, y1]], axis=1).tolist() + + # End-point of bar in lon/lat coords. + start, end = _point_along_line(ax, + central_coord_map, + length, + projected=projected, + verbose=verbose) + + # choose exact X points as sensible grid ticks with Axis 'ticker' helper + xcoords = np.empty(max_stripes + 1) + xlabels = [] + + xcoords[0] = start[0] + + ycoords = np.empty_like(xcoords) + + for i in range(0, max_stripes): + + startp, endp = _point_along_line(ax, central_coord_map, + length * (i + 1), + projected=projected) + # to ensure that the scalebar will not be so long as to + # cause errors in the plot. + if i > 0: + if endp[0] < xcoords[i]: + break + + xcoords[i + 1] = endp[0] + + ycoords[i + 1] = end[1] + + label = round(length * (i + 1) / unit_converter) + + xlabels.append(label) + + # Stacking data coordinates (the target ticks of the scalebar) in a list + + _add_scalebar(ax, + projected, + xcoords, + dy, + xlabels=xlabels, + ylabels=None, + loc=4, + + frameon=frameon, + bbox_to_anchor=bbox_to_anchor, + fontsize=fontsize, + tick_fontweight=tick_fontweight, + ruler_unit_fontsize=ruler_unit_fontsize, + ruler_unit=ruler_unit, + ruler_fontweight=ruler_fontweight, + ) + + +if '__main__' == __name__: + + def test_scalebar(): + """Test""" + + fig, axes = plt.subplots(1, 2, + subplot_kw={'projection': + ccrs.Mercator()}) + + projections = [ccrs.Mercator(), ccrs.PlateCarree()] + + axes = axes.ravel() + + for proj, ax in zip(projections, axes): + + ax.projection = proj + + ax.set_title(ax.projection.__class__.__name__) + + add_scalebar(ax, + bbox_to_anchor=(0.1, 0.2), + length=10_000_000, + ruler_unit='km', + max_stripes=3, + fontsize=8, + frameon=True, + ruler_unit_fontsize=15, + ruler_fontweight='bold', + tick_fontweight='bold', + dy=0.085) + + ax.gridlines(draw_labels=True) + ax.stock_img() + ax.coastlines() + + return axes + + axes = test_scalebar() diff --git a/lib/cartopy/mpl/slippy_image_artist.py b/lib/cartopy/mpl/slippy_image_artist.py index 0b3088c68..4fa9d6480 100644 --- a/lib/cartopy/mpl/slippy_image_artist.py +++ b/lib/cartopy/mpl/slippy_image_artist.py @@ -24,6 +24,7 @@ class SlippyImageArtist(AxesImage): Kwargs are passed to the AxesImage constructor. """ + def __init__(self, ax, raster_source, **kwargs): self.raster_source = raster_source # This artist fills the Axes, so should not influence layout. diff --git a/lib/cartopy/tests/crs/test_utm.py b/lib/cartopy/tests/crs/test_utm.py index 7c82f1eee..13dcd412a 100644 --- a/lib/cartopy/tests/crs/test_utm.py +++ b/lib/cartopy/tests/crs/test_utm.py @@ -28,7 +28,7 @@ def test_default(south): assert_almost_equal(np.array(utm.x_limits), [-250000, 1250000]) assert_almost_equal(np.array(utm.y_limits), - [-10000000, 25000000]) + [-10000000, 25000000]) def test_ellipsoid_transform(): @@ -43,7 +43,7 @@ def test_ellipsoid_transform(): assert_almost_equal(np.array(utm.x_limits), [-250000, 1250000]) assert_almost_equal(np.array(utm.y_limits), - [-10000000, 25000000]) + [-10000000, 25000000]) result = utm.transform_point(-73.5, 40.5, geodetic) assert_almost_equal(result, np.array([127106.5 + 500000, 4484124.4]), diff --git a/lib/cartopy/tests/feature/test_nightshade.py b/lib/cartopy/tests/feature/test_nightshade.py index 8764943df..cd1bca1de 100644 --- a/lib/cartopy/tests/feature/test_nightshade.py +++ b/lib/cartopy/tests/feature/test_nightshade.py @@ -35,10 +35,10 @@ def test_julian_day(): # ?month=6&day=21&year=2030&hour=0&min=0&sec=0&n=&ntxt=&earth=0 @pytest.mark.parametrize('dt, true_lat, true_lon', [ - (datetime(2018, 9, 29, 0, 0), -(2 + 18/60), (177 + 37/60)), - (datetime(2018, 9, 29, 14, 0), -(2 + 32/60), -(32 + 25/60)), - (datetime(1992, 2, 14, 0, 0), -(13 + 20/60), -(176 + 26/60)), - (datetime(2030, 6, 21, 0, 0), (23 + 26/60), -(179 + 34/60)) + (datetime(2018, 9, 29, 0, 0), -(2 + 18 / 60), (177 + 37 / 60)), + (datetime(2018, 9, 29, 14, 0), -(2 + 32 / 60), -(32 + 25 / 60)), + (datetime(1992, 2, 14, 0, 0), -(13 + 20 / 60), -(176 + 26 / 60)), + (datetime(2030, 6, 21, 0, 0), (23 + 26 / 60), -(179 + 34 / 60)) ]) def test_solar_position(dt, true_lat, true_lon): lat, lon = _solar_position(dt) diff --git a/lib/cartopy/tests/mpl/test_caching.py b/lib/cartopy/tests/mpl/test_caching.py index 60f5b871c..0eb51f865 100644 --- a/lib/cartopy/tests/mpl/test_caching.py +++ b/lib/cartopy/tests/mpl/test_caching.py @@ -60,6 +60,7 @@ class CallCounter: """ + def __init__(self, parent, function_name): self.count = 0 self.parent = parent diff --git a/lib/cartopy/tests/mpl/test_examples.py b/lib/cartopy/tests/mpl/test_examples.py index c56a5e1ff..73b5cf4a1 100644 --- a/lib/cartopy/tests/mpl/test_examples.py +++ b/lib/cartopy/tests/mpl/test_examples.py @@ -13,6 +13,7 @@ class ExampleImageTesting(ImageTesting): """Subclasses ImageTesting to nullify the plt.show commands.""" + def __call__(self, test_func): fn = ImageTesting.__call__(self, test_func) diff --git a/lib/cartopy/tests/mpl/test_gridliner.py b/lib/cartopy/tests/mpl/test_gridliner.py index 8c4572630..daa51b5fd 100644 --- a/lib/cartopy/tests/mpl/test_gridliner.py +++ b/lib/cartopy/tests/mpl/test_gridliner.py @@ -4,6 +4,7 @@ # See COPYING and COPYING.LESSER in the root of the repository for full # licensing details. + import matplotlib.pyplot as plt import matplotlib.ticker as mticker import numpy as np @@ -437,8 +438,7 @@ def test_gridliner_draw_labels_param(draw_labels, result): x_inline=False, y_inline=False) gl.xlocator = mticker.FixedLocator([70, 100, 130]) gl.ylocator = mticker.FixedLocator([40, 50]) - plt.show() res = {} for loc in 'left', 'right', 'top', 'bottom': - artists = getattr(gl, loc+'_label_artists') + artists = getattr(gl, loc + '_label_artists') res[loc] = [a.get_text() for a in artists if a.get_visible()] diff --git a/lib/cartopy/tests/mpl/test_images.py b/lib/cartopy/tests/mpl/test_images.py index 0ba8ad229..46c97e89b 100644 --- a/lib/cartopy/tests/mpl/test_images.py +++ b/lib/cartopy/tests/mpl/test_images.py @@ -144,7 +144,7 @@ def test_imshow_wrapping(): def test_imshow_rgba(): # tests that the alpha of a RGBA array passed to imshow is set to 0 # instead of masked - z = np.ones((100, 100))*0.5 + z = np.ones((100, 100)) * 0.5 cmap = cm.get_cmap() norm = colors.Normalize(vmin=0, vmax=1) z1 = cmap(norm(z)) @@ -160,7 +160,7 @@ def test_imshow_rgba(): def test_imshow_rgb(): # tests that the alpha of a RGB array passed to imshow is set to 0 # instead of masked - z = np.ones((100, 100, 3))*0.5 + z = np.ones((100, 100, 3)) * 0.5 plt_crs = ccrs.LambertAzimuthalEqualArea() latlon_crs = ccrs.PlateCarree() ax = plt.axes(projection=plt_crs) diff --git a/lib/cartopy/tests/mpl/test_mpl_integration.py b/lib/cartopy/tests/mpl/test_mpl_integration.py index 3fdf9097f..aa5914975 100644 --- a/lib/cartopy/tests/mpl/test_mpl_integration.py +++ b/lib/cartopy/tests/mpl/test_mpl_integration.py @@ -129,7 +129,7 @@ def test_global_hexbin_wrap_transform(): ax.coastlines(zorder=2) x, y = np.meshgrid(np.arange(0, 360), np.arange(-90, 91)) # wrap values so to match x values from test_global_hexbin_wrap - x_wrap = np.where(x >= 180, x-360, x) + x_wrap = np.where(x >= 180, x - 360, x) data = np.sin(np.sqrt(x_wrap**2 + y**2)) plt.hexbin( x.flatten(), @@ -857,4 +857,4 @@ def test_streamplot(): ax.set_extent(plot_extent, crs=ccrs.PlateCarree()) ax.coastlines() ax.streamplot(x, y, u, v, transform=ccrs.PlateCarree(), - density=(1.5, 2), color=mag, linewidth=2*mag) + density=(1.5, 2), color=mag, linewidth=2 * mag) diff --git a/lib/cartopy/tests/mpl/test_scalebar.py b/lib/cartopy/tests/mpl/test_scalebar.py new file mode 100644 index 000000000..5d8107852 --- /dev/null +++ b/lib/cartopy/tests/mpl/test_scalebar.py @@ -0,0 +1,106 @@ +# Copyright Cartopy Contributors +# +# This file is part of Cartopy and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. + + +import matplotlib.pyplot as plt +import pytest + +import cartopy.crs as ccrs + +from cartopy.tests.mpl import MPL_VERSION, ImageTesting + +from cartopy.mpl.scalebar import add_scalebar + + +# The tolerance on these tests are particularly high because of the high number +# of text objects. A new testing strategy is needed for this kind of test. +if MPL_VERSION < "3": + TOL = 15 +else: + TOL = 0.5 + +grid_label_tol = grid_label_inline_tol = grid_label_inline_usa_tol = TOL + +scalebar_plot = 'scalebar_plot' +scalebar_inline_plot = 'scalebar_inline_plot' + + +@pytest.mark.natural_earth +@ImageTesting([scalebar_plot], tolerance=grid_label_tol) +def test_scalebar(): + """Test""" + + fig, axes = plt.subplots(1, 2, + subplot_kw={'projection': + ccrs.Mercator()}) + + projections = [ccrs.Mercator(), ccrs.PlateCarree()] + + axes = axes.ravel() + + for proj, ax in zip(projections, axes): + + ax.projection = proj + + ax.set_title(ax.projection.__class__.__name__) + + add_scalebar(ax, + bbox_to_anchor=(0.1, 0.2), + length=10_000_000, + ruler_unit='km', + max_stripes=3, + fontsize=8, + frameon=True, + ruler_unit_fontsize=15, + ruler_fontweight='bold', + tick_fontweight='bold', + dy=0.085) + + ax.gridlines(draw_labels=True) + ax.stock_img() + ax.coastlines() + + return axes + + +@pytest.mark.natural_earth +@ImageTesting([scalebar_inline_plot], tolerance=grid_label_tol) +def test_scalebar2(): + """Test""" + + fig, axes = plt.subplots(1, 2, + subplot_kw={'projection': + ccrs.Mercator()}) + + projections = [ccrs.Mercator(), ccrs.PlateCarree()] + + axes = axes.ravel() + + for proj, ax in zip(projections, axes): + + ax.projection = proj + + ax.set_title(ax.projection.__class__.__name__) + + ax.add_scalebar(bbox_to_anchor=(0.1, 0.2), + length=10_000_000, + ruler_unit='km', + max_stripes=3, + fontsize=8, + frameon=True, + ruler_unit_fontsize=15, + ruler_fontweight='bold', + tick_fontweight='bold', + dy=0.085) + + ax.gridlines(draw_labels=True) + ax.stock_img() + ax.coastlines() + + return axes + + +test_scalebar2() diff --git a/lib/cartopy/tests/test_polygon.py b/lib/cartopy/tests/test_polygon.py index c7d7f00f2..96215026a 100644 --- a/lib/cartopy/tests/test_polygon.py +++ b/lib/cartopy/tests/test_polygon.py @@ -280,7 +280,7 @@ def test_project_degenerate_poly(self): target = ccrs.PlateCarree() # Before fixing, this would cause a segmentation fault. polygons = target.project_geometry(polygon, source) - assert type(polygons) == sgeom.MultiPolygon + assert isinstance(polygons, sgeom.MultiPolygon) class TestQuality: