Cartopy-0.14.2/ 0000755 0013740 0002103 00000000000 12706121712 012746 5 ustar itpe avd 0000000 0000000 Cartopy-0.14.2/lib/ 0000755 0013740 0002103 00000000000 12706121712 013514 5 ustar itpe avd 0000000 0000000 Cartopy-0.14.2/lib/Cartopy.egg-info/ 0000755 0013740 0002103 00000000000 12706121712 016627 5 ustar itpe avd 0000000 0000000 Cartopy-0.14.2/lib/Cartopy.egg-info/PKG-INFO 0000644 0013740 0002103 00000004275 12706121710 017732 0 ustar itpe avd 0000000 0000000 Metadata-Version: 1.1
Name: Cartopy
Version: 0.14.2
Summary: A cartographic python library with matplotlib support for visualisation
Home-page: http://scitools.org.uk/cartopy/docs/latest/
Author: UK Met Office
Author-email: UNKNOWN
License: LGPLv3
Download-URL: https://github.com/SciTools/cartopy
Description:
=======
Cartopy
=======
.. image:: http://scitools.org.uk/cartopy/docs/latest/_static/cartopy.png
Cartopy is a Python package designed to make drawing maps for data analysis and visualisation easy.
It features:
* object oriented projection definitions
* point, line, polygon and image transformations between projections
* integration to expose advanced mapping in matplotlib with a simple and intuitive interface
* powerful vector data handling by integrating shapefile reading with Shapely capabilities
Documentation can be found at http://scitools.org.uk/cartopy/docs/latest/.
----
Code
----
Cartopy is licenced under GNU Lesser General Public License (LGPLv3).
Development occurs at https://github.com/SciTools/cartopy.
Keywords: cartography map transform projection proj.4 geos shapely shapefile
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX
Classifier: Operating System :: POSIX :: AIX
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: C++
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Scientific/Engineering :: GIS
Classifier: Topic :: Scientific/Engineering :: Visualization
Cartopy-0.14.2/lib/Cartopy.egg-info/SOURCES.txt 0000644 0013740 0002103 00000022405 12706121712 020516 0 ustar itpe avd 0000000 0000000 CHANGES
CONTRIBUTING.md
COPYING
COPYING.LESSER
INSTALL
MANIFEST.in
README.md
README.rst
setup.py
lib/Cartopy.egg-info/PKG-INFO
lib/Cartopy.egg-info/SOURCES.txt
lib/Cartopy.egg-info/dependency_links.txt
lib/Cartopy.egg-info/requires.txt
lib/Cartopy.egg-info/top_level.txt
lib/cartopy/__init__.py
lib/cartopy/_crs.c
lib/cartopy/_crs.pxd
lib/cartopy/_crs.pyx
lib/cartopy/_epsg.py
lib/cartopy/_trace.cpp
lib/cartopy/_trace.h
lib/cartopy/crs.py
lib/cartopy/feature.py
lib/cartopy/img_transform.py
lib/cartopy/trace.cpp
lib/cartopy/trace.pyx
lib/cartopy/util.py
lib/cartopy/vector_transform.py
lib/cartopy/data/netcdf/HadISST1_SST_update.README.txt
lib/cartopy/data/netcdf/HadISST1_SST_update.nc
lib/cartopy/data/raster/natural_earth/50-natural-earth-1-downsampled.png
lib/cartopy/data/raster/sample/Miriam.A2012270.2050.2km.README.txt
lib/cartopy/data/raster/sample/Miriam.A2012270.2050.2km.jpg
lib/cartopy/data/shapefiles/gshhs/README.TXT
lib/cartopy/data/shapefiles/gshhs/c/GSHHS_c_L1.dbf
lib/cartopy/data/shapefiles/gshhs/c/GSHHS_c_L1.shp
lib/cartopy/data/shapefiles/gshhs/c/GSHHS_c_L1.shx
lib/cartopy/data/shapefiles/gshhs/l/GSHHS_l_L2.dbf
lib/cartopy/data/shapefiles/gshhs/l/GSHHS_l_L2.shp
lib/cartopy/data/shapefiles/gshhs/l/GSHHS_l_L2.shx
lib/cartopy/examples/__init__.py
lib/cartopy/examples/always_circular_stereo.py
lib/cartopy/examples/arrows.py
lib/cartopy/examples/aurora_forecast.py
lib/cartopy/examples/barbs.py
lib/cartopy/examples/eccentric_ellipse.py
lib/cartopy/examples/effects_of_the_ellipse.py
lib/cartopy/examples/eyja_volcano.py
lib/cartopy/examples/favicon.py
lib/cartopy/examples/feature_creation.py
lib/cartopy/examples/features.py
lib/cartopy/examples/geostationary.py
lib/cartopy/examples/global_map.py
lib/cartopy/examples/hurricane_katrina.py
lib/cartopy/examples/image_tiles.py
lib/cartopy/examples/logo.py
lib/cartopy/examples/regridding_arrows.py
lib/cartopy/examples/rotated_pole.py
lib/cartopy/examples/srtm_shading.py
lib/cartopy/examples/star_shaped_boundary.py
lib/cartopy/examples/streamplot.py
lib/cartopy/examples/tick_labels.py
lib/cartopy/examples/tissot.py
lib/cartopy/examples/tube_stations.py
lib/cartopy/examples/un_flag.py
lib/cartopy/examples/waves.py
lib/cartopy/examples/wms.py
lib/cartopy/examples/wmts.py
lib/cartopy/geodesic/__init__.py
lib/cartopy/geodesic/_geodesic.pyx
lib/cartopy/io/__init__.py
lib/cartopy/io/img_nest.py
lib/cartopy/io/img_tiles.py
lib/cartopy/io/ogc_clients.py
lib/cartopy/io/shapereader.py
lib/cartopy/io/srtm.npz
lib/cartopy/io/srtm.py
lib/cartopy/mpl/__init__.py
lib/cartopy/mpl/clip_path.py
lib/cartopy/mpl/feature_artist.py
lib/cartopy/mpl/geoaxes.py
lib/cartopy/mpl/gridliner.py
lib/cartopy/mpl/patch.py
lib/cartopy/mpl/slippy_image_artist.py
lib/cartopy/mpl/ticker.py
lib/cartopy/sphinxext/__init__.py
lib/cartopy/sphinxext/gallery.py
lib/cartopy/sphinxext/summarise_package.py
lib/cartopy/tests/__init__.py
lib/cartopy/tests/test_coastline.py
lib/cartopy/tests/test_coding_standards.py
lib/cartopy/tests/test_crs.py
lib/cartopy/tests/test_crs_transform_vectors.py
lib/cartopy/tests/test_geodesic.py
lib/cartopy/tests/test_img_nest.py
lib/cartopy/tests/test_img_tiles.py
lib/cartopy/tests/test_img_transform.py
lib/cartopy/tests/test_line_string.py
lib/cartopy/tests/test_linear_ring.py
lib/cartopy/tests/test_polygon.py
lib/cartopy/tests/test_shapereader.py
lib/cartopy/tests/test_util.py
lib/cartopy/tests/test_vector_transform.py
lib/cartopy/tests/crs/__init__.py
lib/cartopy/tests/crs/test_albers_equal_area.py
lib/cartopy/tests/crs/test_azimuthal_equidistant.py
lib/cartopy/tests/crs/test_geostationary.py
lib/cartopy/tests/crs/test_lambert_azimuthal_equal_area.py
lib/cartopy/tests/crs/test_lambert_conformal.py
lib/cartopy/tests/crs/test_mercator.py
lib/cartopy/tests/crs/test_robinson.py
lib/cartopy/tests/crs/test_rotated_geodetic.py
lib/cartopy/tests/crs/test_rotated_pole.py
lib/cartopy/tests/crs/test_sinusoidal.py
lib/cartopy/tests/crs/test_stereographic.py
lib/cartopy/tests/crs/test_transverse_mercator.py
lib/cartopy/tests/io/__init__.py
lib/cartopy/tests/io/test_downloaders.py
lib/cartopy/tests/io/test_ogc_clients.py
lib/cartopy/tests/io/test_srtm.py
lib/cartopy/tests/mpl/__init__.py
lib/cartopy/tests/mpl/test_axes.py
lib/cartopy/tests/mpl/test_caching.py
lib/cartopy/tests/mpl/test_crs.py
lib/cartopy/tests/mpl/test_examples.py
lib/cartopy/tests/mpl/test_features.py
lib/cartopy/tests/mpl/test_gridliner.py
lib/cartopy/tests/mpl/test_images.py
lib/cartopy/tests/mpl/test_img_transform.py
lib/cartopy/tests/mpl/test_mpl_integration.py
lib/cartopy/tests/mpl/test_patch.py
lib/cartopy/tests/mpl/test_pseudo_color.py
lib/cartopy/tests/mpl/test_quiver.py
lib/cartopy/tests/mpl/test_set_extent.py
lib/cartopy/tests/mpl/test_shapely_to_mpl.py
lib/cartopy/tests/mpl/test_ticker.py
lib/cartopy/tests/mpl/test_ticks.py
lib/cartopy/tests/mpl/test_web_services.py
lib/cartopy/tests/mpl/baseline_images/mpl/test_crs/lambert_conformal_south.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_crs/mercator_squashed.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_examples/global_map.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_features/gshhs_coastlines.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_features/natural_earth.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_features/natural_earth_custom.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_features/wfs.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_gridliner/gridliner1.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_gridliner/gridliner_labels.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_gridliner/gridliner_labels_pre_mpl_1.5.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_images/image_merge.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_images/image_nest.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_images/imshow_natural_earth_ortho.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_images/imshow_regional_projected.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_images/web_tiles.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_img_tiles2/web_tiles.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_img_transform/regrid_image.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/barbs_plate_carree.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/barbs_regrid.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/barbs_regrid_with_extent.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/global_contour_wrap.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/global_contourf_wrap.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/global_map.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/global_pcolor_wrap.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/global_pcolor_wrap_pre_mpl_1.5.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/global_scatter_wrap.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/multiple_projections1.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/natural_earth_interface.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/pcolormesh_global_wrap1.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/pcolormesh_global_wrap2.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/pcolormesh_global_wrap3.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/pcolormesh_goode_wrap.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/pcolormesh_limited_area_wrap.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/pcolormesh_mercator_wrap.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/quiver_plate_carree.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/quiver_regrid.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/quiver_regrid_with_extent.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/quiver_rotated_pole.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/simple_global.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/streamplot.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/streamplot_pre_mpl_1.5.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_shapely_to_mpl/contour_with_interiors.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_shapely_to_mpl/poly_interiors.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_shapely_to_mpl/poly_interiors_pre_mpl_1.5.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_ticks/xticks_cylindrical.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_ticks/xticks_cylindrical_pre_mpl_1.5.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_ticks/xticks_no_transform.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_ticks/xyticks.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_ticks/xyticks_pre_mpl_1.5.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_ticks/yticks_cylindrical.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_ticks/yticks_cylindrical_pre_mpl_1.5.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_ticks/yticks_no_transform.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_ticks/yticks_no_transform_pre_mpl_1.5.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_web_services/wms.png
lib/cartopy/tests/mpl/baseline_images/mpl/test_web_services/wmts.png
requirements/default.txt
requirements/epsg.txt
requirements/ows.txt
requirements/plotting.txt
requirements/tests.txt Cartopy-0.14.2/lib/Cartopy.egg-info/dependency_links.txt 0000644 0013740 0002103 00000000001 12706121710 022673 0 ustar itpe avd 0000000 0000000
Cartopy-0.14.2/lib/Cartopy.egg-info/requires.txt 0000644 0013740 0002103 00000000302 12706121710 021220 0 ustar itpe avd 0000000 0000000 numpy>=1.6
shapely>=1.5.6
pyshp>=1.1.4
six>=1.3.0
setuptools>=0.7.2
[epsg]
pyepsg>=0.2.0
[ows]
OWSLib>=0.8.7
pillow>=1.7.8
[plotting]
matplotlib>=1.3.0
GDAL>=1.10.0
pillow>=1.7.8
scipy>=0.10
Cartopy-0.14.2/lib/Cartopy.egg-info/top_level.txt 0000644 0013740 0002103 00000000010 12706121710 021346 0 ustar itpe avd 0000000 0000000 cartopy
Cartopy-0.14.2/lib/cartopy/ 0000755 0013740 0002103 00000000000 12706121712 015175 5 ustar itpe avd 0000000 0000000 Cartopy-0.14.2/lib/cartopy/examples/ 0000755 0013740 0002103 00000000000 12706121712 017013 5 ustar itpe avd 0000000 0000000 Cartopy-0.14.2/lib/cartopy/examples/__init__.py 0000644 0013740 0002103 00000000000 12520135744 021116 0 ustar itpe avd 0000000 0000000 Cartopy-0.14.2/lib/cartopy/examples/always_circular_stereo.py 0000755 0013740 0002103 00000002441 12520135744 024142 0 ustar itpe avd 0000000 0000000 __tags__ = ['Lines and polygons']
import matplotlib.path as mpath
import matplotlib.pyplot as plt
import numpy as np
import cartopy.crs as ccrs
import cartopy.feature
def main():
fig = plt.figure(figsize=[10, 5])
ax1 = plt.subplot(1, 2, 1, projection=ccrs.SouthPolarStereo())
ax2 = plt.subplot(1, 2, 2, projection=ccrs.SouthPolarStereo(),
sharex=ax1, sharey=ax1)
fig.subplots_adjust(bottom=0.05, top=0.95,
left=0.04, right=0.95, wspace=0.02)
# Limit the map to -60 degrees latitude and below.
ax1.set_extent([-180, 180, -90, -60], ccrs.PlateCarree())
ax1.add_feature(cartopy.feature.LAND)
ax1.add_feature(cartopy.feature.OCEAN)
ax1.gridlines()
ax2.gridlines()
ax2.add_feature(cartopy.feature.LAND)
ax2.add_feature(cartopy.feature.OCEAN)
# 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)
center, radius = [0.5, 0.5], 0.5
verts = np.vstack([np.sin(theta), np.cos(theta)]).T
circle = mpath.Path(verts * radius + center)
ax2.set_boundary(circle, transform=ax2.transAxes)
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/arrows.py 0000644 0013740 0002103 00000002125 12520135744 020706 0 ustar itpe avd 0000000 0000000 __tags__ = ['Vector data']
import matplotlib.pyplot as plt
import numpy as np
import cartopy
import cartopy.crs as ccrs
def sample_data(shape=(20, 30)):
"""
Returns ``(x, y, u, v, crs)`` of some vector data
computed mathematically. The returned crs will be a rotated
pole CRS, meaning that the vectors will be unevenly spaced in
regular PlateCarree space.
"""
crs = ccrs.RotatedPole(pole_longitude=177.5, pole_latitude=37.5)
x = np.linspace(311.9, 391.1, shape[1])
y = np.linspace(-23.6, 24.8, shape[0])
x2d, y2d = np.meshgrid(x, y)
u = 10 * (2 * np.cos(2 * np.deg2rad(x2d) + 3 * np.deg2rad(y2d + 30)) ** 2)
v = 20 * np.cos(6 * np.deg2rad(x2d))
return x, y, u, v, crs
def main():
ax = plt.axes(projection=ccrs.Orthographic(-10, 45))
ax.add_feature(cartopy.feature.OCEAN, zorder=0)
ax.add_feature(cartopy.feature.LAND, zorder=0, edgecolor='black')
ax.set_global()
ax.gridlines()
x, y, u, v, vector_crs = sample_data()
ax.quiver(x, y, u, v, transform=vector_crs)
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/aurora_forecast.py 0000644 0013740 0002103 00000013745 12705644356 022573 0 ustar itpe avd 0000000 0000000 """
Plotting the Aurora Forecast from NOAA on Orthographic Polar Projection
-----------------------------------------------------------------------
The National Oceanic and Atmospheric Administration (NOAA) monitors the
solar wind conditions using the ACE spacecraft orbiting close to the L1
Lagrangian point of the Sun-Earth system. This data is fed into the
OVATION-Prime model to forecast the probability of visible aurora at
various locations on Earth. Every five minutes a new forecast is
published for the coming 30 minutes. The data is provided as a
1024 by 512 grid of probabilities in percent of visible aurora. The
data spaced equally in degrees from -180 to 180 and -90 to 90.
"""
__tags__ = ["Scalar data"]
try:
from urllib2 import urlopen
except ImportError:
from urllib.request import urlopen
from io import StringIO
import numpy as np
from datetime import datetime
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
import matplotlib.patches as patches
def aurora_forecast():
"""
Gets the latest Aurora Forecast from http://swpc.noaa.gov.
Returns
-------
img : numpy array
The pixels of the image in a numpy array.
img_proj : cartopy CRS
The rectangular coordinate system of the image.
img_extent : tuple of floats
The extent of the image ``(x0, y0, x1, y1)`` referenced in
the ``img_proj`` coordinate system.
origin : str
The origin of the image to be passed through to matplotlib's imshow.
dt : datetime
Time of forecast validity.
"""
# GitHub gist to download the example data from
url = ('https://gist.githubusercontent.com/belteshassar/'
'c7ea9e02a3e3934a9ddc/raw/aurora-nowcast-map.txt')
# To plot the current forecast instead, uncomment the following line
# url = 'http://services.swpc.noaa.gov/text/aurora-nowcast-map.txt'
response_text = StringIO(urlopen(url).read().decode('utf-8'))
img = np.loadtxt(response_text)
# Read forecast date and time
response_text.seek(0)
for line in response_text:
if line.startswith('Product Valid At:', 2):
dt = datetime.strptime(line[-17:-1], '%Y-%m-%d %H:%M')
img_proj = ccrs.PlateCarree()
img_extent = (-180, 180, -90, 90)
return img, img_proj, img_extent, 'lower', dt
def aurora_cmap():
"""Return a colormap with aurora like colors"""
stops = {'red': [(0.00, 0.1725, 0.1725),
(0.50, 0.1725, 0.1725),
(1.00, 0.8353, 0.8353)],
'green': [(0.00, 0.9294, 0.9294),
(0.50, 0.9294, 0.9294),
(1.00, 0.8235, 0.8235)],
'blue': [(0.00, 0.3843, 0.3843),
(0.50, 0.3843, 0.3843),
(1.00, 0.6549, 0.6549)],
'alpha': [(0.00, 0.0, 0.0),
(0.50, 1.0, 1.0),
(1.00, 1.0, 1.0)]}
return LinearSegmentedColormap('aurora', stops)
def sun_pos(dt=None):
"""This function computes a rough estimate of the coordinates for
the point on the surface of the Earth where the Sun is directly
overhead at the time dt. Precision is down to a few degrees. This
means that the equinoxes (when the sign of the latitude changes)
will be off by a few days.
The function is intended only for visualization. For more precise
calculations consider for example the PyEphem package.
Parameters
----------
dt: datetime
Defaults to datetime.utcnow()
Returns
-------
lat, lng: tuple of floats
Approximate coordinates of the point where the sun is
in zenith at the time dt.
"""
if dt is None:
dt = datetime.utcnow()
axial_tilt = 23.4
ref_solstice = datetime(2016, 6, 21, 22, 22)
days_per_year = 365.2425
seconds_per_day = 24*60*60.0
days_since_ref = (dt - ref_solstice).total_seconds()/seconds_per_day
lat = axial_tilt*np.cos(2*np.pi*days_since_ref/days_per_year)
sec_since_midnight = (dt - datetime(dt.year, dt.month, dt.day)).seconds
lng = -(sec_since_midnight/seconds_per_day - 0.5)*360
return lat, lng
def fill_dark_side(ax, time=None, *args, **kwargs):
"""
Plot a fill on the dark side of the planet (without refraction).
Parameters
----------
ax : matplotlib axes
The axes to plot on.
time : datetime
The time to calculate terminator for. Defaults to datetime.utcnow()
**kwargs :
Passed on to Matplotlib's ax.fill()
"""
lat, lng = sun_pos(time)
pole_lng = lng
if lat > 0:
pole_lat = -90 + lat
central_rot_lng = 180
else:
pole_lat = 90 + lat
central_rot_lng = 0
rotated_pole = ccrs.RotatedPole(pole_latitude=pole_lat,
pole_longitude=pole_lng,
central_rotated_longitude=central_rot_lng)
x = np.empty(360)
y = np.empty(360)
x[:180] = -90
y[:180] = np.arange(-90, 90.)
x[180:] = 90
y[180:] = np.arange(90, -90., -1)
ax.fill(x, y, transform=rotated_pole, **kwargs)
def main():
fig = plt.figure(figsize=[10, 5])
# We choose to plot in an Orthographic projection as it looks natural
# and the distortion is relatively small around the poles where
# the aurora is most likely.
# ax1 for Northern Hemisphere
ax1 = plt.subplot(1, 2, 1, projection=ccrs.Orthographic(0, 90))
# ax2 for Southern Hemisphere
ax2 = plt.subplot(1, 2, 2, projection=ccrs.Orthographic(180, -90))
img, crs, extent, origin, dt = aurora_forecast()
for ax in [ax1, ax2]:
ax.coastlines(zorder=3)
ax.stock_img()
ax.gridlines()
fill_dark_side(ax, time=dt, color='black', alpha=0.75)
ax.imshow(img, vmin=0, vmax=100, transform=crs,
extent=extent, origin=origin, zorder=2,
cmap=aurora_cmap())
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/barbs.py 0000644 0013740 0002103 00000001034 12520135744 020460 0 ustar itpe avd 0000000 0000000 __tags__ = ['Vector data']
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from cartopy.examples.arrows import sample_data
def main():
ax = plt.axes(projection=ccrs.PlateCarree())
ax.set_extent([-90, 75, 10, 60])
ax.stock_img()
ax.coastlines()
x, y, u, v, vector_crs = sample_data(shape=(10, 14))
ax.barbs(x, y, u, v, length=5,
sizes=dict(emptybarb=0.25, spacing=0.2, height=0.5),
linewidth=0.95, transform=vector_crs)
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/eccentric_ellipse.py 0000644 0013740 0002103 00000005037 12700747662 023062 0 ustar itpe avd 0000000 0000000 """
Displaying data on an eccentric ellipse
---------------------------------------
This example demonstrates plotting data on an eccentric ellipse. The data
plotted is a topography map of the asteroid Vesta. The map is actually an
image, which is defined on an equirectangluar projection relative to an
ellipse with a semi-major axis of 285 km and a semi-minor axis of 229 km.
The image is reprojected on-the-fly onto a geostationary projection with
matching eccentricity.
"""
__tags__ = ['Miscellanea']
try:
from urllib2 import urlopen
except ImportError:
from urllib.request import urlopen
from io import BytesIO
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
def vesta_image():
"""
Return an image of Vesta's topography.
Image credit: NASA/JPL-Caltech/UCLA/MPS/DLR/IDA/PSI
Returns
-------
img : numpy array
The pixels of the image in a numpy array.
img_proj : cartopy CRS
The rectangular coordinate system of the image.
img_extent : tuple of floats
The extent of the image ``(x0, y0, x1, y1)`` referenced in
the ``img_proj`` coordinate system.
"""
url = 'https://www.nasa.gov/sites/default/files/pia17037.jpg'
img_handle = BytesIO(urlopen(url).read())
raw_image = Image.open(img_handle)
# The image is extremely high-resolution, which takes a long time to
# plot. Sub-sampling reduces the time taken to plot while not
# significantly altering the integrity of the result.
smaller_image = raw_image.resize([raw_image.size[0] // 10,
raw_image.size[1] // 10])
img = np.asarray(smaller_image)
# We define the semimajor and semiminor axes, but must also tell the
# globe not to use the WGS84 ellipse, which is its default behaviour.
img_globe = ccrs.Globe(semimajor_axis=285000., semiminor_axis=229000.,
ellipse=None)
img_proj = ccrs.PlateCarree(globe=img_globe)
img_extent = (-895353.906273091, 895353.906273091,
447676.9531365455, -447676.9531365455)
return img, img_globe, img_proj, img_extent
def main():
img, globe, crs, extent = vesta_image()
projection = ccrs.Geostationary(globe=globe)
ax = plt.axes(projection=projection)
ax.imshow(img, transform=crs, extent=extent)
plt.gcf().text(.075, .012,
"Image credit: NASA/JPL-Caltech/UCLA/MPS/DLR/IDA/PSI",
bbox={'facecolor': 'w', 'edgecolor': 'k'})
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/effects_of_the_ellipse.py 0000644 0013740 0002103 00000011414 12520135744 024052 0 ustar itpe avd 0000000 0000000 """
The effect of badly referencing an ellipse
------------------------------------------
This example demonstrates the effect of referencing your data to an incorrect
ellipse.
First we define two coordinate systems - one using the World Geodetic System
established in 1984 and the other using a spherical globe. Next we extract
data from the Natural Earth land dataset and convert the Geodetic
coordinates (referenced in WGS84) into the respective coordinate systems
that we have defined. Finally, we plot these datasets onto a map assuming
that they are both referenced to the WGS84 ellipse and compare how the
coastlines are shifted as a result of referencing the incorrect ellipse.
"""
__tags__ = ['Lines and polygons']
import cartopy.crs as ccrs
import cartopy.feature
from cartopy.io.img_tiles import MapQuestOpenAerial
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D as Line
from matplotlib.patheffects import Stroke
import numpy as np
import shapely.geometry as sgeom
from shapely.ops import transform as geom_transform
def transform_fn_factory(target_crs, source_crs):
"""
Return a function which can be used by ``shapely.op.transform``
to transform the coordinate points of a geometry.
The function explicitly *does not* do any interpolation or clever
transformation of the coordinate points, so there is no guarantee
that the resulting geometry would make any sense.
"""
def transform_fn(x, y, z=None):
new_coords = target_crs.transform_points(source_crs,
np.asanyarray(x),
np.asanyarray(y))
return new_coords[:, 0], new_coords[:, 1], new_coords[:, 2]
return transform_fn
def main():
# Define the two coordinate systems with different ellipses.
wgs84 = ccrs.PlateCarree(globe=ccrs.Globe(datum='WGS84',
ellipse='WGS84'))
sphere = ccrs.PlateCarree(globe=ccrs.Globe(datum='WGS84',
ellipse='sphere'))
# Define the coordinate system of the data we have from Natural Earth and
# acquire the 1:10m physical coastline shapefile.
geodetic = ccrs.Geodetic(globe=ccrs.Globe(datum='WGS84'))
dataset = cartopy.feature.NaturalEarthFeature(category='physical',
name='coastline',
scale='10m')
# Create a MapQuest map tiler instance, and use its CRS for the GeoAxes.
tiler = MapQuestOpenAerial()
ax = plt.axes(projection=tiler.crs)
plt.title('The effect of incorrectly referencing the Solomon Islands')
# Pick the area of interest. In our case, roughly the Solomon Islands, and
# get hold of the coastlines for that area.
extent = (155, 163, -11.5, -6)
ax.set_extent(extent, geodetic)
geoms = list(dataset.intersecting_geometries(extent))
# Add the MapQuest aerial imagery at zoom level 7.
ax.add_image(tiler, 7)
# Transform the geodetic coordinates of the coastlines into the two
# projections of differing ellipses.
wgs84_geoms = [geom_transform(transform_fn_factory(wgs84, geodetic),
geom) for geom in geoms]
sphere_geoms = [geom_transform(transform_fn_factory(sphere, geodetic),
geom) for geom in geoms]
# Using these differently referenced geometries, assume that they are
# both referenced to WGS84.
ax.add_geometries(wgs84_geoms, wgs84, edgecolor='white', color='none')
ax.add_geometries(sphere_geoms, wgs84, edgecolor='gray', color='none')
# Create a legend for the coastlines.
legend_artists = [Line([0], [0], color=color, linewidth=3)
for color in ('white', 'gray')]
legend_texts = ['Correct ellipse\n(WGS84)', 'Incorrect ellipse\n(sphere)']
legend = plt.legend(legend_artists, legend_texts, fancybox=True,
loc='lower left', framealpha=0.75)
legend.legendPatch.set_facecolor('wheat')
# Create an inset GeoAxes showing the location of the Solomon Islands.
sub_ax = plt.axes([0.7, 0.625, 0.2, 0.2], projection=ccrs.PlateCarree())
sub_ax.set_extent([110, 180, -50, 10], geodetic)
# Make a nice border around the inset axes.
effect = Stroke(linewidth=4, foreground='wheat', alpha=0.5)
sub_ax.outline_patch.set_path_effects([effect])
# Add the land, coastlines and the extent of the Solomon Islands.
sub_ax.add_feature(cartopy.feature.LAND)
sub_ax.coastlines()
extent_box = sgeom.box(extent[0], extent[2], extent[1], extent[3])
sub_ax.add_geometries([extent_box], ccrs.PlateCarree(), color='none',
edgecolor='blue', linewidth=2)
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/eyja_volcano.py 0000755 0013740 0002103 00000003402 12520135744 022044 0 ustar itpe avd 0000000 0000000 # -*- coding: utf-8 -*-
"""
Map tile acquisition
--------------------
Demonstrates cartopy's ability to draw map tiles which are downloaded on
demand from the MapQuest tile server. Internally these tiles are then combined
into a single image and displayed in the cartopy GeoAxes.
"""
__tags__ = ["Scalar data"]
import matplotlib.pyplot as plt
from matplotlib.transforms import offset_copy
import cartopy.crs as ccrs
import cartopy.io.img_tiles as cimgt
def main():
# Create a MapQuest open aerial instance.
map_quest_aerial = cimgt.MapQuestOpenAerial()
# Create a GeoAxes in the tile's projection.
ax = plt.axes(projection=map_quest_aerial.crs)
# Limit the extent of the map to a small longitude/latitude range.
ax.set_extent([-22, -15, 63, 65])
# Add the MapQuest data at zoom level 8.
ax.add_image(map_quest_aerial, 8)
# Add a marker for the Eyjafjallajökull volcano.
plt.plot(-19.613333, 63.62, marker='o', color='yellow', markersize=12,
alpha=0.7, transform=ccrs.Geodetic())
# Use the cartopy interface to create a matplotlib transform object
# for the Geodetic coordinate system. We will use this along with
# matplotlib's offset_copy function to define a coordinate system which
# translates the text by 25 pixels to the left.
geodetic_transform = ccrs.Geodetic()._as_mpl_transform(ax)
text_transform = offset_copy(geodetic_transform, units='dots', x=-25)
# Add text 25 pixels to the left of the volcano.
plt.text(-19.613333, 63.62, u'Eyjafjallajökull',
verticalalignment='center', horizontalalignment='right',
transform=text_transform,
bbox=dict(facecolor='wheat', alpha=0.5, boxstyle='round'))
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/favicon.py 0000644 0013740 0002103 00000003107 12545000115 021005 0 ustar itpe avd 0000000 0000000 __tags__ = ['Miscellanea']
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import matplotlib.textpath
import matplotlib.patches
from matplotlib.font_manager import FontProperties
import numpy as np
def main():
plt.figure(figsize=[8, 8])
ax = plt.axes(projection=ccrs.SouthPolarStereo())
ax.coastlines()
ax.gridlines()
im = ax.stock_img()
def on_draw(event=None):
"""
Hooks into matplotlib's event mechanism to define the clip path of the
background image.
"""
# Clip the image to the current background boundary.
im.set_clip_path(ax.background_patch.get_path(),
transform=ax.background_patch.get_transform())
# Register the on_draw method and call it once now.
plt.gcf().canvas.mpl_connect('draw_event', on_draw)
on_draw()
# Generate a matplotlib path representing the character "C".
fp = FontProperties(family='Bitstream Vera Sans', weight='bold')
logo_path = matplotlib.textpath.TextPath((-4.5e7, -3.7e7),
'C', size=1, prop=fp)
# Scale the letter up to an appropriate X and Y scale.
logo_path._vertices *= np.array([103250000, 103250000])
# Add the path as a patch, drawing black outlines around the text.
patch = matplotlib.patches.PathPatch(logo_path, facecolor='white',
edgecolor='black', linewidth=10,
transform=ccrs.SouthPolarStereo())
ax.add_patch(patch)
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/feature_creation.py 0000644 0013740 0002103 00000002175 12700747662 022725 0 ustar itpe avd 0000000 0000000 __tags__ = ['Lines and polygons']
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from matplotlib.offsetbox import AnchoredText
def main():
ax = plt.axes(projection=ccrs.PlateCarree())
ax.set_extent([80, 170, -45, 30])
# Put a background image on for nice sea rendering.
ax.stock_img()
# Create a feature for States/Admin 1 regions at 1:50m from Natural Earth
states_provinces = cfeature.NaturalEarthFeature(
category='cultural',
name='admin_1_states_provinces_lines',
scale='50m',
facecolor='none')
SOURCE = 'Natural Earth'
LICENSE = 'public domain'
ax.add_feature(cfeature.LAND)
ax.add_feature(cfeature.COASTLINE)
ax.add_feature(states_provinces, edgecolor='gray')
# Add a text annotation for the license information to the
# the bottom right corner.
text = AnchoredText(r'$\mathcircled{{c}}$ {}; license: {}'
''.format(SOURCE, LICENSE),
loc=4, prop={'size': 12}, frameon=True)
ax.add_artist(text)
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/features.py 0000644 0013740 0002103 00000001024 12520135744 021204 0 ustar itpe avd 0000000 0000000 __tags__ = ['Lines and polygons']
import cartopy
import matplotlib.pyplot as plt
def main():
ax = plt.axes(projection=cartopy.crs.PlateCarree())
ax.add_feature(cartopy.feature.LAND)
ax.add_feature(cartopy.feature.OCEAN)
ax.add_feature(cartopy.feature.COASTLINE)
ax.add_feature(cartopy.feature.BORDERS, linestyle=':')
ax.add_feature(cartopy.feature.LAKES, alpha=0.5)
ax.add_feature(cartopy.feature.RIVERS)
ax.set_extent([-20, 60, -40, 40])
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/geostationary.py 0000644 0013740 0002103 00000003613 12520135744 022264 0 ustar itpe avd 0000000 0000000 """
Reprojecting images from a Geostationary projection
---------------------------------------------------
This example demonstrates Cartopy's ability to project images into the desired
projection on-the-fly. The image itself is retrieved from a URL and is loaded
directly into memory without storing it intermediately into a file. It
represents pre-processed data from Moderate-Resolution Imaging
Spectroradiometer (MODIS) which has been put into an image in the data's
native Geostationary coordinate system - it is then projected by cartopy
into a global Miller map.
"""
__tags__ = ["Scalar data"]
try:
from urllib2 import urlopen
except ImportError:
from urllib.request import urlopen
from io import BytesIO
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
def geos_image():
"""
Return a specific MODIS image by retrieving it from a github gist URL.
Returns
-------
img : numpy array
The pixels of the image in a numpy array.
img_proj : cartopy CRS
The rectangular coordinate system of the image.
img_extent : tuple of floats
The extent of the image ``(x0, y0, x1, y1)`` referenced in
the ``img_proj`` coordinate system.
origin : str
The origin of the image to be passed through to matplotlib's imshow.
"""
url = ('https://gist.github.com/pelson/5871263/raw/'
'EIDA50_201211061300_clip2.png')
img_handle = BytesIO(urlopen(url).read())
img = plt.imread(img_handle)
img_proj = ccrs.Geostationary(satellite_height=35786000)
img_extent = (-5500000, 5500000, -5500000, 5500000)
return img, img_proj, img_extent, 'upper'
def main():
ax = plt.axes(projection=ccrs.Miller())
ax.coastlines()
ax.set_global()
img, crs, extent, origin = geos_image()
plt.imshow(img, transform=crs, extent=extent, origin=origin, cmap='gray')
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/global_map.py 0000755 0013740 0002103 00000001074 12520135744 021473 0 ustar itpe avd 0000000 0000000 __tags__ = ['Lines and polygons']
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
def main():
ax = plt.axes(projection=ccrs.Robinson())
# make the map global rather than have it zoom in to
# the extents of any plotted data
ax.set_global()
ax.stock_img()
ax.coastlines()
plt.plot(-0.08, 51.53, 'o', transform=ccrs.PlateCarree())
plt.plot([-0.08, 132], [51.53, 43.17], transform=ccrs.PlateCarree())
plt.plot([-0.08, 132], [51.53, 43.17], transform=ccrs.Geodetic())
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/hurricane_katrina.py 0000755 0013740 0002103 00000006230 12700747662 023076 0 ustar itpe avd 0000000 0000000 __tags__ = ['Lines and polygons']
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import shapely.geometry as sgeom
import cartopy.crs as ccrs
import cartopy.io.shapereader as shpreader
def sample_data():
"""
Returns a list of latitudes and a list of longitudes (lons, lats)
for Hurricane Katrina (2005).
The data was originally sourced from the HURDAT2 dataset from AOML/NOAA:
http://www.aoml.noaa.gov/hrd/hurdat/newhurdat-all.html on 14th Dec 2012.
"""
lons = [-75.1, -75.7, -76.2, -76.5, -76.9, -77.7, -78.4, -79.0,
-79.6, -80.1, -80.3, -81.3, -82.0, -82.6, -83.3, -84.0,
-84.7, -85.3, -85.9, -86.7, -87.7, -88.6, -89.2, -89.6,
-89.6, -89.6, -89.6, -89.6, -89.1, -88.6, -88.0, -87.0,
-85.3, -82.9]
lats = [23.1, 23.4, 23.8, 24.5, 25.4, 26.0, 26.1, 26.2, 26.2, 26.0,
25.9, 25.4, 25.1, 24.9, 24.6, 24.4, 24.4, 24.5, 24.8, 25.2,
25.7, 26.3, 27.2, 28.2, 29.3, 29.5, 30.2, 31.1, 32.6, 34.1,
35.6, 37.0, 38.6, 40.1]
return lons, lats
def main():
ax = plt.axes([0, 0, 1, 1],
projection=ccrs.LambertConformal())
ax.set_extent([-125, -66.5, 20, 50], ccrs.Geodetic())
shapename = 'admin_1_states_provinces_lakes_shp'
states_shp = shpreader.natural_earth(resolution='110m',
category='cultural', name=shapename)
lons, lats = sample_data()
# to get the effect of having just the states without a map "background"
# turn off the outline and background patches
ax.background_patch.set_visible(False)
ax.outline_patch.set_visible(False)
plt.title('US States which intersect the track '
'of Hurricane Katrina (2005)')
# turn the lons and lats into a shapely LineString
track = sgeom.LineString(zip(lons, lats))
# buffer the linestring by two degrees (note: this is a non-physical
# distance)
track_buffer = track.buffer(2)
for state in shpreader.Reader(states_shp).geometries():
# pick a default color for the land with a black outline,
# this will change if the storm intersects with our track
facecolor = [0.9375, 0.9375, 0.859375]
edgecolor = 'black'
if state.intersects(track):
facecolor = 'red'
elif state.intersects(track_buffer):
facecolor = '#FF7E00'
ax.add_geometries([state], ccrs.PlateCarree(),
facecolor=facecolor, edgecolor=edgecolor)
ax.add_geometries([track_buffer], ccrs.PlateCarree(),
facecolor='#C8A2C8', alpha=0.5)
ax.add_geometries([track], ccrs.PlateCarree(),
facecolor='none')
# make two proxy artists to add to a legend
direct_hit = mpatches.Rectangle((0, 0), 1, 1, facecolor="red")
within_2_deg = mpatches.Rectangle((0, 0), 1, 1, facecolor="#FF7E00")
labels = ['State directly intersects\nwith track',
'State is within \n2 degrees of track']
plt.legend([direct_hit, within_2_deg], labels,
loc='lower left', bbox_to_anchor=(0.025, -0.1), fancybox=True)
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/image_tiles.py 0000644 0013740 0002103 00000000756 12520135744 021663 0 ustar itpe avd 0000000 0000000 __tags__ = ['Web services']
"""
Web tile imagery
----------------
This example demonstrates how imagery from a tile
providing web service can be accessed.
"""
import matplotlib.pyplot as plt
from cartopy.io.img_tiles import StamenTerrain
def main():
tiler = StamenTerrain()
mercator = tiler.crs
ax = plt.axes(projection=mercator)
ax.set_extent([-90, -73, 22, 34])
ax.add_image(tiler, 6)
ax.coastlines('10m')
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/logo.py 0000644 0013740 0002103 00000002665 12545000115 020330 0 ustar itpe avd 0000000 0000000 __tags__ = ['Miscellanea']
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import matplotlib.textpath
import matplotlib.patches
from matplotlib.font_manager import FontProperties
import numpy as np
def main():
plt.figure(figsize=[12, 6])
ax = plt.axes(projection=ccrs.Robinson())
ax.coastlines()
ax.gridlines()
# generate a matplotlib path representing the word "cartopy"
fp = FontProperties(family='Bitstream Vera Sans', weight='bold')
logo_path = matplotlib.textpath.TextPath((-175, -35), 'cartopy',
size=1, prop=fp)
# scale the letters up to sensible longitude and latitude sizes
logo_path._vertices *= np.array([80, 160])
# add a background image
im = ax.stock_img()
# clip the image according to the logo_path. mpl v1.2.0 does not support
# the transform API that cartopy makes use of, so we have to convert the
# projection into a transform manually
plate_carree_transform = ccrs.PlateCarree()._as_mpl_transform(ax)
im.set_clip_path(logo_path, transform=plate_carree_transform)
# add the path as a patch, drawing black outlines around the text
patch = matplotlib.patches.PathPatch(logo_path,
facecolor='none', edgecolor='black',
transform=ccrs.PlateCarree())
ax.add_patch(patch)
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/regridding_arrows.py 0000644 0013740 0002103 00000003145 12520135744 023107 0 ustar itpe avd 0000000 0000000 """
Regridding vectors with quiver
------------------------------
This example demonstrates the regridding functionality in quiver (there exists
equivalent functionality in :meth:`cartopy.mpl.geoaxes.GeoAxes.barbs`).
Regridding can be an effective way of visualising a vector field, particularly
if the data is dense or warped.
"""
__tags__ = ['Vector data']
import matplotlib.pyplot as plt
import numpy as np
import cartopy.crs as ccrs
def sample_data(shape=(20, 30)):
"""
Returns ``(x, y, u, v, crs)`` of some vector data
computed mathematically. The returned CRS will be a North Polar
Stereographic projection, meaning that the vectors will be unevenly
spaced in a PlateCarree projection.
"""
crs = ccrs.NorthPolarStereo()
scale = 1e7
x = np.linspace(-scale, scale, shape[1])
y = np.linspace(-scale, scale, shape[0])
x2d, y2d = np.meshgrid(x, y)
u = 10 * np.cos(2 * x2d / scale + 3 * y2d / scale)
v = 20 * np.cos(6 * x2d / scale)
return x, y, u, v, crs
def main():
plt.figure(figsize=(8, 10))
x, y, u, v, vector_crs = sample_data(shape=(50, 50))
ax1 = plt.subplot(2, 1, 1, projection=ccrs.PlateCarree())
ax1.coastlines('50m')
ax1.set_extent([-45, 55, 20, 80], ccrs.PlateCarree())
ax1.quiver(x, y, u, v, transform=vector_crs)
ax2 = plt.subplot(2, 1, 2, projection=ccrs.PlateCarree())
plt.title('The same vector field regridded')
ax2.coastlines('50m')
ax2.set_extent([-45, 55, 20, 80], ccrs.PlateCarree())
ax2.quiver(x, y, u, v, transform=vector_crs, regrid_shape=20)
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/rotated_pole.py 0000755 0013740 0002103 00000002060 12520135744 022053 0 ustar itpe avd 0000000 0000000 """
Rotated pole boxes
------------------
This example demonstrates the way a box is warped when it is defined
in a rotated pole coordinate system.
Try changing the ``box_top`` to ``44``, ``46`` and ``75`` to see the effect
that including the pole in the polygon has.
"""
__tags__ = ['Lines and polygons']
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
def main():
rotated_pole = ccrs.RotatedPole(pole_latitude=45, pole_longitude=180)
box_top = 45
x, y = [-44, -44, 45, 45, -44], [-45, box_top, box_top, -45, -45]
ax = plt.subplot(211, projection=rotated_pole)
ax.stock_img()
ax.coastlines()
ax.plot(x, y, marker='o', transform=rotated_pole)
ax.fill(x, y, color='coral', transform=rotated_pole, alpha=0.4)
ax.gridlines()
ax = plt.subplot(212, projection=ccrs.PlateCarree())
ax.stock_img()
ax.coastlines()
ax.plot(x, y, marker='o', transform=rotated_pole)
ax.fill(x, y, transform=rotated_pole, color='coral', alpha=0.4)
ax.gridlines()
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/srtm_shading.py 0000644 0013740 0002103 00000003044 12700747662 022064 0 ustar itpe avd 0000000 0000000 __tags__ = ['Scalar data']
"""
This example illustrates the automatic download of STRM data, and adding of
shading to create a so-called "Shaded Relief SRTM".
Originally contributed by Thomas Lecocq (http://geophysique.be).
"""
import cartopy.crs as ccrs
from cartopy.io import srtm
import matplotlib.pyplot as plt
from cartopy.io import PostprocessedRasterSource, LocatedImage
from cartopy.io.srtm import SRTM3Source, SRTM1Source
def shade(located_elevations):
"""
Given an array of elevations in a LocatedImage, add a relief (shadows) to
give a realistic 3d appearance.
"""
new_img = srtm.add_shading(located_elevations.image,
azimuth=135, altitude=15)
return LocatedImage(new_img, located_elevations.extent)
def plot(Source, name):
plt.figure()
ax = plt.axes(projection=ccrs.PlateCarree())
# Define a raster source which uses the SRTM data and applies the
# shade function when the data is retrieved.
shaded_srtm = PostprocessedRasterSource(Source(), shade)
# Add the shaded SRTM source to our map with a grayscale colormap.
ax.add_raster(shaded_srtm, cmap='Greys')
# This data is high resolution, so pick a small area which has some
# interesting orography.
ax.set_extent([12, 13, 47, 48])
plt.title(name + " Shaded Relief Map")
gl = ax.gridlines(draw_labels=True)
gl.xlabels_top = False
gl.ylabels_left = False
def main():
plot(SRTM3Source, 'SRTM3')
plot(SRTM1Source, 'SRTM1')
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/star_shaped_boundary.py 0000644 0013740 0002103 00000001732 12520135744 023574 0 ustar itpe avd 0000000 0000000 """
Modifying the boundary/neatline of a map in cartopy
---------------------------------------------------
This example demonstrates how to modify the boundary/neatline
of an axes. We construct a star with coordinates in a Plate Carree
coordinate system, and use the star as the outline of the map.
Notice how changing the projection of the map represents a *projected*
star shaped boundary.
"""
__tags__ = ['Miscellanea']
import matplotlib.path as mpath
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
def main():
ax = plt.axes([0, 0, 1, 1], projection=ccrs.PlateCarree())
ax.coastlines()
# Construct a star in longitudes and latitudes.
star_path = mpath.Path.unit_regular_star(5, 0.5)
star_path = mpath.Path(star_path.vertices.copy() * 80,
star_path.codes.copy())
# Use the star as the boundary.
ax.set_boundary(star_path, transform=ccrs.PlateCarree())
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/streamplot.py 0000644 0013740 0002103 00000001011 12520135744 021554 0 ustar itpe avd 0000000 0000000 __tags__ = ['Vector data']
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from cartopy.examples.arrows import sample_data
def main():
ax = plt.axes(projection=ccrs.PlateCarree())
ax.set_extent([-90, 75, 10, 60])
ax.coastlines()
x, y, u, v, vector_crs = sample_data(shape=(80, 100))
magnitude = (u ** 2 + v ** 2) ** 0.5
ax.streamplot(x, y, u, v, transform=vector_crs,
linewidth=2, density=2, color=magnitude)
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/tick_labels.py 0000644 0013740 0002103 00000003274 12520135744 021653 0 ustar itpe avd 0000000 0000000 __tags__ = ['Miscellanea']
"""
This example demonstrates adding tick labels to maps on rectangular
projections using special tick formatters.
"""
import cartopy.crs as ccrs
from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter
import matplotlib.pyplot as plt
def main():
plt.figure(figsize=(8, 10))
# Label axes of a Plate Carree projection with a central longitude of 180:
ax1 = plt.subplot(211, projection=ccrs.PlateCarree(central_longitude=180))
ax1.set_global()
ax1.coastlines()
ax1.set_xticks([0, 60, 120, 180, 240, 300, 360], crs=ccrs.PlateCarree())
ax1.set_yticks([-90, -60, -30, 0, 30, 60, 90], crs=ccrs.PlateCarree())
lon_formatter = LongitudeFormatter(zero_direction_label=True)
lat_formatter = LatitudeFormatter()
ax1.xaxis.set_major_formatter(lon_formatter)
ax1.yaxis.set_major_formatter(lat_formatter)
# Label axes of a Mercator projection without degree symbols in the labels
# and formatting labels to include 1 decimal place:
ax2 = plt.subplot(212, projection=ccrs.Mercator())
ax2.set_global()
ax2.coastlines()
ax2.set_xticks([-180, -120, -60, 0, 60, 120, 180], crs=ccrs.PlateCarree())
ax2.set_yticks([-78.5, -60, -25.5, 25.5, 60, 80], crs=ccrs.PlateCarree())
lon_formatter = LongitudeFormatter(number_format='.1f',
degree_symbol='',
dateline_direction_label=True)
lat_formatter = LatitudeFormatter(number_format='.1f',
degree_symbol='')
ax2.xaxis.set_major_formatter(lon_formatter)
ax2.yaxis.set_major_formatter(lat_formatter)
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/tissot.py 0000644 0013740 0002103 00000000575 12700747662 020735 0 ustar itpe avd 0000000 0000000 import matplotlib.pyplot as plt
import cartopy.crs as ccrs
def main():
ax = plt.axes(projection=ccrs.PlateCarree())
# make the map global rather than have it zoom in to
# the extents of any plotted data
ax.set_global()
ax.stock_img()
ax.coastlines()
ax.tissot(facecolor='orange', alpha=0.4)
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/tube_stations.py 0000644 0013740 0002103 00000004471 12520135744 022262 0 ustar itpe avd 0000000 0000000 __tags__ = ['Miscellanea']
"""
Produces a map showing London Underground station locations with high
resolution background imagery provided by MapQuest.
"""
from matplotlib.path import Path
import matplotlib.pyplot as plt
import numpy as np
import cartopy.crs as ccrs
from cartopy.io.img_tiles import MapQuestOSM
def tube_locations():
"""
Returns an (n, 2) array of selected London Tube locations in Ordnance
Survey GB coordinates.
Source: http://www.doogal.co.uk/london_stations.php
"""
return np.array([[531738., 180890.], [532379., 179734.],
[531096., 181642.], [530234., 180492.],
[531688., 181150.], [530242., 180982.],
[531940., 179144.], [530406., 180380.],
[529012., 180283.], [530553., 181488.],
[531165., 179489.], [529987., 180812.],
[532347., 180962.], [529102., 181227.],
[529612., 180625.], [531566., 180025.],
[529629., 179503.], [532105., 181261.],
[530995., 180810.], [529774., 181354.],
[528941., 179131.], [531050., 179933.],
[530240., 179718.]])
def main():
imagery = MapQuestOSM()
ax = plt.axes(projection=imagery.crs)
ax.set_extent((-0.14, -0.1, 51.495, 51.515))
# Construct concentric circles and a rectangle,
# suitable for a London Underground logo.
theta = np.linspace(0, 2 * np.pi, 100)
circle_verts = np.vstack([np.sin(theta), np.cos(theta)]).T
concentric_circle = Path.make_compound_path(Path(circle_verts[::-1]),
Path(circle_verts * 0.6))
rectangle = Path([[-1.1, -0.2], [1, -0.2], [1, 0.3], [-1.1, 0.3]])
# Add the imagery to the map.
ax.add_image(imagery, 14)
# Plot the locations twice, first with the red concentric circles,
# then with the blue rectangle.
xs, ys = tube_locations().T
plt.plot(xs, ys, transform=ccrs.OSGB(),
marker=concentric_circle, color='red', markersize=9,
linestyle='')
plt.plot(xs, ys, transform=ccrs.OSGB(),
marker=rectangle, color='blue', markersize=11,
linestyle='')
plt.title('London underground locations')
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/un_flag.py 0000644 0013740 0002103 00000016242 12700747662 021021 0 ustar itpe avd 0000000 0000000 __tags__ = ['Miscellanea']
import cartopy.crs as ccrs
import cartopy.feature
import matplotlib.pyplot as plt
from matplotlib.patches import PathPatch
import matplotlib.path
import matplotlib.ticker
from matplotlib.transforms import BboxTransform, Bbox
import numpy as np
# When drawing the flag, we can either use white filled land, or be a little
# more fancy and use the Natural Earth shaded relief imagery.
filled_land = True
def olive_path():
"""
Returns a matplotlib path representing a single olive branch from the
UN Flag. The path coordinates were extracted from the SVG at
https://commons.wikimedia.org/wiki/File:Flag_of_the_United_Nations.svg.
"""
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,
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,
211, 224, 219, 230, 220, 212, 207, 198, 195, 176, 197, 220, 239,
259, 277, 280, 663, 295, 293, 264, 250, 247, 244, 240, 240, 243,
244, 249, 251, 250, 248, 242, 245, 233, 236, 230, 228, 224, 222,
234, 249, 262, 275, 285, 291, 295, 296, 295, 663, 294, 293, 292,
289, 294, 277, 271, 269, 268, 265, 264, 264, 264, 272, 260, 248,
245, 243, 242, 240, 243, 245, 247, 252, 256, 259, 258, 257, 258,
267, 285, 290, 294, 297, 294, 663, 285, 285, 277, 266, 265, 265,
265, 277, 266, 268, 269, 269, 269, 268, 268, 267, 267, 264, 248,
235, 232, 229, 228, 229, 232, 236, 246, 266, 269, 271, 285, 285,
663, 252, 245, 238, 230, 246, 245, 250, 252, 255, 256, 256, 253,
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,
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,
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,
171, 184, 202, 216, 233, 249, 595, 276, 296, 312, 327, 327, 327,
327, 308, 284, 262, 240, 240, 239, 239, 242, 244, 247, 265, 277,
290, 293, 296, 300, 291, 282, 274, 253, 236, 213, 235, 252, 276,
595, 342, 349, 355, 357, 346, 326, 309, 303, 297, 291, 290, 297,
304, 310, 321, 327, 343, 321, 305, 292, 286, 278, 270, 276, 281,
287, 306, 328, 342, 595, 379, 369, 355, 343, 333, 326, 318, 328,
340, 349, 366, 373, 379, 595]]).T
olives_codes = np.array([1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 79, 1, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 79, 1, 4, 4, 4, 4, 4, 4, 2, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 79, 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 79, 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 4,
4, 4, 4, 4, 4, 79, 1, 4, 4, 4, 4, 4, 4, 4, 4, 4,
2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 79, 1, 4, 4, 4, 4, 4, 4, 4, 4,
4, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 79, 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 79], dtype=np.uint8)
return matplotlib.path.Path(olives_verts, olives_codes)
def main():
blue = '#4b92db'
# We're drawing a flag with a 3:5 aspect ratio.
fig = plt.figure(figsize=[10, 6], facecolor=blue)
# Put a blue background on the figure.
blue_background = PathPatch(matplotlib.path.Path.unit_rectangle(),
transform=fig.transFigure, color=blue,
zorder=-1)
fig.patches.append(blue_background)
# Set up the Azimuthal Equidistant and Plate Carree projections
# for later use.
az_eq = ccrs.AzimuthalEquidistant(central_latitude=90)
pc = ccrs.PlateCarree()
# Pick a suitable location for the map (which is in an Azimuthal
# Equidistant projection).
ax = plt.axes([0.25, 0.24, 0.5, 0.54], projection=az_eq)
# The background patch and outline patch are not needed in this example.
ax.background_patch.set_facecolor('none')
ax.outline_patch.set_edgecolor('none')
# We want the map to go down to -60 degrees latitude.
ax.set_extent([-180, 180, -60, 90], ccrs.PlateCarree())
# Importantly, we want the axes to be circular at the -60 latitude
# rather than cartopy's default behaviour of zooming in and becoming
# square.
_, patch_radius = az_eq.transform_point(0, -60, pc)
circular_path = matplotlib.path.Path.circle(0, patch_radius)
ax.set_boundary(circular_path)
if filled_land:
ax.add_feature(
cartopy.feature.LAND, facecolor='white', edgecolor='none')
else:
ax.stock_img()
gl = ax.gridlines(crs=pc, linewidth=3, color='white', linestyle='-')
# Meridians every 45 degrees, and 5 parallels.
gl.xlocator = matplotlib.ticker.FixedLocator(np.arange(0, 361, 45))
parallels = np.linspace(-60, 70, 5, endpoint=True)
gl.ylocator = matplotlib.ticker.FixedLocator(parallels)
# Now add the olive branches around the axes. We do this in normalised
# figure coordinates
olive_leaf = olive_path()
olives_bbox = Bbox.null()
olives_bbox.update_from_path(olive_leaf)
# The first olive branch goes from left to right.
olive1_axes_bbox = Bbox([[0.45, 0.15], [0.725, 0.75]])
olive1_trans = BboxTransform(olives_bbox, olive1_axes_bbox)
# THe second olive branch goes from right to left (mirroring the first).
olive2_axes_bbox = Bbox([[0.55, 0.15], [0.275, 0.75]])
olive2_trans = BboxTransform(olives_bbox, olive2_axes_bbox)
olive1 = PathPatch(olive_leaf, facecolor='white', edgecolor='none',
transform=olive1_trans + fig.transFigure)
olive2 = PathPatch(olive_leaf, facecolor='white', edgecolor='none',
transform=olive2_trans + fig.transFigure)
fig.patches.append(olive1)
fig.patches.append(olive2)
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/waves.py 0000755 0013740 0002103 00000001626 12520135744 020526 0 ustar itpe avd 0000000 0000000 __tags__ = ['Scalar data']
import matplotlib.pyplot as plt
import numpy as np
import cartopy.crs as ccrs
def sample_data(shape=(73, 145)):
"""Returns ``lons``, ``lats`` and ``data`` of some fake data."""
nlats, nlons = shape
lats = np.linspace(-np.pi / 2, np.pi / 2, nlats)
lons = np.linspace(0, 2 * np.pi, nlons)
lons, lats = np.meshgrid(lons, lats)
wave = 0.75 * (np.sin(2 * lats) ** 8) * np.cos(4 * lons)
mean = 0.5 * np.cos(2 * lats) * ((np.sin(2 * lats)) ** 2 + 2)
lats = np.rad2deg(lats)
lons = np.rad2deg(lons)
data = wave + mean
return lons, lats, data
def main():
ax = plt.axes(projection=ccrs.Mollweide())
lons, lats, data = sample_data()
ax.contourf(lons, lats, data,
transform=ccrs.PlateCarree(),
cmap='spectral')
ax.coastlines()
ax.set_global()
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/wms.py 0000644 0013740 0002103 00000001043 12520135744 020175 0 ustar itpe avd 0000000 0000000 __tags__ = ['Web services']
"""
Interactive WMS (Web Map Service)
---------------------------------
This example demonstrates the interactive pan and zoom capability
supported by an OGC web services Web Map Service (WMS) aware axes.
"""
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
def main():
ax = plt.axes(projection=ccrs.InterruptedGoodeHomolosine())
ax.coastlines()
ax.add_wms(wms='http://vmap0.tiles.osgeo.org/wms/vmap0',
layers=['basic'])
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/examples/wmts.py 0000644 0013740 0002103 00000002017 12700747662 020373 0 ustar itpe avd 0000000 0000000 __tags__ = ['Web services']
"""
Interactive WMTS (Web Map Tile Service)
---------------------------------------
This example demonstrates the interactive pan and zoom capability
supported by an OGC web services Web Map Tile Service (WMTS) aware axes.
The example WMTS layer is a single composite of data sampled over nine days
in April 2012 and thirteen days in October 2012 showing the Earth at night.
It does not vary over time.
The imagery was collected by the Suomi National Polar-orbiting Partnership
(Suomi NPP) weather satellite operated by the United States National Oceanic
and Atmospheric Administration (NOAA).
"""
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
def main():
url = 'https://map1c.vis.earthdata.nasa.gov/wmts-geo/wmts.cgi'
layer = 'VIIRS_CityLights_2012'
ax = plt.axes(projection=ccrs.PlateCarree())
ax.add_wmts(url, layer)
ax.set_extent((-15, 25, 35, 60))
plt.title('Suomi NPP Earth at night April/October 2012')
plt.show()
if __name__ == '__main__':
main()
Cartopy-0.14.2/lib/cartopy/geodesic/ 0000755 0013740 0002103 00000000000 12706121712 016757 5 ustar itpe avd 0000000 0000000 Cartopy-0.14.2/lib/cartopy/geodesic/__init__.py 0000644 0013740 0002103 00000001517 12700747662 021110 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2015 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
from __future__ import (absolute_import, division, print_function)
from cartopy.geodesic._geodesic import Geodesic
Cartopy-0.14.2/lib/cartopy/geodesic/_geodesic.pyx 0000644 0013740 0002103 00000020704 12700747662 021461 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2015 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
"""
This module defines the Geodesic class which can interface with the Proj.4.
geodesic functions.
"""
from cpython.mem cimport PyMem_Malloc
from cpython.mem cimport PyMem_Realloc
from cpython.mem cimport PyMem_Free
import numpy as np
cimport numpy as np
cimport cython
from cython.parallel cimport prange
cdef extern from "geodesic.h":
# External imports of Proj4.9 functions
cdef struct geod_geodesic:
pass
ctypedef geod_geodesic* geodesic_t
void geod_init(geodesic_t, double, double)
void geod_direct(geodesic_t, double, double, double, double,
double*, double*, double*) nogil
void geod_inverse(geodesic_t, double, double, double, double,
double*, double*, double*) nogil
cdef class Geodesic:
"""
Defines an ellipsoid on which to solve geodesic problems.
"""
cdef geod_geodesic* geod
cdef double radius
cdef double flattening
def __cinit__(self, radius=6378137.0, flattening=1/298.257223563):
"""
Create an ellipsoid with a given radius and flattening.
Kwargs:
* radius - Equatorial radius (metres). Defaults to the WGS84
semimajor axis (6378137.0 metres).
* flattening - Flattening of ellipsoid.
Setting flattening = 0 gives a sphere. Negative
flattening gives a prolate ellipsoid. If
flattening > 1, set flattening to 1/flattening.
Defaults to the WGS84 flattening (1/298.257223563).
"""
# allocate some memory (filled with random data)
self.geod = PyMem_Malloc(sizeof(geod_geodesic))
if not self.geod:
raise MemoryError()
geod_init(self.geod, radius, flattening)
self.radius = radius
self.flattening = flattening
def __dealloc__(self):
# Free allocated memory.
PyMem_Free(self.geod)
def __str__(self):
fmt = self.radius, 1/self.flattening
return '' % fmt
@cython.boundscheck(False)
def direct(self, points, azimuths, distances):
"""
Solve the direct geodesic problem where the length of the geodesic is
specified in terms of distance.
Can accept and broadcast length 1 arguments. For example, given a
single start point and distance, an array of different azimuths can be
supplied to locate multiple endpoints.
Args:
* points - An n (or 1) by 2 numpy.ndarray, list or tuple of lon-lat
points.
The starting point(s) from which to travel.
* azimuths - A length n (or 1) numpy.ndarray or list of azimuth
values (degrees).
* distances - A length n (or 1) numpy.ndarray or list of distances
values (metres).
Returns:
An n by 3 np.ndarray of lons, lats, and forward azimuths of the
located endpoint(s).
"""
cdef int n_points, i
cdef double[:, :] pts, orig_pts
cdef double[:] azims, dists
# Create numpy arrays from inputs, and ensure correct shape. Note:
# reshape(-1) returns a 1D array from a 0 dimensional array as required
# for broadcasting.
pts = np.array(points, dtype=np.float64).reshape((-1, 2))
azims = np.array(azimuths, dtype=np.float64).reshape(-1)
dists = np.array(distances, dtype=np.float64).reshape(-1)
sizes = [pts.shape[0], azims.size, dists.size]
n_points = max(sizes)
if not all(size in [1, n_points] for size in sizes):
raise ValueError("Inputs must have common length n or length one.")
# Broadcast any length 1 arrays to the correct size.
if pts.shape[0] == 1:
orig_pts = pts
pts = np.empty([n_points, 2], dtype=np.float64)
pts[:, :] = orig_pts
if azims.size == 1:
azims = np.repeat(azims, n_points)
if dists.size == 1:
dists = np.repeat(dists, n_points)
cdef double[:, :] return_pts = np.empty((n_points, 3),
dtype=np.float64)
with nogil:
for i in prange(n_points):
geod_direct(self.geod,
pts[i, 1], pts[i, 0], azims[i], dists[i],
&return_pts[i, 1], &return_pts[i, 0],
&return_pts[i, 2])
return return_pts
@cython.boundscheck(False)
def inverse(self, points, endpoints):
"""
Solve the inverse geodesic problem.
Can accept and broadcast length 1 arguments. For example, given a
single start point, an array of different endpoints can be supplied to
find multiple distances.
Args:
* points - An n (or 1) by 2 numpy.ndarray, list or tuple of lon-lat
points.
The starting point(s) from which to travel.
* endpoints - An n (or 1) by 2 numpy.ndarray, list or tuple of
lon-lat points.
The point(s) to travel to.
Returns:
An n by 3 np.ndarray of distances, and the (forward) azimuths of
the start and end points.
"""
cdef int n_points, i
cdef double[:, :] pts, epts, orig_pts
# Create numpy arrays from inputs, and ensure correct shape. Note:
# reshape(-1) returns a 1D array from a 0 dimensional array as required
# for broadcasting.
pts = np.array(points, dtype=np.float64).reshape((-1, 2))
epts = np.array(endpoints, dtype=np.float64).reshape((-1, 2))
sizes = [pts.shape[0], epts.shape[0]]
n_points = max(sizes)
if not all(size in [1, n_points] for size in sizes):
raise ValueError("Inputs must have common length n or length one.")
# Broadcast any length 1 arrays to the correct size.
if pts.shape[0] == 1:
orig_pts = pts
pts = np.empty([n_points, 2], dtype=np.float64)
pts[:, :] = orig_pts
if epts.shape[0] == 1:
orig_pts = epts
epts = np.empty([n_points, 2], dtype=np.float64)
epts[:, :] = orig_pts
cdef double[:, :] results = np.empty((n_points, 3), dtype=np.float64)
with nogil:
for i in prange(n_points):
geod_inverse(self.geod, pts[i, 1], pts[i, 0], epts[i, 1],
epts[i, 0], &results[i, 0], &results[i, 1],
&results[i, 2])
return results
def circle(self, double lon, double lat, double radius, int n_samples=180,
endpoint=False):
"""
Find a geodesic circle of given radius at a given point.
Args:
* lon - Longitude coordinate of the centre.
* lat - Latitude coordinate of the centre.
* radius - The radius of the circle (metres).
Kwargs:
* n_samples - Integer number of sample points of circle.
* endpoint - Boolean for whether to repeat endpoint at the end of
returned array.
Returns:
An n_samples by 2 np.ndarray of evenly spaced lon-lat points on the
circle.
"""
cdef int i
# Put the input arguments into c-typed values.
cdef double[:, :] center = np.array([lon, lat]).reshape((1, 2))
cdef double[:] radius_m = np.asarray(radius).reshape(1)
azimuths = np.linspace(360., 0., n_samples,
endpoint=endpoint).astype(np.double)
return self.direct(center, azimuths, radius_m)[:, 0:2]
Cartopy-0.14.2/lib/cartopy/io/ 0000755 0013740 0002103 00000000000 12706121712 015604 5 ustar itpe avd 0000000 0000000 Cartopy-0.14.2/lib/cartopy/io/__init__.py 0000644 0013740 0002103 00000036427 12700747662 017745 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2011 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
"""
Provides a collection of sub-packages for loading, saving and retrieving
various data formats.
"""
from __future__ import (absolute_import, division, print_function)
import collections
import os
import string
import warnings
import six
if six.PY3:
from urllib.request import urlopen
else:
from urllib2 import urlopen
from cartopy import config
def fh_getter(fh, mode='r', needs_filename=False):
"""
Convenience function for opening files.
Args:
* fh - File handle, filename or (file handle, filename) tuple
Kwargs:
* mode - Open mode. Defaults to "r".
Returns:
* (file handle, filename), opened in the given mode.
"""
if mode != 'r':
raise ValueError('Only mode "r" currently supported.')
if isinstance(fh, six.string_types):
filename = fh
fh = open(fh, mode)
elif isinstance(fh, tuple):
fh, filename = fh
if filename is None:
try:
filename = fh.name
except AttributeError: # does this occur?
if needs_filename:
raise ValueError('filename cannot be determined')
else:
filename = ''
return fh, filename
class DownloadWarning(Warning):
"""Issued when a file is being downloaded by a :class:`Downloader`."""
pass
class Downloader(object):
"""
Represents a resource, that can be configured easily, which knows
how to acquire itself (perhaps via HTTP).
The key interface method is :meth:`path` - typically *all* external calls
will be made to that method. To get hold of an appropriate
:class:`Downloader` instance the :func:`Downloader.from_config` static
method should be considered.
.. note:
All ``*_template`` arguments should be formattable using the
standard :meth:`string.format` rules. The formatting itself
is not done until a call to a subsequent method (such as
:meth:`Downloader.path`).
Args:
``url_template`` - The template of the full URL representing this
resource.
``target_path_template`` - The template of the full path to the file
that this Downloader represents. Typically
the path will be a subdirectory of
``config['data_dir']``, but this is not a
strict requirement. If the file does not
exist when calling :meth:`Downloader.path`
it will be downloaded to this location.
Kwargs:
``pre_downloaded_path_template`` - The template of a full path of a
file which has been downloaded
outside of this Downloader which
should be used as the file that
this resource represents. If the
file does not exist when
:meth:`Downloader.path` is called
it will not be downloaded to this
location (unlike the
``target_path_template`` argument).
"""
FORMAT_KEYS = ('config',)
"""
The minimum keys which should be provided in the ``format_dict``
argument for the ``path``, ``url``, ``target_path``,
``pre_downloaded_path`` and ``acquire_resource`` methods.
"""
def __init__(self, url_template, target_path_template,
pre_downloaded_path_template=''):
self.url_template = url_template
self.target_path_template = target_path_template
self.pre_downloaded_path_template = pre_downloaded_path_template
# define a formatter which will process the templates. Subclasses
# may override the standard ``''.format`` formatting by defining
# their own formatter subclass here.
self._formatter = string.Formatter()
def url(self, format_dict):
"""
The full URL that this resource represents.
Args:
``format_dict`` - The dictionary which is used to replace
certain template variables. Subclasses should
document which keys are expected as a minimum
in their ``FORMAT_KEYS`` class attribute.
"""
return self._formatter.format(self.url_template, **format_dict)
def target_path(self, format_dict):
"""
The path on disk of the file that this resource represents, must
either exist, or be writable by the current user. This method
does not check either of these conditions.
Args:
``format_dict`` - The dictionary which is used to replace
certain template variables. Subclasses should
document which keys are expected as a minimum
in their ``FORMAT_KEYS`` class attribute.
"""
return self._formatter.format(self.target_path_template,
**format_dict)
def pre_downloaded_path(self, format_dict):
"""
The path on disk of the file that this resource represents, if it does
not exist, then no further action will be taken with this path, and all
further processing will be done using :meth:`target_path` instead.
Args:
``format_dict`` - The dictionary which is used to replace
certain template variables. Subclasses should
document which keys are expected as a minimum
in their ``FORMAT_KEYS`` class attribute.
"""
return self._formatter.format(self.pre_downloaded_path_template,
**format_dict)
def path(self, format_dict):
"""
Returns the path to a file on disk that this resource represents.
If the file doesn't exist in :meth:`pre_downloaded_path` then it
will check whether it exists in :meth:`target_path`, otherwise
the resource will be downloaded via :meth:`acquire_resouce` from
:meth:`url` to :meth:`target_path`.
Typically, this is the method that most applications will call,
allowing implementors of new Downloaders to specialise
:meth:`acquire_resource`.
Args:
``format_dict`` - The dictionary which is used to replace
certain template variables. Subclasses should
document which keys are expected as a minimum
in their ``FORMAT_KEYS`` class attribute.
"""
pre_downloaded_path = self.pre_downloaded_path(format_dict)
target_path = self.target_path(format_dict)
if (pre_downloaded_path is not None and
os.path.exists(pre_downloaded_path)):
result_path = pre_downloaded_path
elif os.path.exists(target_path):
result_path = target_path
else:
# we need to download the file
result_path = self.acquire_resource(target_path, format_dict)
return result_path
def acquire_resource(self, target_path, format_dict):
"""
Downloads, via HTTP, the file that this resource represents.
Subclasses will typically override this method.
Args:
``format_dict`` - The dictionary which is used to replace
certain template variables. Subclasses should
document which keys are expected as a minimum
in their ``FORMAT_KEYS`` class attribute.
"""
target_dir = os.path.dirname(target_path)
if not os.path.isdir(target_dir):
os.makedirs(target_dir)
url = self.url(format_dict)
# try getting the resource (no exception handling, just let it raise)
response = self._urlopen(url)
with open(target_path, 'wb') as fh:
fh.write(response.read())
return target_path
def _urlopen(self, url):
"""
Return a file handle to the given HTTP resource URL.
Caller should close the file handle when finished with it.
"""
warnings.warn('Downloading: {}'.format(url), DownloadWarning)
return urlopen(url)
@staticmethod
def from_config(specification, config_dict=None):
"""
The ``from_config`` static method implements the logic for acquiring a
Downloader (sub)class instance from the config dictionary.
Args:
``specification`` - should be iterable, as it will be traversed
in reverse order to find the most appropriate
Downloader instance for this specification.
An example specification is
``('shapefiles', 'natural_earth')`` for the
Natural Earth shapefiles.
Kwargs:
``config_dict`` - typically this is left as None to use the
default ``cartopy.config`` "downloaders"
dictionary.
Example:
>>> from cartopy.io import Downloader
>>>
>>> dnldr = Downloader('https://example.com/{name}', './{name}.txt')
>>> config = {('level_1', 'level_2'): dnldr}
>>> d1 = Downloader.from_config(('level_1', 'level_2', 'level_3'),
... config_dict=config)
>>> print(d1.url_template)
https://example.com/{name}
>>> print(d1.url({'name': 'item_name'}))
https://example.com/item_name
"""
spec_depth = len(specification)
if config_dict is None:
downloaders = config['downloaders']
else:
downloaders = config_dict
result_downloader = None
for i in range(spec_depth, 0, -1):
lookup = specification[:i]
downloadable_item = downloaders.get(lookup, None)
if downloadable_item is not None:
result_downloader = downloadable_item
break
if result_downloader is None:
# should never really happen, but could if the user does
# some strange things like not having any downloaders defined
# in the config...
raise ValueError('No generic downloadable item in the config '
'dictionary for {}'.format(specification))
return result_downloader
class LocatedImage(collections.namedtuple('LocatedImage', 'image, extent')):
"""
Defines an image and associated extent in the form:
``image, (min_x, max_x, min_y, max_y)``
"""
class RasterSource(object):
"""
Defines the cartopy raster fetching interface.
A :class:`RasterSource` instance is able to supply images and
associated extents (as a sequence of :class:`LocatedImage` instances)
through its :meth:`~RasterSource.fetch_raster` method.
As a result, further interfacing classes, such as
:class:`cartopy.mpl.slippy_image_artist.SlippyImageArtist`, can then
make use of the interface for functionality such as interactive image
retrieval with pan and zoom functionality.
"""
def validate_projection(self, projection):
"""
Raise an error if this raster source cannot provide images in the
specified projection.
Parameters
----------
projection : :class:`cartopy.crs.Projection`
The desired projection of the image.
"""
raise NotImplementedError()
def fetch_raster(self, projection, extent, target_resolution):
"""
Return a sequence of images with extents given some constraining
information.
Parameters
----------
projection : :class:`cartopy.crs.Projection`
The desired projection of the image.
extent : iterable of length 4
The extent of the requested image in projected coordinates. The
resulting image may not be defined exactly by these extents, and
so the extent of the resulting image is also returned. The extents
must be defined in the form ``(min_x, max_x, min_y, max_y)``.
target_resolution : iterable of length 2
The desired resolution of the image as ``(width, height)``
in pixels.
Returns
-------
A sequence of :class:`LocatedImage` instances.
"""
raise NotImplementedError()
class RasterSourceContainer(RasterSource):
"""
A container which simply calls the appropriate methods on the
contained :class:`RasterSource`.
"""
def __init__(self, contained_source):
"""
Parameters
----------
contained_source : :class:`RasterSource` instance.
The source of the raster that this container is wrapping.
"""
self._source = contained_source
def fetch_raster(self, projection, extent, target_resolution):
return self._source.fetch_raster(projection, extent,
target_resolution)
def validate_projection(self, projection):
return self._source.validate_projection(projection)
class PostprocessedRasterSource(RasterSourceContainer):
"""
A :class:`RasterSource` which wraps another, an then applies a
post-processing step on the raster fetched from the contained source.
"""
def __init__(self, contained_source, img_post_process):
"""
Parameters
----------
contained_source : :class:`RasterSource` instance.
The source of the raster that this container is wrapping.
img_post_process : callable
Called after each `fetch_raster` call which yields a non-None
image result. The callable must accept the :class:`LocatedImage`
from the contained fetch_raster as its only argument, and must
return a single LocatedImage.
"""
super(PostprocessedRasterSource, self).__init__(contained_source)
self._post_fetch_fn = img_post_process
def fetch_raster(self, *args, **kwargs):
fetch_raster = super(PostprocessedRasterSource, self).fetch_raster
located_imgs = fetch_raster(*args, **kwargs)
if located_imgs:
located_imgs = [self._post_fetch_fn(img) for img in located_imgs]
return located_imgs
Cartopy-0.14.2/lib/cartopy/io/img_nest.py 0000755 0013740 0002103 00000045757 12700747662 020024 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2011 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
from __future__ import (absolute_import, division, print_function)
import collections
import glob
import itertools
import os.path
import numpy as np
from PIL import Image
import shapely.geometry as sgeom
from six.moves import zip
_img_class_attrs = ['filename', 'extent', 'origin', 'pixel_size']
class Img(collections.namedtuple('Img', _img_class_attrs)):
def __new__(cls, *args, **kwargs):
# ensure any lists given as args or kwargs are turned into tuples.
new_args = []
for item in args:
if isinstance(item, list):
item = tuple(item)
new_args.append(item)
new_kwargs = {}
for k, item in kwargs.items():
if isinstance(item, list):
item = tuple(item)
new_kwargs[k] = item
return super(Img, cls).__new__(cls, *new_args, **new_kwargs)
def __init__(self, *args, **kwargs):
"""
Represents a simple geo-located image.
Args:
* filename:
Filename of the image tile.
* extent:
The (x_lower, x_upper, y_lower, y_upper) extent of the image
in units of the native projection.
* origin:
Name of the origin.
* pixel_size:
The (x_scale, y_scale) pixel width, in units of the native
projection per pixel.
.. note::
API is likely to change in the future to include a CRS.
"""
self._bbox = None
def __getstate__(self):
"""
Override the default to ensure when pickling that any new attributes
introduced are included in the pickled object.
"""
return self.__dict__
def bbox(self):
"""
Return a :class:`~shapely.geometry.polygon.Polygon` instance for
this image's extents.
"""
if self._bbox is None:
x0, x1, y0, y1 = self.extent
self._bbox = sgeom.box(x0, y0, x1, y1)
return self._bbox
@staticmethod
def world_files(fname):
"""
Determine potential world filename combinations, without checking
their existence.
For example, a '*.tif' file may have one of the following
popular conventions for world file extensions '*.tifw',
'*.tfw', '*.TIFW' or '*.TFW'.
Given the possible world file extensions, the upper case basename
combinations are also generated. For example, the file 'map.tif'
will generate the following world file variations, 'map.tifw',
'map.tfw', 'map.TIFW', 'map.TFW', 'MAP.tifw', 'MAP.tfw', 'MAP.TIFW'
and 'MAP.TFW'.
Args:
* fname:
Name of the file for which to get all the possible world
filename combinations.
Returns:
A list of possible world filename combinations.
Examples:
>>> from cartopy.io.img_nest import Img
>>> Img.world_files('img.png')[:6]
['img.pngw', 'img.pgw', 'img.PNGW', 'img.PGW', 'IMG.pngw', 'IMG.pgw']
>>> Img.world_files('/path/to/img.TIF')[:2]
['/path/to/img.tifw', '/path/to/img.tfw']
>>> Img.world_files('/path/to/img/with_no_extension')[0]
'/path/to/img/with_no_extension.w'
"""
froot, fext = os.path.splitext(fname)
# If there was no extension to the filename.
if froot == fname:
result = ['{}.{}'.format(fname, 'w'),
'{}.{}'.format(fname, 'W')]
else:
fext = fext[1::].lower()
if len(fext) < 3:
result = ['{}.{}'.format(fname, 'w'),
'{}.{}'.format(fname, 'W')]
else:
fext_types = [fext + 'w', fext[0] + fext[-1] + 'w']
fext_types.extend([ext.upper() for ext in fext_types])
result = ['{}.{}'.format(froot, ext) for ext in fext_types]
def _convert_basename(name):
dirname, basename = os.path.dirname(name), os.path.basename(name)
base, ext = os.path.splitext(basename)
if base == base.upper():
result = base.lower() + ext
else:
result = base.upper() + ext
if dirname:
result = os.path.join(dirname, result)
return result
result += [_convert_basename(r) for r in result]
return result
def __array__(self):
return np.array(Image.open(self.filename))
@classmethod
def from_world_file(cls, img_fname, world_fname):
"""
Return an Img instance from the given image filename and
worldfile filename.
"""
im = Image.open(img_fname)
with open(world_fname) as world_fh:
extent, pix_size = cls.world_file_extent(world_fh, im.size)
if hasattr(im, 'close'):
im.close()
return cls(img_fname, extent, 'lower', pix_size)
@staticmethod
def world_file_extent(worldfile_handle, im_shape):
"""
Return the extent ``(x0, x1, y0, y1)`` and pixel size
``(x_width, y_width)`` as defined in the given worldfile file handle
and associated image shape ``(x, y)``.
"""
lines = worldfile_handle.readlines()
if len(lines) != 6:
raise ValueError('Only world files with 6 lines are supported.')
pix_size = (float(lines[0]), float(lines[3]))
pix_rotation = (float(lines[1]), float(lines[2]))
if pix_rotation != (0., 0.):
raise ValueError('Rotated pixels in world files is not currently '
'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.)
return (min_x, max_x, min_y, max_y), pix_size
class ImageCollection(object):
def __init__(self, name, crs, images=None):
"""
Represents a collection of images at the same logical level.
Typically these are images at the same zoom level or resolution.
Args:
* name:
The name of the image collection.
* crs:
The :class:`~cartopy.crs.Projection` instance.
Kwargs:
* images:
A list of one or more :class:`~cartopy.io.img_nest.Img` instances.
"""
self.name = name
self.crs = crs
self.images = images or []
def scan_dir_for_imgs(self, directory, glob_pattern='*.tif',
img_class=Img):
"""
Search the given directory for the associated world files
of the image files.
Args:
* directory:
The directory path to search for image files.
Kwargs:
* glob_pattern:
The image filename glob pattern to search with.
Defaults to '*.tif'.
* img_class
The class used to construct each image in the Collection.
.. note::
Does not recursively search sub-directories.
"""
imgs = glob.glob(os.path.join(directory, glob_pattern))
for img in imgs:
dirname, fname = os.path.split(img)
worlds = img_class.world_files(fname)
for fworld in worlds:
fworld = os.path.join(dirname, fworld)
if os.path.exists(fworld):
break
else:
msg = 'Image file {!r} has no associated world file'
raise ValueError(msg.format(img))
self.images.append(img_class.from_world_file(img, fworld))
class NestedImageCollection(object):
def __init__(self, name, crs, collections, _ancestry=None):
"""
Represents a complex nest of ImageCollections.
On construction, the image collections are scanned for ancestry,
leading to fast image finding capabilities.
A complex (and time consuming to create) NestedImageCollection instance
can be saved as a pickle file and subsequently be (quickly) restored.
There is a simplified creation interface for NestedImageCollection
``from_configuration`` for more detail.
Args:
* name:
The name of the nested image collection.
* crs:
The native :class:`~cartopy.crs.Projection` of all the image
collections.
* collections:
A list of one or more :class:`~cartopy.io.img_nest.ImageCollection`
instances.
"""
# NOTE: all collections must have the same crs.
_names = set([collection.name for collection in collections])
assert len(_names) == len(collections), \
'The collections must have unique names.'
self.name = name
self.crs = crs
self._collections_by_name = {collection.name: collection
for collection in collections}
def sort_func(c):
return np.max([image.bbox().area for image in c.images])
self._collections = sorted(collections, key=sort_func, reverse=True)
self._ancestry = {}
"""
maps (collection name, image) to a list of children
(collection name, image).
"""
if _ancestry is not None:
self._ancestry = _ancestry
else:
parent_wth_children = zip(self._collections,
self._collections[1:])
for parent_collection, collection in parent_wth_children:
for parent_image in parent_collection.images:
for image in collection.images:
if self._is_parent(parent_image, image):
# append the child image to the parent's ancestry
key = (parent_collection.name, parent_image)
self._ancestry.setdefault(key, []).append(
(collection.name, image))
# TODO check that the ancestry is in a good state (i.e. that each
# collection has child images)
@staticmethod
def _is_parent(parent, child):
"""
Returns whether the given Image is the parent of image.
Used by __init__.
"""
result = False
pbox = parent.bbox()
cbox = child.bbox()
if pbox.area > cbox.area:
result = pbox.intersects(cbox) and not pbox.touches(cbox)
return result
def image_for_domain(self, target_domain, target_z):
"""
Determine the image that provides complete coverage of target location.
The composed image is merged from one or more image tiles that overlay
the target location and provide complete image coverage of the target
location.
Args:
* target_domain:
A :class:`~shapely.geometry.linestring.LineString` instance that
specifies the target location requiring image coverage.
* target_z:
The name of the target
:class`~cartopy.io.img_nest.ImageCollection` which specifies the
target zoom level (resolution) of the required images.
Returns:
A tuple containing three items, consisting of the target
location :class:`numpy.ndarray` image data, the
(x-lower, x-upper, y-lower, y-upper) extent of the image, and the
origin for the target location.
"""
# XXX Copied from cartopy.io.img_tiles
if target_z not in self._collections_by_name:
# TODO: Handle integer depths also?
msg = '{!r} is not one of the possible collections.'
raise ValueError(msg.format(target_z))
tiles = []
for tile in self.find_images(target_domain, target_z):
try:
img, extent, origin = self.get_image(tile)
except IOError:
continue
img = np.array(img)
x = np.linspace(extent[0], extent[1], img.shape[1],
endpoint=False)
y = np.linspace(extent[2], extent[3], img.shape[0],
endpoint=False)
tiles.append([np.array(img), x, y, origin])
from cartopy.io.img_tiles import _merge_tiles
img, extent, origin = _merge_tiles(tiles)
return img, extent, origin
def find_images(self, target_domain, target_z, start_tiles=None):
"""
A generator that finds all images that overlap the bounded
target location.
Args:
* target_domain:
A :class:`~shapely.geometry.linestring.LineString` instance that
specifies the target location requiring image coverage.
* target_z:
The name of the target
:class:`~cartopy.io.img_nest.ImageCollection` which specifies
the target zoom level (resolution) of the required images.
Kwargs:
* start_tiles:
A list of one or more tuple pairs, composed of a
:class:`~cartopy.io.img_nest.ImageCollection` name and an
:class:`~cartopy.io.img_nest.Img` instance, from which to search
for the target images.
Returns:
A generator tuple pair composed of a
:class:`~cartopy.io.img_nest.ImageCollection` name and an
:class:`~cartopy.io.img_nest.Img` instance.
"""
# XXX Copied from cartopy.io.img_tiles
if target_z not in self._collections_by_name:
# TODO: Handle integer depths also?
msg = '{!r} is not one of the possible collections.'
raise ValueError(msg.format(target_z))
if start_tiles is None:
start_tiles = ((self._collections[0].name, img)
for img in self._collections[0].images)
for start_tile in start_tiles:
# recursively drill down to the images at the target zoom
domain = start_tile[1].bbox()
if target_domain.intersects(domain) and \
not target_domain.touches(domain):
if start_tile[0] == target_z:
yield start_tile
else:
for tile in self.subtiles(start_tile):
for result in self.find_images(target_domain,
target_z,
start_tiles=[tile]):
yield result
def subtiles(self, collection_image):
"""
Find the higher resolution image tiles that compose this parent
image tile.
Args:
* collection_image:
A tuple pair containing the parent
:class:`~cartopy.io.img_nest.ImageCollection` name and
:class:`~cartopy.io.img_nest.Img` instance.
Returns:
An iterator of tuple pairs containing the higher resolution child
:class:`~cartopy.io.img_nest.ImageCollection` name and
:class:`~cartopy.io.img_nest.Img` instance that compose the parent.
"""
return iter(self._ancestry.get(collection_image, []))
desired_tile_form = 'RGB'
def get_image(self, collection_image):
"""
Retrieve the data of the target image from file.
.. note::
The format of the retrieved image file data is controlled by
:attr:`~cartopy.io.img_nest.NestedImageCollection.desired_tile_form`,
which defaults to 'RGB' format.
Args:
* collection_image:
A tuple pair containing the target
:class:`~cartopy.io.img_nest.ImageCollection` name and
:class:`~cartopy.io.img_nest.Img` instance.
Returns:
A tuple containing three items, consisting of the associated image
file data, the (x_lower, x_upper, y_lower, y_upper) extent of the
image, and the image origin.
"""
img = collection_image[1]
img_data = Image.open(img.filename)
img_data = img_data.convert(self.desired_tile_form)
return img_data, img.extent, img.origin
@classmethod
def from_configuration(cls, name, crs, name_dir_pairs,
glob_pattern='*.tif',
img_class=Img):
"""
Creates a :class:`~cartopy.io.img_nest.NestedImageCollection` instance
given the list of image collection name and directory path pairs.
This is very convenient functionality for simple configuration level
creation of this complex object.
For example, to produce a nested collection of OS map tiles::
files = [['OS 1:1,000,000', '/directory/to/1_to_1m'],
['OS 1:250,000', '/directory/to/1_to_250k'],
['OS 1:50,000', '/directory/to/1_to_50k'],
]
r = NestedImageCollection.from_configuration('os',
ccrs.OSGB(),
files)
.. important::
The list of image collection name and directory path pairs must be
given in increasing resolution order i.e. from low resolution to
high resolution.
Args:
* name:
The name for the
:class:`~cartopy.io.img_nest.NestedImageCollection` instance.
* crs:
The :class:`~cartopy.crs.Projection` of the image collection.
* name_dir_pairs:
A list of image collection name and directory path pairs.
Kwargs:
* glob_pattern:
The image collection filename glob pattern.
Defaults to '*.tif'.
* img_class:
The class of images created in the image collection.
Returns:
A :class:`~cartopy.io.img_nest.NestedImageCollection` instance.
"""
collections = []
for collection_name, collection_dir in name_dir_pairs:
collection = ImageCollection(collection_name, crs)
collection.scan_dir_for_imgs(collection_dir,
glob_pattern=glob_pattern,
img_class=img_class)
collections.append(collection)
return cls(name, crs, collections)
Cartopy-0.14.2/lib/cartopy/io/img_tiles.py 0000755 0013740 0002103 00000035521 12700747662 020157 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2011 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
"""
Implements image tile identification and fetching from various sources.
The matplotlib interface can make use of tile objects (defined below) via the
:meth:`cartopy.mpl.geoaxes.GeoAxes.add_image` method. For example, to add a
:class:`MapQuest Open Aerial tileset ` to an existing axes
at zoom level 2, do ``ax.add_image(MapQuestOpenAerial(), 2)``. An example of
using tiles in this way can be found at :ref:`examples-eyja_volcano`.
"""
from __future__ import (absolute_import, division, print_function)
from PIL import Image
import shapely.geometry as sgeom
import numpy as np
import six
import cartopy.crs as ccrs
class GoogleTiles(object):
"""
Implements web tile retrieval using the Google WTS coordinate system.
A "tile" in this class refers to the coordinates (x, y, z).
"""
def __init__(self, desired_tile_form='RGB', style="street"):
"""
:param desired_tile_form:
:param style: The style for the Google Maps tiles. One of 'street',
'satellite', 'terrain', and 'only_streets'.
Defaults to 'street'.
"""
# Only streets are partly transparent tiles that can be overlayed over
# the satellite map to create the known hybrid style from google.
styles = ["street", "satellite", "terrain", "only_streets"]
style = style.lower()
if style not in styles:
msg = "Invalid style '%s'. Valid styles: %s" % \
(style, ", ".join(styles))
raise ValueError(msg)
self.style = style
# The 'satellite' and 'terrain' styles require pillow with a jpeg
# decoder.
if self.style in ["satellite", "terrain"] and \
not hasattr(Image.core, "jpeg_decoder") or \
not Image.core.jpeg_decoder:
msg = "The '%s' style requires pillow with jpeg decoding support."
raise ValueError(msg % self.style)
self.imgs = []
self.crs = ccrs.Mercator.GOOGLE
self.desired_tile_form = desired_tile_form
def image_for_domain(self, target_domain, target_z):
tiles = []
for tile in self.find_images(target_domain, target_z):
try:
img, extent, origin = self.get_image(tile)
except IOError:
continue
img = np.array(img)
x = np.linspace(extent[0], extent[1], img.shape[1])
y = np.linspace(extent[2], extent[3], img.shape[0])
tiles.append([img, x, y, origin])
img, extent, origin = _merge_tiles(tiles)
return img, extent, origin
def _find_images(self, target_domain, target_z, start_tile=(0, 0, 0)):
"""Target domain is a shapely polygon in native coordinates."""
assert isinstance(target_z, int) and target_z >= 0, ('target_z must '
'be an integer '
'>=0.')
# Recursively drill down to the images at the target zoom.
x0, x1, y0, y1 = self._tileextent(start_tile)
domain = sgeom.box(x0, y0, x1, y1)
if domain.intersects(target_domain):
if start_tile[2] == target_z:
yield start_tile
else:
for tile in self._subtiles(start_tile):
for result in self._find_images(target_domain, target_z,
start_tile=tile):
yield result
find_images = _find_images
def subtiles(self, x_y_z):
x, y, z = x_y_z
# Google tile specific (i.e. up->down).
for xi in range(0, 2):
for yi in range(0, 2):
yield x * 2 + xi, y * 2 + yi, z + 1
_subtiles = subtiles
def tile_bbox(self, x, y, z, y0_at_north_pole=True):
"""
Returns the ``(x0, x1), (y0, y1)`` bounding box for the given x, y, z
tile position.
Parameters
----------
x, y, z : int
The x, y, z tile coordinates in the Google tile numbering system
(with y=0 being at the north pole), unless `y0_at_north_pole` is
set to ``False``, in which case `y` is in the TMS numbering system
(with y=0 being at the south pole).
y0_at_north_pole : bool
Whether the numbering of the y coordinate starts at the north
pole (as is the convention for Google tiles), or the south
pole (as is the convention for TMS).
"""
n = 2 ** z
assert 0 <= x <= (n - 1), ("Tile's x index is out of range. Upper "
"limit %s. Got %s" % (n, x))
assert 0 <= y <= (n - 1), ("Tile's y index is out of range. Upper "
"limit %s. Got %s" % (n, y))
x0, x1 = self.crs.x_limits
y0, y1 = self.crs.y_limits
# Compute the box height and width in native coordinates
# for this zoom level.
box_h = (y1 - y0) / n
box_w = (x1 - x0) / n
# Compute the native x & y extents of the tile.
n_xs = x0 + (x + np.arange(0, 2, dtype=np.float64)) * box_w
n_ys = y0 + (y + np.arange(0, 2, dtype=np.float64)) * box_h
if y0_at_north_pole:
n_ys = -1 * n_ys[::-1]
return n_xs, n_ys
def tileextent(self, x_y_z):
"""Returns extent tuple ``(x0,x1,y0,y1)`` in Mercator coordinates."""
x, y, z = x_y_z
x_lim, y_lim = self.tile_bbox(x, y, z, y0_at_north_pole=True)
return tuple(x_lim) + tuple(y_lim)
_tileextent = tileextent
def _image_url(self, tile):
style_dict = {
"street": "m",
"satellite": "s",
"terrain": "t",
"only_streets": "h"}
url = ('https://mts0.google.com/vt/lyrs={style}@177000000&hl=en&'
'src=api&x={tile_x}&y={tile_y}&z={tile_z}&s=G'.format(
style=style_dict[self.style],
tile_x=tile[0],
tile_y=tile[1],
tile_z=tile[2]))
return url
def get_image(self, tile):
if six.PY3:
from urllib.request import urlopen
else:
from urllib2 import urlopen
url = self._image_url(tile)
fh = urlopen(url)
im_data = six.BytesIO(fh.read())
fh.close()
img = Image.open(im_data)
img = img.convert(self.desired_tile_form)
return img, self.tileextent(tile), 'lower'
class MapQuestOSM(GoogleTiles):
# http://developer.mapquest.com/web/products/open/map for terms of use
def _image_url(self, tile):
x, y, z = tile
url = 'http://otile1.mqcdn.com/tiles/1.0.0/osm/%s/%s/%s.jpg' % (
z, x, y)
return url
class MapQuestOpenAerial(GoogleTiles):
# http://developer.mapquest.com/web/products/open/map for terms of use
# The following attribution should be included in the resulting image:
# "Portions Courtesy NASA/JPL-Caltech and U.S. Depart. of Agriculture,
# Farm Service Agency"
def _image_url(self, tile):
x, y, z = tile
url = 'http://oatile1.mqcdn.com/tiles/1.0.0/sat/%s/%s/%s.jpg' % (
z, x, y)
return url
class OSM(GoogleTiles):
# http://developer.mapquest.com/web/products/open/map for terms of use
def _image_url(self, tile):
x, y, z = tile
url = 'https://a.tile.openstreetmap.org/%s/%s/%s.png' % (z, x, y)
return url
class StamenTerrain(GoogleTiles):
"""
Terrain tiles defined for the continental United States, and include land
color and shaded hills. The land colors are a custom palette developed by
Gem Spear for the National Atlas 1km land cover data set, which defines
twenty-four land classifications including five kinds of forest,
combinations of shrubs, grasses and crops, and a few tundras and wetlands.
The colors are at their highest contrast when fully zoomed-out to the
whole U.S., and they slowly fade out to pale off-white as you zoom in to
leave room for foreground data and break up the weirdness of large areas
of flat, dark green.
Additional info:
http://mike.teczno.com/notes/osm-us-terrain-layer/background.html
http://maps.stamen.com/#terrain/12/37.6902/-122.3600
https://wiki.openstreetmap.org/wiki/List_of_OSM_based_Services
https://github.com/migurski/DEM-Tools
"""
def _image_url(self, tile):
x, y, z = tile
url = 'http://tile.stamen.com/terrain-background/%s/%s/%s.png' % (
z, x, y)
return url
class MapboxTiles(GoogleTiles):
"""
Implements web tile retrieval from Mapbox.
For terms of service, see https://www.mapbox.com/tos/.
"""
def __init__(self, access_token, map_id):
"""
Set up a new Mapbox tiles instance.
Access to Mapbox web services requires an access token and a map ID.
See https://www.mapbox.com/developers/api/ for details.
Parameters
----------
access_token: str
A valid Mapbox API access token.
map_id: str
A map ID for a publically accessible map. This is the map whose
tiles will be retrieved through this process.
"""
self.access_token = access_token
self.map_id = map_id
super(MapboxTiles, self).__init__()
def _image_url(self, tile):
x, y, z = tile
url = ('https://api.tiles.mapbox.com/v4/{mapid}/{z}/{x}/{y}.png?'
'access_token={token}'.format(z=z, y=y, x=x,
mapid=self.map_id,
token=self.access_token))
return url
class QuadtreeTiles(GoogleTiles):
"""
Implements web tile retrieval using the Microsoft WTS quadkey coordinate
system.
A "tile" in this class refers to a quadkey such as "1", "14" or "141"
where the length of the quatree is the zoom level in Google Tile terms.
"""
def _image_url(self, tile):
url = ('http://ecn.dynamic.t1.tiles.virtualearth.net/comp/'
'CompositionHandler/{tile}?mkt=en-'
'gb&it=A,G,L&shading=hill&n=z'.format(tile=tile))
return url
def tms_to_quadkey(self, tms, google=False):
quadKey = ""
x, y, z = tms
# this algorithm works with google tiles, rather than tms, so convert
# to those first.
if not google:
y = (2 ** z - 1) - y
for i in range(z, 0, -1):
digit = 0
mask = 1 << (i - 1)
if (x & mask) != 0:
digit += 1
if (y & mask) != 0:
digit += 2
quadKey += str(digit)
return quadKey
def quadkey_to_tms(self, quadkey, google=False):
# algorithm ported from
# https://msdn.microsoft.com/en-us/library/bb259689.aspx
assert isinstance(quadkey, six.string_types), \
'quadkey must be a string'
x = y = 0
z = len(quadkey)
for i in range(z, 0, -1):
mask = 1 << (i - 1)
if quadkey[z - i] == '0':
pass
elif quadkey[z - i] == '1':
x |= mask
elif quadkey[z - i] == '2':
y |= mask
elif quadkey[z - i] == '3':
x |= mask
y |= mask
else:
raise ValueError('Invalid QuadKey digit '
'sequence.' + str(quadkey))
# the algorithm works to google tiles, so convert to tms
if not google:
y = (2 ** z - 1) - y
return (x, y, z)
def subtiles(self, quadkey):
for i in range(4):
yield quadkey + str(i)
def tileextent(self, quadkey):
x_y_z = self.quadkey_to_tms(quadkey, google=True)
return GoogleTiles.tileextent(self, x_y_z)
def find_images(self, target_domain, target_z, start_tile=None):
"""
Find all the quadtree's at the given target zoom, in the given
target domain.
target_z must be a value >= 1.
"""
if target_z == 0:
raise ValueError('The empty quadtree cannot be returned.')
if start_tile is None:
start_tiles = ['0', '1', '2', '3']
else:
start_tiles = [start_tile]
for start_tile in start_tiles:
start_tile = self.quadkey_to_tms(start_tile, google=True)
for tile in GoogleTiles.find_images(self, target_domain, target_z,
start_tile=start_tile):
yield self.tms_to_quadkey(tile, google=True)
def _merge_tiles(tiles):
"""Return a single image, merging the given images."""
if not tiles:
raise ValueError('A non-empty list of tiles should '
'be provided to merge.')
xset = [set(x) for i, x, y, _ in tiles]
yset = [set(y) for i, x, y, _ in tiles]
xs = xset[0]
xs.update(*xset[1:])
ys = yset[0]
ys.update(*yset[1:])
xs = sorted(xs)
ys = sorted(ys)
other_len = tiles[0][0].shape[2:]
img = np.zeros((len(ys), len(xs)) + other_len, dtype=np.uint8) - 1
for tile_img, x, y, origin in tiles:
y_first, y_last = y[0], y[-1]
yi0, yi1 = np.where((y_first == ys) | (y_last == ys))[0]
if origin == 'upper':
yi0 = tile_img.shape[0] - yi0 - 1
yi1 = tile_img.shape[0] - yi1 - 1
start, stop, step = yi0, yi1, 1 if yi0 < yi1 else -1
if step == 1 and stop == img.shape[0] - 1:
stop = None
elif step == -1 and stop == 0:
stop = None
else:
stop += step
y_slice = slice(start, stop, step)
xi0, xi1 = np.where((x[0] == xs) | (x[-1] == xs))[0]
start, stop, step = xi0, xi1, 1 if xi0 < xi1 else -1
if step == 1 and stop == img.shape[1] - 1:
stop = None
elif step == -1 and stop == 0:
stop = None
else:
stop += step
x_slice = slice(start, stop, step)
img_slice = (y_slice, x_slice, Ellipsis)
if origin == 'lower':
tile_img = tile_img[::-1, ::]
img[img_slice] = tile_img
return img, [min(xs), max(xs), min(ys), max(ys)], 'lower'
Cartopy-0.14.2/lib/cartopy/io/ogc_clients.py 0000644 0013740 0002103 00000074017 12700747662 020474 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2014 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
"""
Implements RasterSource classes which can retrieve imagery from web services
such as WMS and WMTS.
The matplotlib interface can make use of RasterSources via the
:meth:`cartopy.mpl.geoaxes.GeoAxes.add_raster` method,
with additional specific methods which make use of this for WMS and WMTS
(:meth:`~cartopy.mpl.geoaxes.GeoAxes.add_wms` and
:meth:`~cartopy.mpl.geoaxes.GeoAxes.add_wmts`). An example of using WMTS in
this way can be found at :ref:`examples-wmts`.
"""
from __future__ import (absolute_import, division, print_function)
import six
import collections
import io
import math
import warnings
import weakref
from xml.etree import ElementTree
import numpy as np
from PIL import Image
import shapely.geometry as sgeom
try:
from owslib.wms import WebMapService
from owslib.wfs import WebFeatureService
import owslib.util
import owslib.wmts
_OWSLIB_AVAILABLE = True
except ImportError:
WebMapService = None
WebFeatureService = None
_OWSLIB_AVAILABLE = False
import cartopy.crs as ccrs
from cartopy.io import LocatedImage, RasterSource
from cartopy.img_transform import warp_array
_OWSLIB_REQUIRED = 'OWSLib is required to use OGC web services.'
# Hardcode some known EPSG codes for now.
# The order given here determines the preferred SRS for WMS retrievals.
_CRS_TO_OGC_SRS = collections.OrderedDict(
[(ccrs.PlateCarree(), 'EPSG:4326'),
(ccrs.Mercator.GOOGLE, 'EPSG:900913')])
# Standard pixel size of 0.28 mm as defined by WMTS.
METERS_PER_PIXEL = 0.28e-3
_WGS84_METERS_PER_UNIT = 2 * math.pi * 6378137 / 360
METERS_PER_UNIT = {
'urn:ogc:def:crs:EPSG::27700': 1,
'urn:ogc:def:crs:EPSG::900913': 1,
'urn:ogc:def:crs:OGC:1.3:CRS84': _WGS84_METERS_PER_UNIT,
}
_URN_TO_CRS = {
'urn:ogc:def:crs:EPSG::27700': ccrs.OSGB(),
'urn:ogc:def:crs:EPSG::4326': ccrs.PlateCarree(),
'urn:ogc:def:crs:EPSG::900913': ccrs.GOOGLE_MERCATOR,
'urn:ogc:def:crs:OGC:1.3:CRS84': ccrs.PlateCarree(),
'urn:ogc:def:crs:EPSG::3031': ccrs.Stereographic(central_latitude=-90,
true_scale_latitude=-71)
}
# XML namespace definitions
_MAP_SERVER_NS = '{http://mapserver.gis.umn.edu/mapserver}'
_GML_NS = '{http://www.opengis.net/gml}'
class WMSRasterSource(RasterSource):
"""
A WMS imagery retriever which can be added to a map.
.. note:: Requires owslib and Pillow to work.
.. note::
No caching of retrieved maps is done with this WMSRasterSource.
To reduce load on the WMS server it is encouraged to tile
map requests and subsequently stitch them together to recreate
a single raster, thus allowing for a more aggressive caching scheme,
but this WMSRasterSource does not currently implement WMS tile
fetching.
Whilst not the same service, there is also a WMTSRasterSource which
makes use of tiles and comes with built-in caching for fast repeated
map retrievals.
"""
def __init__(self, service, layers, getmap_extra_kwargs=None):
"""
Parameters
----------
service : string or WebMapService instance
The WebMapService instance, or URL of a WMS service, from whence
to retrieve the image.
layers : string or list of strings
The name(s) of layers to use from the WMS service.
getmap_extra_kwargs : dict or None
Extra keywords to pass through to the service's getmap method.
If None, a dictionary with ``{'transparent': True}`` will
be defined.
"""
if WebMapService is None:
raise ImportError(_OWSLIB_REQUIRED)
if isinstance(service, six.string_types):
service = WebMapService(service)
if isinstance(layers, six.string_types):
layers = [layers]
if getmap_extra_kwargs is None:
getmap_extra_kwargs = {'transparent': True}
if len(layers) == 0:
raise ValueError('One or more layers must be defined.')
for layer in layers:
if layer not in service.contents:
raise ValueError('The {!r} layer does not exist in '
'this service.'.format(layer))
#: The OWSLib WebMapService instance.
self.service = service
#: The names of the layers to fetch.
self.layers = layers
#: Extra kwargs passed through to the service's getmap request.
self.getmap_extra_kwargs = getmap_extra_kwargs
def _native_srs(self, projection):
# Return the SRS which corresponds to the given projection when
# known, otherwise return None.
return _CRS_TO_OGC_SRS.get(projection)
def _fallback_proj_and_srs(self):
"""
Return a :class:`cartopy.crs.Projection` and corresponding
SRS string in which the WMS service can supply the requested
layers.
"""
contents = self.service.contents
for proj, srs in six.iteritems(_CRS_TO_OGC_SRS):
missing = any(srs not in contents[layer].crsOptions for
layer in self.layers)
if not missing:
break
if missing:
raise ValueError('The requested layers are not available in a '
'known SRS.')
return proj, srs
def validate_projection(self, projection):
if self._native_srs(projection) is None:
self._fallback_proj_and_srs()
def _image_and_extent(self, wms_proj, wms_srs, wms_extent, output_proj,
output_extent, target_resolution):
min_x, max_x, min_y, max_y = wms_extent
wms_image = self.service.getmap(layers=self.layers,
srs=wms_srs,
bbox=(min_x, min_y, max_x, max_y),
size=target_resolution,
format='image/png',
**self.getmap_extra_kwargs)
wms_image = Image.open(io.BytesIO(wms_image.read()))
if wms_proj == output_proj:
extent = output_extent
else:
# Convert Image to numpy array (flipping so that origin
# is 'lower').
img, extent = warp_array(np.asanyarray(wms_image)[::-1],
source_proj=wms_proj,
source_extent=wms_extent,
target_proj=output_proj,
target_res=target_resolution,
target_extent=output_extent,
mask_extrapolated=True)
# Convert arrays with masked RGB(A) values to non-masked RGBA
# arrays, setting the alpha channel to zero for masked values.
# This avoids unsightly grey boundaries appearing when the
# extent is limited (i.e. not global).
if np.ma.is_masked(img):
if img.shape[2:3] == (3,):
# RGB
old_img = img
img = np.zeros(img.shape[:2] + (4,), dtype=img.dtype)
img[:, :, 0:3] = old_img
img[:, :, 3] = ~ np.any(old_img.mask, axis=2)
if img.dtype.kind == 'u':
img[:, :, 3] *= 255
elif img.shape[2:3] == (4,):
# RGBA
img[:, :, 3] = np.where(np.any(img.mask, axis=2), 0,
img[:, :, 3])
img = img.data
# Convert warped image array back to an Image, undoing the
# earlier flip.
wms_image = Image.fromarray(img[::-1])
return LocatedImage(wms_image, extent)
def fetch_raster(self, projection, extent, target_resolution):
min_x, max_x, min_y, max_y = extent
target_resolution = [int(np.ceil(val)) for val in target_resolution]
wms_srs = self._native_srs(projection)
if wms_srs is not None:
wms_proj = projection
wms_extents = [extent]
else:
# The SRS for the requested projection is not known, so
# attempt to use the fallback and perform the necessary
# transformations.
wms_proj, wms_srs = self._fallback_proj_and_srs()
# Calculate the bounding box(es) in WMS projection.
# Start with the requested area.
target_box = sgeom.box(min_x, min_y, max_x, max_y)
# If the requested area (i.e. target_box) is bigger (or
# nearly bigger) than the entire output projection domain
# then we erode the request area to avoid re-projection
# instabilities near the full-projection limit.
buffered_target_box = target_box.buffer(projection.threshold,
resolution=1)
fudge_mode = buffered_target_box.contains(projection.domain)
if fudge_mode:
target_box = projection.domain.buffer(-projection.threshold)
# Convert the requested area to the WMS server's projection.
wms_polys = wms_proj.project_geometry(target_box, projection)
wms_extents = []
for wms_poly in wms_polys:
min_x, min_y, max_x, max_y = wms_poly.bounds
if fudge_mode:
# If we shrunk the request area before, then here we
# need to re-inflate.
radius = min(max_x - min_x, max_y - min_y) / 5.0
radius = min(radius, wms_proj.threshold * 15)
wms_poly = wms_poly.buffer(radius, resolution=1)
# Prevent the expanded request going beyond the
# limits of the projection.
wms_poly = wms_proj.domain.intersection(wms_poly)
min_x, min_y, max_x, max_y = wms_poly.bounds
wms_extents.append((min_x, max_x, min_y, max_y))
located_images = []
for wms_extent in wms_extents:
located_images.append(self._image_and_extent(wms_proj, wms_srs,
wms_extent,
projection, extent,
target_resolution))
return located_images
class WMTSRasterSource(RasterSource):
"""
A WMTS imagery retriever which can be added to a map.
Uses tile caching for fast repeated map retrievals.
.. note:: Requires owslib and Pillow to work.
"""
_shared_image_cache = weakref.WeakKeyDictionary()
"""
A nested mapping from WMTS, layer name, tile matrix name, tile row
and tile column to the resulting PIL image::
{wmts: {(layer_name, tile_matrix_name): {(row, column): Image}}}
This provides a significant boost when producing multiple maps of the
same projection or with an interactive figure.
"""
def __init__(self, wmts, layer_name):
"""
Args:
* wmts - The URL of the WMTS, or an
owslib.wmts.WebMapTileService instance.
* layer_name - The name of the layer to use.
"""
if WebMapService is None:
raise ImportError(_OWSLIB_REQUIRED)
if not (hasattr(wmts, 'tilematrixsets') and
hasattr(wmts, 'contents') and
hasattr(wmts, 'gettile')):
wmts = owslib.wmts.WebMapTileService(wmts)
try:
layer = wmts.contents[layer_name]
except KeyError:
raise ValueError('Invalid layer name {!r} for WMTS at {!r}'.format(
layer_name, wmts.url))
#: The OWSLib WebMapTileService instance.
self.wmts = wmts
#: The layer to fetch.
self.layer = layer
self._matrix_set_name_map = {}
def _matrix_set_name(self, projection):
key = id(projection)
matrix_set_name = self._matrix_set_name_map.get(key)
if matrix_set_name is None:
wmts = self.wmts
if hasattr(self.layer, 'tilematrixsetlinks'):
matrix_set_names = self.layer.tilematrixsetlinks.keys()
else:
matrix_set_names = self.layer.tilematrixsets
for tile_matrix_set_name in matrix_set_names:
tile_matrix_set = wmts.tilematrixsets[tile_matrix_set_name]
crs_urn = tile_matrix_set.crs
if crs_urn in _URN_TO_CRS:
tms_crs = _URN_TO_CRS[crs_urn]
if tms_crs == projection:
matrix_set_name = tile_matrix_set_name
break
if matrix_set_name is None:
available_urns = sorted(set(
wmts.tilematrixsets[name].crs for name in
matrix_set_names))
msg = 'Unable to find tile matrix for projection.'
msg += '\n Projection: ' + str(projection)
msg += '\n Available tile CRS URNs:'
msg += '\n ' + '\n '.join(available_urns)
raise ValueError(msg)
self._matrix_set_name_map[key] = matrix_set_name
return matrix_set_name
def validate_projection(self, projection):
self._matrix_set_name(projection)
def fetch_raster(self, projection, extent, target_resolution):
matrix_set_name = self._matrix_set_name(projection)
min_x, max_x, min_y, max_y = extent
width, height = target_resolution
max_pixel_span = min((max_x - min_x) / width,
(max_y - min_y) / height)
image, extent = self._wmts_images(self.wmts, self.layer,
matrix_set_name, extent,
max_pixel_span)
return [LocatedImage(image, extent)]
def _choose_matrix(self, tile_matrices, meters_per_unit, max_pixel_span):
# Get the tile matrices in order of increasing resolution.
tile_matrices = sorted(tile_matrices,
key=lambda tm: tm.scaledenominator,
reverse=True)
# Find which tile matrix has the appropriate resolution.
max_scale = max_pixel_span * meters_per_unit / METERS_PER_PIXEL
for tm in tile_matrices:
if tm.scaledenominator <= max_scale:
return tm
return tile_matrices[-1]
def _tile_span(self, tile_matrix, meters_per_unit):
pixel_span = tile_matrix.scaledenominator * (
METERS_PER_PIXEL / meters_per_unit)
tile_span_x = tile_matrix.tilewidth * pixel_span
tile_span_y = tile_matrix.tileheight * pixel_span
return tile_span_x, tile_span_y
def _select_tiles(self, tile_matrix, tile_matrix_limits,
tile_span_x, tile_span_y, extent):
# Convert the requested extent from CRS coordinates to tile
# indices. See annex H of the WMTS v1.0.0 spec.
# NB. The epsilons get rid of any tiles which only just
# (i.e. one part in a million) intrude into the requested
# extent. Since these wouldn't be visible anyway there's nothing
# to be gained by spending the time downloading them.
min_x, max_x, min_y, max_y = extent
matrix_min_x, matrix_max_y = tile_matrix.topleftcorner
epsilon = 1e-6
min_col = int((min_x - matrix_min_x) / tile_span_x + epsilon)
max_col = int((max_x - matrix_min_x) / tile_span_x - epsilon)
min_row = int((matrix_max_y - max_y) / tile_span_y + epsilon)
max_row = int((matrix_max_y - min_y) / tile_span_y - epsilon)
# Clamp to the limits of the tile matrix.
min_col = max(min_col, 0)
max_col = min(max_col, tile_matrix.matrixwidth - 1)
min_row = max(min_row, 0)
max_row = min(max_row, tile_matrix.matrixheight - 1)
# Clamp to any layer-specific limits on the tile matrix.
if tile_matrix_limits:
min_col = max(min_col, tile_matrix_limits.mintilecol)
max_col = min(max_col, tile_matrix_limits.maxtilecol)
min_row = max(min_row, tile_matrix_limits.mintilerow)
max_row = min(max_row, tile_matrix_limits.maxtilerow)
return min_col, max_col, min_row, max_row
def _wmts_images(self, wmts, layer, matrix_set_name, extent,
max_pixel_span):
"""
Add images from the specified WMTS layer and matrix set to cover
the specified extent at an appropriate resolution.
The zoom level (aka. tile matrix) is chosen to give the lowest
possible resolution which still provides the requested quality.
If insufficient resolution is available, the highest available
resolution is used.
Args:
* wmts - The owslib.wmts.WebMapTileService providing the tiles.
* layer - The owslib.wmts.ContentMetadata (aka. layer) to draw.
* matrix_set_name - The name of the matrix set to use.
* extent - Tuple of (left, right, bottom, top) in Axes coordinates.
* max_pixel_span - Preferred maximum pixel width or height
in Axes coordinates.
"""
# Find which tile matrix has the appropriate resolution.
tile_matrix_set = wmts.tilematrixsets[matrix_set_name]
tile_matrices = tile_matrix_set.tilematrix.values()
meters_per_unit = METERS_PER_UNIT[tile_matrix_set.crs]
tile_matrix = self._choose_matrix(tile_matrices, meters_per_unit,
max_pixel_span)
# Determine which tiles are required to cover the requested extent.
tile_span_x, tile_span_y = self._tile_span(tile_matrix,
meters_per_unit)
tile_matrix_set_links = getattr(layer, 'tilematrixsetlinks', None)
if tile_matrix_set_links is None:
tile_matrix_limits = None
else:
tile_matrix_set_link = tile_matrix_set_links[matrix_set_name]
tile_matrix_limits = tile_matrix_set_link.tilematrixlimits.get(
tile_matrix.identifier)
min_col, max_col, min_row, max_row = self._select_tiles(
tile_matrix, tile_matrix_limits, tile_span_x, tile_span_y, extent)
# Find the relevant section of the image cache.
tile_matrix_id = tile_matrix.identifier
cache_by_wmts = WMTSRasterSource._shared_image_cache
cache_by_layer_matrix = cache_by_wmts.setdefault(wmts, {})
image_cache = cache_by_layer_matrix.setdefault((layer.id,
tile_matrix_id), {})
# To avoid nasty seams between the individual tiles, we
# accumulate the tile images into a single image.
big_img = None
n_rows = 1 + max_row - min_row
n_cols = 1 + max_col - min_col
# Ignore out-of-range errors if the current version of OWSLib
# doesn't provide the regional information.
ignore_out_of_range = tile_matrix_set_links is None
for row in range(min_row, max_row + 1):
for col in range(min_col, max_col + 1):
# Get the tile's Image from the cache if possible.
img_key = (row, col)
img = image_cache.get(img_key)
if img is None:
try:
tile = wmts.gettile(
layer=layer.id,
tilematrixset=matrix_set_name,
tilematrix=tile_matrix_id,
row=row, column=col)
except owslib.util.ServiceException as exception:
if ('TileOutOfRange' in exception.message and
ignore_out_of_range):
continue
raise exception
img = Image.open(io.BytesIO(tile.read()))
image_cache[img_key] = img
if big_img is None:
size = (img.size[0] * n_cols, img.size[1] * n_rows)
big_img = Image.new('RGBA', size, (255, 255, 255, 255))
top = (row - min_row) * tile_matrix.tileheight
left = (col - min_col) * tile_matrix.tilewidth
big_img.paste(img, (left, top))
if big_img is None:
img_extent = None
else:
matrix_min_x, matrix_max_y = tile_matrix.topleftcorner
min_img_x = matrix_min_x + tile_span_x * min_col
max_img_y = matrix_max_y - tile_span_y * min_row
img_extent = (min_img_x, min_img_x + n_cols * tile_span_x,
max_img_y - n_rows * tile_span_y, max_img_y)
return big_img, img_extent
class WFSGeometrySource(object):
"""Web Feature Service (WFS) retrieval for Cartopy."""
def __init__(self, service, features, getfeature_extra_kwargs=None):
"""
Args:
* service:
The URL of a WFS, or an instance of
:class:`owslib.wfs.WebFeatureService`.
* features:
The typename(s) of the features from the WFS that
will be retrieved and made available as geometries.
Kwargs:
* getfeature_extra_kwargs:
Extra keyword args to pass to the service's `getfeature` call.
"""
if WebFeatureService is None:
raise ImportError(_OWSLIB_REQUIRED)
if isinstance(service, six.string_types):
service = WebFeatureService(service)
if isinstance(features, six.string_types):
features = [features]
if getfeature_extra_kwargs is None:
getfeature_extra_kwargs = {}
if not features:
raise ValueError('One or more features must be specified.')
for feature in features:
if feature not in service.contents:
raise ValueError('The {!r} feature does not exist in this '
'service.'.format(feature))
self.service = service
self.features = features
self.getfeature_extra_kwargs = getfeature_extra_kwargs
self._default_urn = None
def default_projection(self):
"""
Return a :class:`cartopy.crs.Projection` in which the WFS
service can supply the requested features.
"""
# Using first element in crsOptions (default).
if self._default_urn is None:
default_urn = set(self.service.contents[feature].crsOptions[0] for
feature in self.features)
if len(default_urn) != 1:
ValueError('Failed to find a single common default SRS '
'across all features (typenames).')
else:
default_urn = default_urn.pop()
default_srs = default_urn.id
if six.text_type(default_urn) not in _URN_TO_CRS:
raise ValueError('Unknown mapping from SRS/CRS_URN {!r} to '
'cartopy projection.'.format(default_urn))
self._default_urn = default_urn
return _URN_TO_CRS[six.text_type(self._default_urn)]
def fetch_geometries(self, projection, extent):
"""
Return any Point, Linestring or LinearRing geometries available
from the WFS that lie within the specified extent.
Args:
* projection: :class:`cartopy.crs.Projection`
The projection in which the extent is specified and in
which the geometries should be returned. Only the default
(native) projection is supported.
* extent: four element tuple
(min_x, max_x, min_y, max_y) tuple defining the geographic extent
of the geometries to obtain.
Returns:
A list of Shapely geometries.
"""
if self.default_projection() != projection:
raise ValueError('Geometries are only available in projection '
'{!r}.'.format(self.default_projection()))
min_x, max_x, min_y, max_y = extent
response = self.service.getfeature(typename=self.features,
bbox=(min_x, min_y, max_x, max_y),
**self.getfeature_extra_kwargs)
geoms_by_srs = self._to_shapely_geoms(response)
if not geoms_by_srs:
geoms = []
elif len(geoms_by_srs) > 1:
raise ValueError('Unexpected response from the WFS server. The '
'geometries are in multiple SRSs, when only one '
'was expected.')
else:
srs, geoms = list(geoms_by_srs.items())[0]
# Attempt to verify the SRS associated with the geometries (if any)
# matches the specified projection.
if srs is not None:
if srs in _URN_TO_CRS:
geom_proj = _URN_TO_CRS[srs]
if geom_proj != projection:
raise ValueError('The geometries are not in expected '
'projection. Expected {!r}, got '
'{!r}.'.format(projection, geom_proj))
else:
msg = 'Unable to verify matching projections due ' \
'to incomplete mappings from SRS identifiers ' \
'to cartopy projections. The geometries have ' \
'an SRS of {!r}.'.format(srs)
warnings.warn(msg)
return geoms
def _to_shapely_geoms(self, response):
"""
Convert polygon coordinate strings in WFS response XML to Shapely
geometries.
Args:
* response: (file-like object)
WFS response XML data.
Returns:
A dictionary containing geometries, with key-value pairs of
the form {srsname: [geoms]}.
"""
linear_rings_data = []
linestrings_data = []
points_data = []
tree = ElementTree.parse(response)
for node in tree.findall('.//{}msGeometry'.format(_MAP_SERVER_NS)):
# Find LinearRing geometries in our msGeometry node.
find_str = './/{gml}LinearRing'.format(gml=_GML_NS)
if self._node_has_child(node, find_str):
data = self._find_polygon_coords(node, find_str)
linear_rings_data.extend(data)
# Find LineString geometries in our msGeometry node.
find_str = './/{gml}LineString'.format(gml=_GML_NS)
if self._node_has_child(node, find_str):
data = self._find_polygon_coords(node, find_str)
linestrings_data.extend(data)
# Find Point geometries in our msGeometry node.
find_str = './/{gml}Point'.format(gml=_GML_NS)
if self._node_has_child(node, find_str):
data = self._find_polygon_coords(node, find_str)
points_data.extend(data)
geoms_by_srs = {}
for srs, x, y in linear_rings_data:
geoms_by_srs.setdefault(srs, []).append(
sgeom.LinearRing(zip(x, y)))
for srs, x, y in linestrings_data:
geoms_by_srs.setdefault(srs, []).append(
sgeom.LineString(zip(x, y)))
for srs, x, y in points_data:
geoms_by_srs.setdefault(srs, []).append(
sgeom.Point(zip(x, y)))
return geoms_by_srs
def _find_polygon_coords(self, node, find_str):
"""
Return the x, y coordinate values for all the geometries in
a given`node`.
Args:
* node: :class:`xml.etree.ElementTree.Element`
Node of the parsed XML response.
* find_str: string
A search string used to match subelements that contain
the coordinates of interest, for example:
'.//{http://www.opengis.net/gml}LineString'
Returns:
A list of (srsName, x_vals, y_vals) tuples.
"""
data = []
for polygon in node.findall(find_str):
feature_srs = polygon.attrib.get('srsName')
x, y = [], []
# We can have nodes called `coordinates` or `coord`.
coordinates_find_str = '{}coordinates'.format(_GML_NS)
coords_find_str = '{}coord'.format(_GML_NS)
if self._node_has_child(polygon, coordinates_find_str):
points = polygon.findtext(coordinates_find_str)
coords = points.strip().split(' ')
for coord in coords:
x_val, y_val = coord.split(',')
x.append(float(x_val))
y.append(float(y_val))
elif self._node_has_child(polygon, coords_find_str):
for coord in polygon.findall(coords_find_str):
x.append(float(coord.findtext('{}X'.format(_GML_NS))))
y.append(float(coord.findtext('{}Y'.format(_GML_NS))))
else:
raise ValueError('Unable to find or parse coordinate values '
'from the XML.')
data.append((feature_srs, x, y))
return data
@staticmethod
def _node_has_child(node, find_str):
"""
Return whether `node` contains (at any sub-level), a node with name
equal to `find_str`.
"""
element = node.find(find_str)
return element is not None
Cartopy-0.14.2/lib/cartopy/io/shapereader.py 0000644 0013740 0002103 00000043745 12700747662 020472 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2011 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
"""
Combines the shapefile access of pyshp with the
geometry representation of shapely:
>>> import os.path
>>> import cartopy.io.shapereader as shapereader
>>> filename = natural_earth(resolution='110m',
... category='physical',
... name='geography_regions_points')
>>> reader = shapereader.Reader(filename)
>>> len(reader)
3
>>> records = list(reader.records())
>>> print(type(records[0]))
>>> print(sorted(records[0].attributes.keys()))
['comment', 'featurecla', 'lat_y', 'long_x', 'name', 'name_alt', \
'region', 'scalerank', 'subregion']
>>> print(records[0].attributes['name'])
Niagara Falls
>>> geoms = list(reader.geometries())
>>> print(type(geoms[0]))
"""
from __future__ import (absolute_import, division, print_function)
import glob
import itertools
import os
import shapely.geometry as sgeom
import shapefile
import six
from cartopy.io import Downloader
from cartopy import config
__all__ = ['Reader', 'Record']
def _create_point(shape):
return sgeom.Point(shape.points[0])
def _create_polyline(shape):
if not shape.points:
return sgeom.MultiLineString()
parts = list(shape.parts) + [None]
bounds = zip(parts[:-1], parts[1:])
lines = [shape.points[slice(lower, upper)] for lower, upper in bounds]
return sgeom.MultiLineString(lines)
def _create_polygon(shape):
if not shape.points:
return sgeom.MultiPolygon()
# Partition the shapefile rings into outer rings/polygons (clockwise) and
# inner rings/holes (anti-clockwise).
parts = list(shape.parts) + [None]
bounds = zip(parts[:-1], parts[1:])
outer_polygons_and_holes = []
inner_polygons = []
for lower, upper in bounds:
polygon = sgeom.Polygon(shape.points[slice(lower, upper)])
if polygon.exterior.is_ccw:
inner_polygons.append(polygon)
else:
outer_polygons_and_holes.append((polygon, []))
# Find the appropriate outer ring for each inner ring.
# aka. Group the holes with their containing polygons.
for inner_polygon in inner_polygons:
for outer_polygon, holes in outer_polygons_and_holes:
if outer_polygon.contains(inner_polygon):
holes.append(inner_polygon.exterior.coords)
break
polygon_defns = [(outer_polygon.exterior.coords, holes)
for outer_polygon, holes in outer_polygons_and_holes]
return sgeom.MultiPolygon(polygon_defns)
def _make_geometry(geometry_factory, shape):
geometry = None
if shape.shapeType != shapefile.NULL:
geometry = geometry_factory(shape)
return geometry
# The mapping from shapefile shapeType values to geometry creation functions.
GEOMETRY_FACTORIES = {
shapefile.POINT: _create_point,
shapefile.POLYLINE: _create_polyline,
shapefile.POLYGON: _create_polygon,
}
class Record(object):
"""
A single logical entry from a shapefile, combining the attributes with
their associated geometry.
"""
def __init__(self, shape, geometry_factory, attributes, fields):
self._shape = shape
self._geometry_factory = geometry_factory
self._bounds = None
# if the record defines a bbox, then use that for the shape's bounds,
# rather than using the full geometry in the bounds property
if hasattr(shape, 'bbox'):
self._bounds = tuple(shape.bbox)
self._geometry = False
"""The cached geometry instance for this Record."""
self.attributes = attributes
"""A dictionary mapping attribute names to attribute values."""
self._fields = fields
def __repr__(self):
return '>' % (self.geometry, self.attributes)
def __str__(self):
return 'Record(%s, %s, )' % (self.geometry, self.attributes)
@property
def bounds(self):
"""
The bounds of this Record's
:meth:`~Record.geometry`.
"""
if self._bounds is None:
self._bounds = self.geometry.bounds
return self._bounds
@property
def geometry(self):
"""
A shapely.geometry instance for this Record.
The geometry may be ``None`` if a null shape is defined in the
shapefile.
"""
if self._geometry is False:
self._geometry = _make_geometry(self._geometry_factory,
self._shape)
return self._geometry
class Reader(object):
"""
Provides an interface for accessing the contents of a shapefile.
The primary methods used on a Reader instance are
:meth:`~Reader.records` and :meth:`~Reader.geometries`.
"""
def __init__(self, filename):
# Validate the filename/shapefile
self._reader = reader = shapefile.Reader(filename)
if reader.shp is None or reader.shx is None or reader.dbf is None:
raise ValueError("Incomplete shapefile definition "
"in '%s'." % filename)
# Figure out how to make appropriate shapely geometry instances
shapeType = reader.shapeType
self._geometry_factory = GEOMETRY_FACTORIES.get(shapeType)
if self._geometry_factory is None:
raise ValueError('Unsupported shape type: %s' % shapeType)
self._fields = self._reader.fields
def __len__(self):
return self._reader.numRecords
def geometries(self):
"""
Returns an iterator of shapely geometries from the shapefile.
This interface is useful for accessing the geometries of the
shapefile where knowledge of the associated metadata is desired.
In the case where further metadata is needed use the
:meth:`~Reader.records`
interface instead, extracting the geometry from the record with the
:meth:`~Record.geometry` method.
"""
geometry_factory = self._geometry_factory
for i in range(self._reader.numRecords):
shape = self._reader.shape(i)
yield _make_geometry(geometry_factory, shape)
def records(self):
"""
Returns an iterator of :class:`~Record` instances.
"""
geometry_factory = self._geometry_factory
# Ignore the "DeletionFlag" field which always comes first
fields = self._reader.fields[1:]
field_names = [field[0] for field in fields]
for i in range(self._reader.numRecords):
shape_record = self._reader.shapeRecord(i)
attributes = dict(zip(field_names, shape_record.record))
yield Record(shape_record.shape, geometry_factory, attributes,
fields)
def natural_earth(resolution='110m', category='physical', name='coastline'):
"""
Returns the path to the requested natural earth shapefile,
downloading and unziping if necessary.
To identify valid components for this function, either browse
NaturalEarthData.com, or if you know what you are looking for, go to
https://github.com/nvkelso/natural-earth-vector/tree/master/zips to
see the actual files which will be downloaded.
.. note::
Some of the Natural Earth shapefiles have special features which are
described in the name. For example, the 110m resolution
"admin_0_countries" data also has a sibling shapefile called
"admin_0_countries_lakes" which excludes lakes in the country
outlines. For details of what is available refer to the Natural Earth
website, and look at the "download" link target to identify
appropriate names.
"""
# get hold of the Downloader (typically a NEShpDownloader instance)
# which we can then simply call its path method to get the appropriate
# shapefile (it will download if necessary)
ne_downloader = Downloader.from_config(('shapefiles', 'natural_earth',
resolution, category, name))
format_dict = {'config': config, 'category': category,
'name': name, 'resolution': resolution}
return ne_downloader.path(format_dict)
class NEShpDownloader(Downloader):
"""
Specialises :class:`cartopy.io.Downloader` to download the zipped
Natural Earth shapefiles and extract them to the defined location
(typically user configurable).
The keys which should be passed through when using the ``format_dict``
are typically ``category``, ``resolution`` and ``name``.
"""
FORMAT_KEYS = ('config', 'resolution', 'category', 'name')
# Define the NaturalEarth URL template. The natural earth website
# returns a 302 status if accessing directly, so we use the naciscdn
# URL directly.
_NE_URL_TEMPLATE = ('http://naciscdn.org/naturalearth/{resolution}'
'/{category}/ne_{resolution}_{name}.zip')
def __init__(self,
url_template=_NE_URL_TEMPLATE,
target_path_template=None,
pre_downloaded_path_template='',
):
# adds some NE defaults to the __init__ of a Downloader
Downloader.__init__(self, url_template,
target_path_template,
pre_downloaded_path_template)
def zip_file_contents(self, format_dict):
"""
Returns a generator of the filenames to be found in the downloaded
natural earth zip file.
"""
for ext in ['.shp', '.dbf', '.shx']:
yield ('ne_{resolution}_{name}'
'{extension}'.format(extension=ext, **format_dict))
def acquire_resource(self, target_path, format_dict):
"""
Downloads the zip file and extracts the files listed in
:meth:`zip_file_contents` to the target path.
"""
from zipfile import ZipFile
target_dir = os.path.dirname(target_path)
if not os.path.isdir(target_dir):
os.makedirs(target_dir)
url = self.url(format_dict)
shapefile_online = self._urlopen(url)
zfh = ZipFile(six.BytesIO(shapefile_online.read()), 'r')
for member_path in self.zip_file_contents(format_dict):
ext = os.path.splitext(member_path)[1]
target = os.path.splitext(target_path)[0] + ext
member = zfh.getinfo(member_path)
with open(target, 'wb') as fh:
fh.write(zfh.open(member).read())
shapefile_online.close()
zfh.close()
return target_path
@staticmethod
def default_downloader():
"""
Returns a generic, standard, NEShpDownloader instance.
Typically, a user will not need to call this staticmethod.
To find the path template of the NEShpDownloader:
>>> ne_dnldr = NEShpDownloader.default_downloader()
>>> print(ne_dnldr.target_path_template)
{config[data_dir]}/shapefiles/natural_earth/{category}/\
{resolution}_{name}.shp
"""
default_spec = ('shapefiles', 'natural_earth', '{category}',
'{resolution}_{name}.shp')
ne_path_template = os.path.join('{config[data_dir]}', *default_spec)
pre_path_template = os.path.join('{config[pre_existing_data_dir]}',
*default_spec)
return NEShpDownloader(target_path_template=ne_path_template,
pre_downloaded_path_template=pre_path_template)
# add a generic Natural Earth shapefile downloader to the config dictionary's
# 'downloaders' section.
_ne_key = ('shapefiles', 'natural_earth')
config['downloaders'].setdefault(_ne_key,
NEShpDownloader.default_downloader())
def gshhs(scale='c', level=1):
"""
Returns the path to the requested GSHHS shapefile,
downloading and unziping if necessary.
"""
# Get hold of the Downloader (typically a GSHHSShpDownloader instance)
# and call its path method to get the appropriate shapefile (it will
# download it if necessary).
gshhs_downloader = Downloader.from_config(('shapefiles', 'gshhs',
scale, level))
format_dict = {'config': config, 'scale': scale, 'level': level}
return gshhs_downloader.path(format_dict)
class GSHHSShpDownloader(Downloader):
"""
Specialises :class:`cartopy.io.Downloader` to download the zipped
GSHHS shapefiles and extract them to the defined location.
The keys which should be passed through when using the ``format_dict``
are ``scale`` (a single character indicating the resolution) and ``level``
(a number indicating the type of feature).
"""
FORMAT_KEYS = ('config', 'scale', 'level')
_GSHHS_URL_TEMPLATE = ('https://www.ngdc.noaa.gov/mgg/shorelines/data/'
'gshhs/oldversions/version2.2.0/'
'GSHHS_shp_2.2.0.zip')
def __init__(self,
url_template=_GSHHS_URL_TEMPLATE,
target_path_template=None,
pre_downloaded_path_template=''):
super(GSHHSShpDownloader, self).__init__(url_template,
target_path_template,
pre_downloaded_path_template)
def zip_file_contents(self, format_dict):
"""
Returns a generator of the filenames to be found in the downloaded
GSHHS zip file for the specified resource.
"""
for ext in ['.shp', '.dbf', '.shx']:
yield (os.path.join('GSHHS_shp', '{scale}',
'GSHHS_{scale}_L{level}{extension}'
).format(extension=ext, **format_dict))
def acquire_all_resources(self, format_dict):
from zipfile import ZipFile
# Download archive.
url = self.url(format_dict)
shapefile_online = self._urlopen(url)
zfh = ZipFile(six.BytesIO(shapefile_online.read()), 'r')
shapefile_online.close()
# Iterate through all scales and levels and extract relevant files.
modified_format_dict = dict(format_dict)
scales = ('c', 'l', 'i', 'h', 'f')
levels = (1, 2, 3, 4)
for scale, level in itertools.product(scales, levels):
modified_format_dict.update({'scale': scale, 'level': level})
target_path = self.target_path(modified_format_dict)
target_dir = os.path.dirname(target_path)
if not os.path.isdir(target_dir):
os.makedirs(target_dir)
for member_path in self.zip_file_contents(modified_format_dict):
ext = os.path.splitext(member_path)[1]
target = os.path.splitext(target_path)[0] + ext
member = zfh.getinfo(member_path)
with open(target, 'wb') as fh:
fh.write(zfh.open(member).read())
zfh.close()
def acquire_resource(self, target_path, format_dict):
"""
Downloads the zip file and extracts the files listed in
:meth:`zip_file_contents` to the target path.
.. note:
Because some of the GSHSS data is available with the cartopy
repository, scales of "l" or "c" will not be downloaded if they
exist in the ``cartopy.config['repo_data_dir']`` directory.
"""
repo_fname_pattern = os.path.join(config['repo_data_dir'],
'shapefiles', 'gshhs', '{scale}',
'GSHHS_{scale}_L?.shp')
repo_fname_pattern = repo_fname_pattern.format(**format_dict)
repo_fnames = glob.glob(repo_fname_pattern)
if repo_fnames:
assert len(repo_fnames) == 1, '>1 repo files found for GSHHS'
return repo_fnames[0]
self.acquire_all_resources(format_dict)
if not os.path.exists(target_path):
raise RuntimeError('Failed to download and extract GSHHS '
'shapefile to {!r}.'.format(target_path))
return target_path
@staticmethod
def default_downloader():
"""
Returns a GSHHSShpDownloader instance that expects (and if necessary
downloads and installs) shapefiles in the data directory of the
cartopy installation.
Typically, a user will not need to call this staticmethod.
To find the path template of the GSHHSShpDownloader:
>>> gshhs_dnldr = GSHHSShpDownloader.default_downloader()
>>> print(gshhs_dnldr.target_path_template)
{config[data_dir]}/shapefiles/gshhs/{scale}/\
GSHHS_{scale}_L{level}.shp
"""
default_spec = ('shapefiles', 'gshhs', '{scale}',
'GSHHS_{scale}_L{level}.shp')
gshhs_path_template = os.path.join('{config[data_dir]}',
*default_spec)
pre_path_tmplt = os.path.join('{config[pre_existing_data_dir]}',
*default_spec)
return GSHHSShpDownloader(target_path_template=gshhs_path_template,
pre_downloaded_path_template=pre_path_tmplt)
# Add a GSHHS shapefile downloader to the config dictionary's
# 'downloaders' section.
_gshhs_key = ('shapefiles', 'gshhs')
config['downloaders'].setdefault(_gshhs_key,
GSHHSShpDownloader.default_downloader())
Cartopy-0.14.2/lib/cartopy/io/srtm.npz 0000644 0013740 0002103 00000005132 12700747662 017337 0 ustar itpe avd 0000000 0000000 PK *ZG꽝, mask.npyݽ$5 `;)6[.p"ġ=2)xan\v]U./S7;Ӷ?xnOqݿ{w/||=<=o>><|/^Q'=`!ht>OIKLhC=9[:
#ɼdv>dzM!LeI7t$2lpLt%JgZ]^iu-~ФYGBIsqC-je,.znh4BsWD˘˰jlFN'ZfbrgAse;}-)ʘht$TZE{r-ɡ<Շ.*LZVtZ訚.nh!
:B*ch (Rήl
.Eq:
MB&4hEZP5x>|>t<:jUi}O.V~ʸ9Nػ֒ZADƅjZ6SqtKESS$4"55[FD3Yja@۾r c;OG:WF_sG/m-&\ n+tbEFvFV>O2:|r\A;VBV_TB+dd,M*)ڟ7AW܁yu6|m3=(=0JN*!є7B#+fZhh/$%&,]|Vޛ|+QG]λS
DfhF=Γkyuzh|d'EqE'dV@qg:qeQFKk6Wݘ?d933WpHp~TuqRtga
L2A_r'
"gx9Z$B'Yyrl=.%i3l<{S&^ѫ5AOyWY;>3E{Y7w)M"J9nEL^jۙvp^*&"տ(U_n_9GM8;a>zeJ̐pe (h\D̝UnT+A^BRjmc/D:>
{6bܞ@`?
%o9]CV bɫ1~\/U;p N}\oQ,7EGd[Oqj~d|qkS~.PwP1A]jhlU3Vw`ka:
f'|{I(ЂFwvSiGm{(ԍS}5,E/^Օ|XkxUQ!
6}eFv8߬7WMAh*MdIwkZ+
iΓ竸_"˖G;[٨ٿк倷
\ٮQ߉ۻ_wFI_Q-(RER-^B-Q{,FdCٱ(mKh"2.C.Dm4jh:ڃ9CDmӃmA6molSd:__mDoMmxcѝlNіц/mG}CkEڌ]7
< {2MDKϑ LM20>?;|4~)SmRm OAΊaq4qD[L܍D]w8sn- }L"qWZsB#~żҢ%r
sHu&gʽYFhÙb.JDkh0Rw5Q&Nzc.mt8ChMͳ%
+D_W6yq\v]ߝa3ofpԍ}Ћ9U<
=UM?T{h fL5gjK
~pqC Z+iF$$?ŊŘfj%lvJ6֊zdN!tO 1=GtVtt#E9K#[k/n+oq4MCk:?[[k`C/v3UylY10ꢣ`T0|E-o}D [a}@z=|a4ѫ7=yLk"MPK *ZG꽝, mask.npyPK 6
Cartopy-0.14.2/lib/cartopy/io/srtm.py 0000644 0013740 0002103 00000044076 12700747662 017172 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2011 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
"""
The Shuttle Radar Topography Mission (SRTM) is an international research
effort that obtained digital elevation models on a near-global scale from
56S to 60N, to generate the most complete high-resolution digital topographic
database of Earth prior to the release of the ASTER GDEM in 2009.
- Wikipedia (August 2012)
"""
from __future__ import (absolute_import, division, print_function)
import os
import warnings
import numpy as np
import six
from cartopy import config
import cartopy.crs as ccrs
from cartopy.io import fh_getter, Downloader, RasterSource, LocatedImage
class _SRTMSource(RasterSource):
"""
A source of SRTM data, which implements Cartopy's :ref:`RasterSource
interface `.
"""
def __init__(self, resolution, downloader, max_nx, max_ny):
"""
Parameters
==========
resolution : int
The resolution of SRTM to download, in arc-seconds. Data is
available at resolutions of 3 and 1 arc-seconds.
downloader : :class:`cartopy.io.Downloader` instance or None
The downloader to use for the SRTM dataset. If None, the
downloader will be taken using
:class:`cartopy.io.Downloader.from_config` with ('SRTM',
'SRTM{resolution}') as the target.
max_nx : int
The maximum number of x tiles to be combined when producing a
wider composite for this RasterSource.
max_ny : int
The maximum number of y tiles to be combined when producing a
taller composite for this RasterSource.
"""
if resolution == 3:
self._shape = (1201, 1201)
elif resolution == 1:
self._shape = (3601, 3601)
else:
raise ValueError(
'Resolution is an unexpected value ({}).'.format(resolution))
self._resolution = resolution
#: The CRS of the underlying SRTM data.
self.crs = ccrs.PlateCarree()
#: The Cartopy Downloader which can handle SRTM data. Normally, this
#: will be a :class:`SRTMDownloader` instance.
self.downloader = downloader
if self.downloader is None:
self.downloader = Downloader.from_config(
('SRTM', 'SRTM' + str(resolution)))
#: A tuple of (max_x_tiles, max_y_tiles).
self._max_tiles = (max_nx, max_ny)
def validate_projection(self, projection):
return projection == self.crs
def fetch_raster(self, projection, extent, target_resolution):
"""
Fetch SRTM elevation for the given projection and approximate extent.
"""
if not self.validate_projection(projection):
raise ValueError(
'Unsupported projection for the SRTM{} source.'.format(
self._resolution))
min_x, max_x, min_y, max_y = extent
min_x, min_y = np.floor([min_x, min_y])
nx = int(np.ceil(max_x) - min_x)
ny = int(np.ceil(max_y) - min_y)
skip = False
if nx > self._max_tiles[0]:
warnings.warn(
'Required SRTM{} tile count ({}) exceeds maximum ({}). '
'Increase max_nx limit.'.format(self._resolution, nx,
self._max_tiles[0]))
skip = True
if ny > self._max_tiles[1]:
warnings.warn(
'Required SRTM{} tile count ({}) exceeds maximum ({}). '
'Increase max_ny limit.'.format(self._resolution, ny,
self._max_tiles[1]))
skip = True
if skip:
return []
else:
img, _, extent = self.combined(min_x, min_y, nx, ny)
return [LocatedImage(np.flipud(img), extent)]
def srtm_fname(self, lon, lat):
"""
Return the filename for the given lon/lat SRTM tile (downloading if
necessary), or None if no such tile exists (i.e. the tile would be
entirely over water, or out of latitude range).
"""
if int(lon) != lon or int(lat) != lat:
raise ValueError('Integer longitude/latitude values required.')
x = '%s%03d' % ('E' if lon >= 0 else 'W', abs(int(lon)))
y = '%s%02d' % ('N' if lat >= 0 else 'S', abs(int(lat)))
srtm_downloader = Downloader.from_config(
('SRTM', 'SRTM' + str(self._resolution)))
params = {'config': config, 'resolution': self._resolution,
'x': x, 'y': y}
# If the URL doesn't exist then we are over sea/north/south of the
# limits of the SRTM data and we return None.
if srtm_downloader.url(params) is None:
return None
else:
return self.downloader.path(params)
def combined(self, lon_min, lat_min, nx, ny):
"""
Return an image and its extent for the group of nx by ny tiles
starting at the given bottom left location.
"""
bottom_left_ll = (lon_min, lat_min)
shape = np.array(self._shape)
img = np.zeros(shape * (ny, nx))
for i, j in np.ndindex(nx, ny):
x_img_slice = slice(i * shape[1], (i + 1) * shape[1])
y_img_slice = slice(j * shape[0], (j + 1) * shape[0])
try:
tile_img, _, _ = self.single_tile(bottom_left_ll[0] + i,
bottom_left_ll[1] + j)
except ValueError:
img[y_img_slice, x_img_slice] = 0
else:
img[y_img_slice, x_img_slice] = tile_img
extent = (bottom_left_ll[0], bottom_left_ll[0] + nx,
bottom_left_ll[1], bottom_left_ll[1] + ny)
return img, self.crs, extent
def single_tile(self, lon, lat):
fname = self.srtm_fname(lon, lat)
if fname is None:
raise ValueError('No srtm tile found for those coordinates.')
return read_SRTM(fname)
class SRTM3Source(_SRTMSource):
"""
A source of SRTM3 data, which implements Cartopy's :ref:`RasterSource
interface `.
"""
def __init__(self, downloader=None, max_nx=3, max_ny=3):
"""
Parameters
==========
downloader : :class:`cartopy.io.Downloader` instance or None
The downloader to use for the SRTM3 dataset. If None, the
downloader will be taken using
:class:`cartopy.io.Downloader.from_config` with ('SRTM', 'SRTM3')
as the target.
max_nx : int
The maximum number of x tiles to be combined when producing a
wider composite for this RasterSource.
max_ny : int
The maximum number of y tiles to be combined when producing a
taller composite for this RasterSource.
"""
super(SRTM3Source, self).__init__(resolution=3, downloader=downloader,
max_nx=max_nx, max_ny=max_ny)
class SRTM1Source(_SRTMSource):
"""
A source of SRTM1 data, which implements Cartopy's :ref:`RasterSource
interface `.
"""
def __init__(self, downloader=None, max_nx=3, max_ny=3):
"""
Parameters
==========
downloader : :class:`cartopy.io.Downloader` instance or None
The downloader to use for the SRTM1 dataset. If None, the
downloader will be taken using
:class:`cartopy.io.Downloader.from_config` with ('SRTM', 'SRTM1')
as the target.
max_nx : int
The maximum number of x tiles to be combined when producing a
wider composite for this RasterSource.
max_ny : int
The maximum number of y tiles to be combined when producing a
taller composite for this RasterSource.
"""
super(SRTM1Source, self).__init__(resolution=1, downloader=downloader,
max_nx=max_nx, max_ny=max_ny)
def srtm(lon, lat):
"""
Return (elevation, crs, extent) for the given longitude latitude.
Elevation is in meters.
"""
warnings.warn("This method has been deprecated. "
"See the \"What's new\" section for v0.12.")
return SRTM3Source().single_tile(lon, lat)
def add_shading(elevation, azimuth, altitude):
"""Adds shading to SRTM elevation data, using azimuth and altitude
of the sun.
:type elevation: numpy.ndarray
:param elevation: SRTM elevation data (in meters)
:type azimuth: float
:param azimuth: azimuth of the Sun (in degrees)
:type altitude: float
:param altitude: altitude of the Sun (in degrees)
:rtype: numpy.ndarray
:return: shaded SRTM relief map.
"""
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))
# -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)
return shaded
def fill_gaps(elevation, max_distance=10):
"""Fills gaps in SRTM elevation data for which the distance from
missing pixel to nearest existing one is smaller than `max_distance`.
This function requires osgeo/gdal to work.
:type elevation: numpy.ndarray
:param elevation: SRTM elevation data (in meters)
:type max_distance: int
:param max_distance: maximal distance (in pixels) between a missing point
and the nearest valid one.
:rtype: numpy.ndarray
:return: SRTM elevation data with filled gaps..
"""
warnings.warn("The fill_gaps function has been deprecated. "
"See the \"What's new\" section for v0.14.")
# Lazily import osgeo - it is only an optional dependency for cartopy.
from osgeo import gdal
from osgeo import gdal_array
src_ds = gdal_array.OpenArray(elevation)
srcband = src_ds.GetRasterBand(1)
dstband = srcband
maskband = srcband
smoothing_iterations = 0
options = []
gdal.FillNodata(dstband, maskband,
max_distance, smoothing_iterations, options,
callback=None)
elevation = dstband.ReadAsArray()
return elevation
def srtm_composite(lon_min, lat_min, nx, ny):
warnings.warn("This method has been deprecated. "
"See the \"What's new\" section for v0.12.")
return SRTM3Source().combined(lon_min, lat_min, nx, ny)
def read_SRTM(fh):
"""
Read the array of (y, x) elevation data from the given named file-handle.
Parameters
==========
fh : file-handle like
A named file-like as passed through to :func:`cartopy.io.fh_getter`.
The filename is used to determine the extent of the resulting array.
Returns
=======
elevation : numpy array
The elevation values from the SRTM file. Data is flipped vertically
such that the higher the y-index, the further north the data. Data
shape is automatically determined by the size of data read from file,
and is either (1201, 1201) for 3 arc-second data or (3601, 3601) for 1
arc-second data.
crs : :class:`cartopy.crs.CRS`
The coordinate reference system of the extents.
extents : 4-tuple (x0, x1, y0, y1)
The boundaries of the returned elevation array.
"""
fh, fname = fh_getter(fh, needs_filename=True)
if fname.endswith('.zip'):
from zipfile import ZipFile
zfh = ZipFile(fh, 'rb')
fh = zfh.open(os.path.basename(fname[:-4]), 'r')
elev = np.fromfile(fh, dtype=np.dtype('>i2'))
if elev.size == 12967201:
elev.shape = (3601, 3601)
elif elev.size == 1442401:
elev.shape = (1201, 1201)
else:
raise ValueError(
'Shape of SRTM data ({}) is unexpected.'.format(elev.size))
fname = os.path.basename(fname)
y_dir, y, x_dir, x = fname[0], int(fname[1:3]), fname[3], int(fname[4:7])
if y_dir == 'S':
y *= -1
if x_dir == 'W':
x *= -1
return elev[::-1, ...], ccrs.PlateCarree(), (x, x + 1, y, y + 1)
read_SRTM3 = read_SRTM
read_SRTM1 = read_SRTM
def SRTM3_retrieve(lon, lat):
"""
Return the path of a .hgt file for the given SRTM location.
If no such .hgt file exists (because it is over the ocean)
None will be returned.
"""
warnings.warn("This method has been deprecated. "
"See the \"What's new\" section for v0.12.")
return SRTM3Source().srtm_fname(lon, lat)
class SRTMDownloader(Downloader):
"""
Provides a SRTM download mechanism.
"""
FORMAT_KEYS = ('config', 'resolution', 'x', 'y')
_SRTM_BASE_URL = ('http://e4ftl01.cr.usgs.gov/SRTM/SRTMGL{resolution}.003/'
'2000.02.11/')
_SRTM_LOOKUP_CACHE = os.path.join(os.path.dirname(__file__),
'srtm.npz')
_SRTM_LOOKUP_MASK = np.load(_SRTM_LOOKUP_CACHE)['mask']
"""
The SRTM lookup mask determines whether keys such as 'N43E043' are
available to download.
"""
def __init__(self,
target_path_template,
pre_downloaded_path_template='',
):
# adds some SRTM defaults to the __init__ of a Downloader
# namely, the URL is determined on the fly using the
# ``SRTMDownloader._SRTM_LOOKUP_MASK`` array
Downloader.__init__(self, None,
target_path_template,
pre_downloaded_path_template)
def url(self, format_dict):
# override the url method, looking up the url from the
# ``SRTMDownloader._SRTM_LOOKUP_MASK`` array
lat = int(format_dict['y'][1:])
# Change to co-latitude.
if format_dict['y'][0] == 'N':
colat = 90 - lat
else:
colat = 90 + lat
lon = int(format_dict['x'][1:4])
# Ensure positive.
if format_dict['x'][0] == 'W':
lon = 360 - lon
if SRTMDownloader._SRTM_LOOKUP_MASK[lon, colat]:
return (SRTMDownloader._SRTM_BASE_URL +
u'{y}{x}.SRTMGL{resolution}.hgt.zip').format(**format_dict)
else:
return None
def acquire_resource(self, target_path, format_dict):
from zipfile import ZipFile
target_dir = os.path.dirname(target_path)
if not os.path.isdir(target_dir):
os.makedirs(target_dir)
url = self.url(format_dict)
srtm_online = self._urlopen(url)
zfh = ZipFile(six.BytesIO(srtm_online.read()), 'r')
zip_member_path = u'{y}{x}.hgt'.format(**format_dict)
member = zfh.getinfo(zip_member_path)
with open(target_path, 'wb') as fh:
fh.write(zfh.open(member).read())
srtm_online.close()
zfh.close()
return target_path
@staticmethod
def _create_srtm_mask(resolution, filename=None):
"""
Returns a NumPy mask of available lat/lon.
This is slow as it must query the SRTM server to identify the
continent from which the tile comes. Hence a NumPy file with this
content exists in ``SRTMDownloader._SRTM_LOOKUP_CACHE``.
The NumPy file was created with::
import cartopy.io.srtm as srtm
import numpy as np
np.savez_compressed(srtm.SRTMDownloader._SRTM_LOOKUP_CACHE,
mask=srtm.SRTMDownloader._create_srtm_mask(3))
"""
# lazy imports. In most situations, these are not
# dependencies of cartopy.
from bs4 import BeautifulSoup
if filename is None:
from six.moves.urllib.request import urlopen
url = SRTMDownloader._SRTM_BASE_URL.format(resolution=resolution)
with urlopen(url) as f:
html = f.read()
else:
with open(filename) as f:
html = f.read()
mask = np.zeros((360, 181), dtype=np.bool)
soup = BeautifulSoup(html)
for link in soup('a'):
name = str(link.text).strip()
if name[0] in 'NS' and name.endswith('.hgt.zip'):
lat = int(name[1:3])
# Change to co-latitude.
if name[0] == 'N':
colat = 90 - lat
else:
colat = 90 + lat
lon = int(name[4:7])
# Ensure positive.
if name[3] == 'W':
lon = 360 - lon
mask[lon, colat] = True
return mask
@classmethod
def default_downloader(cls):
"""
Returns a typical downloader for this class. In general, this static
method is used to create the default configuration in cartopy.config
"""
default_spec = ('SRTM', 'SRTMGL{resolution}', '{y}{x}.hgt')
target_path_template = os.path.join('{config[data_dir]}',
*default_spec)
pre_path_template = os.path.join('{config[pre_existing_data_dir]}',
*default_spec)
return cls(target_path_template=target_path_template,
pre_downloaded_path_template=pre_path_template)
# add a generic SRTM downloader to the config 'downloaders' section.
config['downloaders'].setdefault(('SRTM', 'SRTM3'),
SRTMDownloader.default_downloader())
config['downloaders'].setdefault(('SRTM', 'SRTM1'),
SRTMDownloader.default_downloader())
Cartopy-0.14.2/lib/cartopy/mpl/ 0000755 0013740 0002103 00000000000 12706121712 015765 5 ustar itpe avd 0000000 0000000 Cartopy-0.14.2/lib/cartopy/mpl/__init__.py 0000644 0013740 0002103 00000001437 12700747662 020117 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2011 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
from __future__ import (absolute_import, division, print_function)
Cartopy-0.14.2/lib/cartopy/mpl/clip_path.py 0000644 0013740 0002103 00000012314 12700747662 020317 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2013 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
from __future__ import (absolute_import, division, print_function)
import matplotlib.path as mpath
import numpy as np
def clip_path_python(subject, clip, point_inside_clip_path):
"""
Clip the subject path with the given clip path using the
Sutherland-Hodgman polygon clipping algorithm.
Args:
* subject - The subject path to be clipped. Must be a simple, single
polygon path with straight line segments only.
* clip - The clip path to use. Must be a simple, single
polygon path with straight line segments only.
* point_inside_clip_path - a point which can be found inside the clip path
polygon.
"""
inside_pt = point_inside_clip_path
output_verts = subject.vertices
for i in xrange(clip.vertices.shape[0] - 1):
clip_edge = clip.vertices[i:i + 2, :]
input_verts = output_verts
output_verts = []
inside = np.cross(clip_edge[1, :] - clip_edge[0, :],
inside_pt - clip_edge[0, :])
try:
s = input_verts[-1]
except IndexError:
break
for e in input_verts:
e_clip_cross = np.cross(clip_edge[1, :] - clip_edge[0, :],
e - clip_edge[0, :])
s_clip_cross = np.cross(clip_edge[1, :] - clip_edge[0, :],
s - clip_edge[0, :])
if np.sign(e_clip_cross) == np.sign(inside):
if np.sign(s_clip_cross) != np.sign(inside):
p = intersection_point(clip_edge[0, :], clip_edge[1, :],
e, s)
output_verts.append(p)
output_verts.append(e)
elif np.sign(s_clip_cross) == np.sign(inside):
p = intersection_point(clip_edge[0, :], clip_edge[1, :],
e, s)
output_verts.append(p)
s = e
if output_verts == []:
path = mpath.Path([[0, 0]], codes=[mpath.Path.MOVETO])
else:
# If the subject polygon was closed, then the return should be too.
if np.all(subject.vertices[0, :] == subject.vertices[-1, :]):
output_verts.append(output_verts[0])
path = mpath.Path(np.array(output_verts))
return path
def intersection_point(p0, p1, p2, p3):
"""
Return the intersection point of the two infinite lines that pass through
point p0->p1 and p2->p3 respectively.
"""
x_1, y_1 = p0
x_2, y_2 = p1
x_3, y_3 = p2
x_4, y_4 = p3
div = (x_1 - x_2) * (y_3 - y_4) - (y_1 - y_2) * (x_3 - x_4)
if div == 0:
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
return x, y
# Provide a clip_path function which clips the given path to the given Bbox.
# There is inbuilt mpl functionality with v1.2.1 and beyond, but we provide
# a shim here for older mpl versions.
if hasattr(mpath.Path, 'clip_to_bbox'):
def clip_path(subject, clip_bbox):
"""
Clip the given path to the given bounding box.
"""
return subject.clip_to_bbox(clip_bbox)
else:
def clip_path(subject, clip_bbox):
"""
Clip the given path to the given bounding box.
"""
# A shim on clip_path_python to support Bbox path clipping.
bbox_patch = bbox_to_path(clip_bbox)
bbox_center = ((clip_bbox.x0 + clip_bbox.x1) / 2,
(clip_bbox.y0 + clip_bbox.y1) / 2)
return clip_path_python(subject, bbox_patch, bbox_center)
def lines_intersect(p0, p1, p2, p3):
"""
Return whether the two lines defined by p0->p1 and p2->p3 intersect.
"""
x_1, y_1 = p0
x_2, y_2 = p1
x_3, y_3 = p2
x_4, y_4 = p3
return (x_1 - x_2) * (y_3 - y_4) - (y_1 - y_2) * (x_3 - x_4) != 0
cp1 = np.cross(p1 - p0, p2 - p0)
cp2 = np.cross(p1 - p0, p3 - p0)
return np.sign(cp1) == np.sign(cp2) and cp1 != 0
def bbox_to_path(bbox):
"""
Turn the given :class:`matplotlib.transforms.Bbox` instance into
a :class:`matplotlib.path.Path` instance.
"""
verts = np.array([[bbox.x0, bbox.y0], [bbox.x1, bbox.y0],
[bbox.x1, bbox.y1], [bbox.x0, bbox.y1],
[bbox.x0, bbox.y0]])
return mpath.Path(verts)
Cartopy-0.14.2/lib/cartopy/mpl/feature_artist.py 0000644 0013740 0002103 00000014443 12700747662 021402 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2011 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
"""
This module defines the :class:`FeatureArtist` class, for drawing
:class:`Feature` instances with matplotlib.
"""
from __future__ import (absolute_import, division, print_function)
import warnings
import weakref
import matplotlib.artist
import matplotlib.collections
import cartopy.mpl.patch as cpatch
class _GeomKey(object):
"""
Provide id() based equality and hashing for geometries.
Instances of this class must be treated as immutable for the caching
to operate correctly.
A workaround for Shapely polygons no longer being hashable as of 1.5.13.
"""
def __init__(self, geom):
self._id = id(geom)
def __eq__(self, other):
return self._id == other._id
def __hash__(self):
return hash(self._id)
class FeatureArtist(matplotlib.artist.Artist):
"""
A subclass of :class:`~matplotlib.artist.Artist` capable of
drawing a :class:`cartopy.feature.Feature`.
"""
_geom_key_to_geometry_cache = weakref.WeakValueDictionary()
"""
A mapping from _GeomKey to geometry to assist with the caching of
transformed matplotlib paths.
"""
_geom_key_to_path_cache = weakref.WeakKeyDictionary()
"""
A nested mapping from geometry (converted to a _GeomKey) and target
projection to the resulting transformed matplotlib paths::
{geom: {target_projection: list_of_paths}}
This provides a significant boost when producing multiple maps of the
same projection.
"""
def __init__(self, feature, **kwargs):
"""
Args:
* feature:
an instance of :class:`cartopy.feature.Feature` to draw.
* kwargs:
keyword arguments to be used when drawing the feature. These
will override those shared with the feature.
"""
super(FeatureArtist, self).__init__()
if kwargs is None:
kwargs = {}
self._kwargs = dict(kwargs)
# Set default zorder so that features are drawn before
# lines e.g. contours but after images.
# Note that the zorder of Patch, PatchCollection and PathCollection
# are all 1 by default. Assuming equal zorder drawing takes place in
# the following order: collections, patches, lines (default zorder=2),
# text (default zorder=3), then other artists e.g. FeatureArtist.
if self._kwargs.get('zorder') is not None:
self.set_zorder(self._kwargs['zorder'])
elif feature.kwargs.get('zorder') is not None:
self.set_zorder(feature.kwargs['zorder'])
else:
# The class attribute matplotlib.collections.PathCollection.zorder
# was removed after mpl v1.2.0, so the hard-coded value of 1 is
# used instead.
self.set_zorder(1)
self._feature = feature
@matplotlib.artist.allow_rasterization
def draw(self, renderer, *args, **kwargs):
"""
Draws the geometries of the feature that intersect with the extent of
the :class:`cartopy.mpl.GeoAxes` instance to which this
object has been added.
"""
if not self.get_visible():
return
ax = self.axes
feature_crs = self._feature.crs
# Get geometries that we need to draw.
extent = None
try:
extent = ax.get_extent(feature_crs)
except ValueError:
warnings.warn('Unable to determine extent. Defaulting to global.')
geoms = self._feature.intersecting_geometries(extent)
# Project (if necessary) and convert geometries to matplotlib paths.
paths = []
key = ax.projection
for geom in geoms:
# As Shapely geometries cannot be relied upon to be
# hashable, we have to use a WeakValueDictionary to manage
# their weak references. The key can then be a simple,
# "disposable", hashable geom-key object that just uses the
# id() of a geometry to determine equality and hash value.
# The only persistent, strong reference to the geom-key is
# in the WeakValueDictionary, so when the geometry is
# garbage collected so is the geom-key.
# The geom-key is also used to access the WeakKeyDictionary
# cache of transformed geometries. So when the geom-key is
# garbage collected so are the transformed geometries.
geom_key = _GeomKey(geom)
FeatureArtist._geom_key_to_geometry_cache.setdefault(
geom_key, geom)
mapping = FeatureArtist._geom_key_to_path_cache.setdefault(
geom_key, {})
geom_paths = mapping.get(key)
if geom_paths is None:
if ax.projection != feature_crs:
projected_geom = ax.projection.project_geometry(
geom, feature_crs)
else:
projected_geom = geom
geom_paths = cpatch.geos_to_path(projected_geom)
mapping[key] = geom_paths
paths.extend(geom_paths)
# Build path collection and draw it.
transform = ax.projection._as_mpl_transform(ax)
# Combine all the keyword args in priority order
final_kwargs = dict(self._feature.kwargs)
final_kwargs.update(self._kwargs)
final_kwargs.update(kwargs)
c = matplotlib.collections.PathCollection(paths,
transform=transform,
**final_kwargs)
c.set_clip_path(ax.patch)
c.set_figure(ax.figure)
return c.draw(renderer)
Cartopy-0.14.2/lib/cartopy/mpl/geoaxes.py 0000644 0013740 0002103 00000206440 12705644356 020015 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2011 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
"""
This module defines the :class:`GeoAxes` class, for use with matplotlib.
When a matplotlib figure contains a GeoAxes the plotting commands can transform
plot results from source coordinates to the GeoAxes' target projection.
"""
from __future__ import (absolute_import, division, print_function)
import collections
import contextlib
import warnings
import weakref
import matplotlib as mpl
import matplotlib.artist
import matplotlib.axes
from matplotlib.image import imread
import matplotlib.transforms as mtransforms
import matplotlib.patches as mpatches
import matplotlib.path as mpath
import matplotlib.ticker as mticker
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
import cartopy.img_transform
from cartopy.mpl.clip_path import clip_path
import cartopy.mpl.feature_artist as feature_artist
import cartopy.mpl.patch as cpatch
from cartopy.mpl.slippy_image_artist import SlippyImageArtist
from cartopy.vector_transform import vector_scalar_to_grid
assert matplotlib.__version__ >= '1.3', ('Cartopy is only supported with '
'matplotlib 1.3 or greater.')
_PATH_TRANSFORM_CACHE = weakref.WeakKeyDictionary()
"""
A nested mapping from path, source CRS, and target projection to the
resulting transformed paths::
{path: {(source_crs, target_projection): list_of_paths}}
Provides a significant performance boost for contours which, at
matplotlib 1.2.0 called transform_path_non_affine twice unnecessarily.
"""
# XXX call this InterCRSTransform
class InterProjectionTransform(mtransforms.Transform):
"""
Transforms coordinates from the source_projection to
the ``target_projection``.
"""
input_dims = 2
output_dims = 2
is_separable = False
has_inverse = True
def __init__(self, source_projection, target_projection):
"""
Create the transform object from the given projections.
Args:
* source_projection - A :class:`~cartopy.crs.CRS`.
* target_projection - A :class:`~cartopy.crs.CRS`.
"""
# assert target_projection is cartopy.crs.Projection
# assert source_projection is cartopy.crs.CRS
self.source_projection = source_projection
self.target_projection = target_projection
mtransforms.Transform.__init__(self)
def __repr__(self):
return ('< {!s} {!s} -> {!s} >'.format(self.__class__.__name__,
self.source_projection,
self.target_projection))
def transform_non_affine(self, xy):
"""
Transforms from source to target coordinates.
Args:
* xy - An (n,2) array of points in source coordinates.
Returns:
* An (n,2) array of transformed points in target coordinates.
"""
prj = self.target_projection
if isinstance(xy, np.ndarray):
return prj.transform_points(self.source_projection,
xy[:, 0], xy[:, 1])[:, 0:2]
else:
x, y = xy
x, y = prj.transform_point(x, y, self.source_projection)
return x, y
def transform_path_non_affine(self, src_path):
"""
Transforms from source to target coordinates.
Caches results, so subsequent calls with the same *src_path* argument
(and the same source and target projections) are faster.
Args:
* src_path - A matplotlib :class:`~matplotlib.path.Path` object
with vertices in source coordinates.
Returns
* A matplotlib :class:`~matplotlib.path.Path` with vertices
in target coordinates.
"""
mapping = _PATH_TRANSFORM_CACHE.get(src_path)
if mapping is not None:
key = (self.source_projection, self.target_projection)
result = mapping.get(key)
if result is not None:
return result
# Allow the vertices to be quickly transformed, if
# quick_vertices_transform allows it.
new_vertices = self.target_projection.quick_vertices_transform(
src_path.vertices, self.source_projection)
if new_vertices is not None:
if new_vertices is src_path.vertices:
return src_path
else:
return mpath.Path(new_vertices, src_path.codes)
if src_path.vertices.shape == (1, 2):
return mpath.Path(self.transform(src_path.vertices))
transformed_geoms = []
# Check whether this transform has the "force_path_ccw" attribute set.
# This is a cartopy extension to the Transform API to allow finer
# control of Path orientation handling (Path ordering is not important
# in matplotlib, but is in Cartopy).
geoms = cpatch.path_to_geos(src_path,
getattr(self, 'force_path_ccw', False))
for geom in geoms:
proj_geom = self.target_projection.project_geometry(
geom, self.source_projection)
transformed_geoms.append(proj_geom)
if not transformed_geoms:
result = mpath.Path(np.empty([0, 2]))
else:
paths = cpatch.geos_to_path(transformed_geoms)
if not paths:
return mpath.Path(np.empty([0, 2]))
points, codes = list(zip(*[cpatch.path_segments(path,
curves=False,
simplify=False)
for path in paths]))
result = mpath.Path(np.concatenate(points, 0),
np.concatenate(codes))
# store the result in the cache for future performance boosts
key = (self.source_projection, self.target_projection)
if mapping is None:
_PATH_TRANSFORM_CACHE[src_path] = {key: result}
else:
mapping[key] = result
return result
def inverted(self):
"""
Return a matplotlib :class:`~matplotlib.transforms.Transform`
from target to source coordinates.
"""
return InterProjectionTransform(self.target_projection,
self.source_projection)
class GeoAxes(matplotlib.axes.Axes):
"""
A subclass of :class:`matplotlib.axes.Axes` which represents a
map :class:`~cartopy.crs.Projection`.
This class replaces the matplotlib :class:`~matplotlib.axes.Axes` class
when created with the *projection* keyword. For example::
# Set up a standard map for latlon data.
geo_axes = pyplot.axes(projection=cartopy.crs.PlateCarree())
# Set up an OSGB map.
geo_axes = pyplot.subplot(2, 2, 1, projection=cartopy.crs.OSGB())
When a source projection is provided to one of it's plotting methods,
using the *transform* keyword, the standard matplotlib plot result is
transformed from source coordinates to the target projection. For example::
# Plot latlon data on an OSGB map.
pyplot.axes(projection=cartopy.crs.OSGB())
pyplot.contourf(x, y, data, transform=cartopy.crs.PlateCarree())
"""
def __init__(self, *args, **kwargs):
"""
Create a GeoAxes object using standard matplotlib
:class:`~matplotlib.axes.Axes` args and kwargs.
Kwargs:
* map_projection - The target :class:`~cartopy.crs.Projection` of
this Axes object.
All other args and keywords are passed through to
:class:`matplotlib.axes.Axes`.
"""
self.projection = kwargs.pop('map_projection')
"""The :class:`cartopy.crs.Projection` of this GeoAxes."""
self.outline_patch = None
"""The patch that provides the line bordering the projection."""
self.background_patch = None
"""The patch that provides the filled background of the projection."""
super(GeoAxes, self).__init__(*args, **kwargs)
self._gridliners = []
self.img_factories = []
self._done_img_factory = False
def add_image(self, factory, *args, **kwargs):
"""
Adds an image "factory" to the Axes.
Any image "factory" added, will be asked to retrieve an image
with associated metadata for a given bounding box at draw time.
The advantage of this approach is that the limits of the map
do not need to be known when adding the image factory, but can
be deferred until everything which can effect the limits has been
added.
Currently an image "factory" is just an object with
a ``image_for_domain`` method. Examples of image factories
are :class:`cartopy.io.img_nest.NestedImageCollection` and
:class:`cartopy.io.image_tiles.GoogleTiles`.
"""
if hasattr(factory, 'image_for_domain'):
# XXX TODO: Needs deprecating.
self.img_factories.append([factory, args, kwargs])
else:
# Args and kwargs not allowed.
assert not bool(args) and not bool(kwargs)
image = factory
try:
super(GeoAxes, self).add_image(image)
except AttributeError:
# If add_image method doesn't exist (only available from
# v1.4 onwards) we implement it ourselves.
self._set_artist_props(image)
self.images.append(image)
image._remove_method = lambda h: self.images.remove(h)
return image
@contextlib.contextmanager
def hold_limits(self, hold=True):
"""
Keep track of the original view and data limits for the life of this
context manager, optionally reverting any changes back to the original
values after the manager exits.
Parameters
----------
hold : bool (default True)
Whether to revert the data and view limits after the context
manager exits.
"""
data_lim = self.dataLim.frozen().get_points()
view_lim = self.viewLim.frozen().get_points()
other = (self.ignore_existing_data_limits,
self._autoscaleXon, self._autoscaleYon)
try:
yield
finally:
if hold:
self.dataLim.set_points(data_lim)
self.viewLim.set_points(view_lim)
(self.ignore_existing_data_limits,
self._autoscaleXon, self._autoscaleYon) = other
@matplotlib.artist.allow_rasterization
def draw(self, renderer=None, inframe=False):
"""
Extends the standard behaviour of :func:`matplotlib.axes.Axes.draw`.
Draws grid lines and image factory results before invoking standard
matplotlib drawing. A global range is used if no limits have yet
been set.
"""
# If data has been added (i.e. autoscale hasn't been turned off)
# then we should autoscale the view.
if self.get_autoscale_on() and self.ignore_existing_data_limits:
self.autoscale_view()
if self.outline_patch.reclip or self.background_patch.reclip:
clipped_path = clip_path(self.outline_patch.orig_path,
self.viewLim)
self.outline_patch._path = clipped_path
self.background_patch._path = clipped_path
for gl in self._gridliners:
gl._draw_gridliner(background_patch=self.background_patch)
self._gridliners = []
# XXX This interface needs a tidy up:
# image drawing on pan/zoom;
# caching the resulting image;
# buffering the result by 10%...;
if not self._done_img_factory:
for factory, args, kwargs in self.img_factories:
img, extent, origin = factory.image_for_domain(
self._get_extent_geom(factory.crs), args[0])
self.imshow(img, extent=extent, origin=origin,
transform=factory.crs, *args[1:], **kwargs)
self._done_img_factory = True
return matplotlib.axes.Axes.draw(self, renderer=renderer,
inframe=inframe)
def __str__(self):
return '< GeoAxes: %s >' % self.projection
def cla(self):
"""Clears the current axes and adds boundary lines."""
result = matplotlib.axes.Axes.cla(self)
self.xaxis.set_visible(False)
self.yaxis.set_visible(False)
# Enable tight autoscaling.
self._tight = True
self.set_aspect('equal')
with self.hold_limits():
self._boundary()
# XXX consider a margin - but only when the map is not global...
# self._xmargin = 0.15
# self._ymargin = 0.15
self.dataLim.intervalx = self.projection.x_limits
self.dataLim.intervaly = self.projection.y_limits
return result
def format_coord(self, x, y):
"""Return a string formatted for the matplotlib GUI status bar."""
lon, lat = ccrs.Geodetic().transform_point(x, y, self.projection)
ns = 'N' if lat >= 0.0 else 'S'
ew = 'E' if lon >= 0.0 else 'W'
return u'%.4g, %.4g (%f\u00b0%s, %f\u00b0%s)' % (x, y, abs(lat),
ns, abs(lon), ew)
def coastlines(self, resolution='110m', color='black', **kwargs):
"""
Adds coastal **outlines** to the current axes from the Natural Earth
"coastline" shapefile collection.
Kwargs:
* resolution - a named resolution to use from the Natural Earth
dataset. Currently can be one of "110m", "50m", and
"10m".
.. note::
Currently no clipping is done on the coastlines before adding
them to the axes. This means, if very high resolution coastlines
are being used, performance is likely to be severely effected.
This should be resolved transparently by v0.5.
"""
kwargs['edgecolor'] = color
kwargs['facecolor'] = 'none'
feature = cartopy.feature.NaturalEarthFeature('physical', 'coastline',
resolution, **kwargs)
return self.add_feature(feature)
def tissot(self, rad_km=5e5, lons=None, lats=None, n_samples=80, **kwargs):
"""
Adds Tissot's indicatrices to the axes.
Kwargs:
* rad_km - The radius in km of the the circles to be drawn.
* lons - A numpy.ndarray, list or tuple of longitude values that
locate the centre of each circle. Specifying more than one
dimension allows individual points to be drawn whereas a
1D array produces a grid of points.
* lats - A numpy.ndarray, list or tuple of latitude values that
that locate the centre of each circle. See lons.
* n_samples - Integer number of points sampled around the
circumference of each circle.
**kwargs are passed through to `class:ShapelyFeature`.
"""
from cartopy import geodesic
geod = geodesic.Geodesic()
geoms = []
if lons is None:
lons = np.linspace(-180, 180, 6, endpoint=False)
else:
lons = np.asarray(lons)
if lats is None:
lats = np.linspace(-80, 80, 6)
else:
lats = np.asarray(lats)
if lons.ndim == 1 or lats.ndim == 1:
lons, lats = np.meshgrid(lons, lats)
lons, lats = lons.flatten(), lats.flatten()
if lons.shape != lats.shape:
raise ValueError('lons and lats must have the same shape.')
for i in range(len(lons)):
circle = geod.circle(lons[i], lats[i], rad_km,
n_samples=n_samples)
geoms.append(sgeom.Polygon(circle))
feature = cartopy.feature.ShapelyFeature(geoms, ccrs.Geodetic(),
**kwargs)
return self.add_feature(feature)
def natural_earth_shp(self, name='land', resolution='110m',
category='physical', **kwargs):
"""
Adds the geometries from the specified Natural Earth shapefile to the
Axes as a :class:`~matplotlib.collections.PathCollection`.
``**kwargs`` are passed through to the
:class:`~matplotlib.collections.PathCollection` constructor.
Returns the created :class:`~matplotlib.collections.PathCollection`.
.. note::
Currently no clipping is done on the geometries before adding them
to the axes. This means, if very high resolution geometries are
being used, performance is likely to be severely effected. This
should be resolved transparently by v0.5.
"""
warnings.warn('This method has been deprecated.'
' Please use `add_feature` instead.')
kwargs.setdefault('edgecolor', 'face')
kwargs.setdefault('facecolor', cartopy.feature.COLORS['land'])
feature = cartopy.feature.NaturalEarthFeature(category, name,
resolution, **kwargs)
return self.add_feature(feature)
def add_feature(self, feature, **kwargs):
"""
Adds the given :class:`~cartopy.feature.Feature` instance to the axes.
Args:
* feature:
An instance of :class:`~cartopy.feature.Feature`.
Kwargs:
Keyword arguments to be used when drawing the feature. This allows
standard matplotlib control over aspects such as 'facecolor',
'alpha', etc.
Returns:
* A :class:`cartopy.mpl.feature_artist.FeatureArtist`
instance responsible for drawing the feature.
"""
# Instantiate an artist to draw the feature and add it to the axes.
artist = feature_artist.FeatureArtist(feature, **kwargs)
return self.add_artist(artist)
def add_geometries(self, geoms, crs, **kwargs):
"""
Add the given shapely geometries (in the given crs) to the axes.
Args:
* geoms:
A collection of shapely geometries.
* crs:
The cartopy CRS in which the provided geometries are defined.
Kwargs:
Keyword arguments to be used when drawing this feature.
Returns:
A :class:`cartopy.mpl.feature_artist.FeatureArtist`
instance responsible for drawing the geometries.
"""
feature = cartopy.feature.ShapelyFeature(geoms, crs, **kwargs)
return self.add_feature(feature)
def get_extent(self, crs=None):
"""
Get the extent (x0, x1, y0, y1) of the map in the given coordinate
system.
If no crs is given, the returned extents' coordinate system will be
the CRS of this Axes.
"""
p = self._get_extent_geom(crs)
r = p.bounds
x1, y1, x2, y2 = r
return x1, x2, y1, y2
def _get_extent_geom(self, crs=None):
# Perform the calculations for get_extent(), which just repackages it.
with self.hold_limits():
if self.get_autoscale_on():
self.autoscale_view()
[x1, y1], [x2, y2] = self.viewLim.get_points()
domain_in_src_proj = sgeom.Polygon([[x1, y1], [x2, y1],
[x2, y2], [x1, y2],
[x1, y1]])
# Determine target projection based on requested CRS.
if crs is None:
proj = self.projection
elif isinstance(crs, ccrs.Projection):
proj = crs
else:
# Attempt to select suitable projection for
# non-projection CRS.
if isinstance(crs, ccrs.RotatedGeodetic):
proj = ccrs.RotatedPole(crs.proj4_params['lon_0'] - 180,
crs.proj4_params['o_lat_p'])
warnings.warn('Approximating coordinate system {!r} with a '
'RotatedPole projection.'.format(crs))
elif hasattr(crs, 'is_geodetic') and crs.is_geodetic():
proj = ccrs.PlateCarree(crs.globe)
warnings.warn('Approximating coordinate system {!r} with the '
'PlateCarree projection.'.format(crs))
else:
raise ValueError('Cannot determine extent in'
' coordinate system {!r}'.format(crs))
# Calculate intersection with boundary and project if necesary.
boundary_poly = sgeom.Polygon(self.projection.boundary)
if proj != self.projection:
# Erode boundary by threshold to avoid transform issues.
# This is a workaround for numerical issues at the boundary.
eroded_boundary = boundary_poly.buffer(-self.projection.threshold)
geom_in_src_proj = eroded_boundary.intersection(
domain_in_src_proj)
geom_in_crs = proj.project_geometry(geom_in_src_proj,
self.projection)
else:
geom_in_crs = boundary_poly.intersection(domain_in_src_proj)
return geom_in_crs
def set_extent(self, extents, crs=None):
"""
Set the extent (x0, x1, y0, y1) of the map in the given
coordinate system.
If no crs is given, the extents' coordinate system will be assumed
to be the Geodetic version of this axes' projection.
"""
# TODO: Implement the same semantics as plt.xlim and
# plt.ylim - allowing users to set None for a minimum and/or
# maximum value
x1, x2, y1, y2 = extents
domain_in_crs = sgeom.polygon.LineString([[x1, y1], [x2, y1],
[x2, y2], [x1, y2],
[x1, y1]])
projected = None
# Sometimes numerical issues cause the projected vertices of the
# requested extents to appear outside the projection domain.
# This results in an empty geometry, which has an empty `bounds`
# tuple, which causes an unpack error.
# This workaround avoids using the projection when the requested
# extents are obviously the same as the projection domain.
try_workaround = ((crs is None and
isinstance(self.projection, ccrs.PlateCarree)) or
crs == self.projection)
if try_workaround:
boundary = self.projection.boundary
if boundary.equals(domain_in_crs):
projected = boundary
if projected is None:
projected = self.projection.project_geometry(domain_in_crs, crs)
try:
# This might fail with an unhelpful error message ('need more
# than 0 values to unpack') if the specified extents fall outside
# the projection extents, so try and give a better error message.
x1, y1, x2, y2 = projected.bounds
except ValueError:
msg = ('Failed to determine the required bounds in projection '
'coordinates. Check that the values provided are within '
'the valid range (x_limits=[{xlim[0]}, {xlim[1]}], '
'y_limits=[{ylim[0]}, {ylim[1]}]).')
raise ValueError(msg.format(xlim=self.projection.x_limits,
ylim=self.projection.y_limits))
self.set_xlim([x1, x2])
self.set_ylim([y1, y2])
def set_global(self):
"""
Set the extent of the Axes to the limits of the projection.
.. note::
In some cases where the projection has a limited sensible range
the ``set_global`` method does not actually make the whole globe
visible. Instead, the most appropriate extents will be used (e.g.
Ordnance Survey UK will set the extents to be around the British
Isles.
"""
self.set_xlim(self.projection.x_limits)
self.set_ylim(self.projection.y_limits)
def set_xticks(self, ticks, minor=False, crs=None):
"""
Set the x ticks.
Args:
* ticks - list of floats denoting the desired position of x ticks.
Kwargs:
* minor - boolean flag indicating whether the ticks should be minor
ticks i.e. small and unlabelled (default is False).
* crs - An instance of :class:`~cartopy.crs.CRS` indicating the
coordinate system of the provided tick values. If no
coordinate system is specified then the values are assumed
to be in the coordinate system of the projection.
Only transformations from one rectangular coordinate system
to another rectangular coordinate system are supported.
.. note::
This interface is subject to change whilst functionality is added
to support other map projections.
"""
# Project ticks if crs differs from axes' projection
if crs is not None and crs != self.projection:
if not isinstance(crs, (ccrs._RectangularProjection,
ccrs.Mercator)) or \
not isinstance(self.projection,
(ccrs._RectangularProjection,
ccrs.Mercator)):
raise RuntimeError('Cannot handle non-rectangular coordinate '
'systems.')
proj_xyz = self.projection.transform_points(crs,
np.asarray(ticks),
np.zeros(len(ticks)))
xticks = proj_xyz[..., 0]
else:
xticks = ticks
# Switch on drawing of x axis
self.xaxis.set_visible(True)
return super(GeoAxes, self).set_xticks(xticks, minor)
def set_yticks(self, ticks, minor=False, crs=None):
"""
Set the y ticks.
Args:
* ticks - list of floats denoting the desired position of y ticks.
Kwargs:
* minor - boolean flag indicating whether the ticks should be minor
ticks i.e. small and unlabelled (default is False).
* crs - An instance of :class:`~cartopy.crs.CRS` indicating the
coordinate system of the provided tick values. If no
coordinate system is specified then the values are assumed
to be in the coordinate system of the projection.
Only transformations from one rectangular coordinate system
to another rectangular coordinate system are supported.
.. note::
This interface is subject to change whilst functionality is added
to support other map projections.
"""
# Project ticks if crs differs from axes' projection
if crs is not None and crs != self.projection:
if not isinstance(crs, (ccrs._RectangularProjection,
ccrs.Mercator)) or \
not isinstance(self.projection,
(ccrs._RectangularProjection,
ccrs.Mercator)):
raise RuntimeError('Cannot handle non-rectangular coordinate '
'systems.')
proj_xyz = self.projection.transform_points(crs,
np.zeros(len(ticks)),
np.asarray(ticks))
yticks = proj_xyz[..., 1]
else:
yticks = ticks
# Switch on drawing of y axis
self.yaxis.set_visible(True)
return super(GeoAxes, self).set_yticks(yticks, minor)
def stock_img(self, name='ne_shaded'):
"""
Add a standard image to the map.
Currently, the only (and default) option is a downsampled version of
the Natural Earth shaded relief raster.
"""
if name == 'ne_shaded':
import os
source_proj = ccrs.PlateCarree()
fname = os.path.join(config["repo_data_dir"],
'raster', 'natural_earth',
'50-natural-earth-1-downsampled.png')
return self.imshow(imread(fname), origin='upper',
transform=source_proj,
extent=[-180, 180, -90, 90])
else:
raise ValueError('Unknown stock image %r.' % name)
def add_raster(self, raster_source, **slippy_image_kwargs):
"""
Add the given raster source to the GeoAxes.
Parameters
----------
raster_source : :class:`cartopy.io.RasterSource` like instance
``raster_source`` may be any object which implements the
RasterSource interface, including instances of objects such as
:class:`~cartopy.io.ogc_clients.WMSRasterSource` and
:class:`~cartopy.io.ogc_clients.WMTSRasterSource`. Note that image
retrievals are done at draw time, not at creation time.
"""
# Allow a fail-fast error if the raster source cannot provide
# images in the current projection.
raster_source.validate_projection(self.projection)
img = SlippyImageArtist(self, raster_source, **slippy_image_kwargs)
with self.hold_limits():
self.add_image(img)
return img
def _regrid_shape_aspect(self, regrid_shape, target_extent):
"""
Helper for setting regridding shape which is used in several
plotting methods.
"""
if not isinstance(regrid_shape, collections.Sequence):
target_size = int(regrid_shape)
x_range, y_range = np.diff(target_extent)[::2]
desired_aspect = x_range / y_range
if x_range >= y_range:
regrid_shape = (target_size * desired_aspect, target_size)
else:
regrid_shape = (target_size, target_size / desired_aspect)
return regrid_shape
def imshow(self, img, *args, **kwargs):
"""
Add the "transform" keyword to :func:`~matplotlib.pyplot.imshow'.
Parameters
----------
transform : :class:`~cartopy.crs.Projection` or matplotlib transform
The coordinate system in which the given image is rectangular.
regrid_shape : int or pair of ints
The shape of the desired image if it needs to be transformed.
If a single integer is given then that will be used as the minimum
length dimension, while the other dimension will be scaled up
according to the target extent's aspect ratio. The default is for
the minimum dimension of a transformed image to have length 750,
so for an image being transformed into a global PlateCarree
projection the resulting transformed image would have a shape of
``(750, 1500)``.
extent : tuple
The corner coordinates of the image in the form
``(left, right, bottom, top)``. The coordinates should be in the
coordinate system passed to the transform keyword.
origin : {'lower', 'upper'}
The origin of the vertical pixels. See
:func:`matplotlib.pyplot.imshow` for further details. Default
is ``'lower'``.
"""
transform = kwargs.pop('transform', None)
if 'update_datalim' in kwargs:
raise ValueError('The update_datalim keyword has been removed in '
'imshow. To hold the data and view limits see '
'GeoAxes.hold_limits.')
kwargs.setdefault('origin', 'lower')
same_projection = (isinstance(transform, ccrs.Projection) and
self.projection == transform)
if transform is None or transform == self.transData or same_projection:
if isinstance(transform, ccrs.Projection):
transform = transform._as_mpl_transform(self)
result = matplotlib.axes.Axes.imshow(self, img, *args, **kwargs)
else:
extent = kwargs.pop('extent', None)
img = np.asanyarray(img)
if kwargs['origin'] == 'upper':
# It is implicitly assumed by the regridding operation that the
# origin of the image is 'lower', so simply adjust for that
# here.
img = img[::-1]
kwargs['origin'] = 'lower'
if not isinstance(transform, ccrs.Projection):
raise ValueError('Expected a projection subclass. Cannot '
'handle a %s in imshow.' % type(transform))
target_extent = self.get_extent(self.projection)
regrid_shape = kwargs.pop('regrid_shape', 750)
regrid_shape = self._regrid_shape_aspect(regrid_shape,
target_extent)
warp_array = cartopy.img_transform.warp_array
img, extent = warp_array(img,
source_proj=transform,
source_extent=extent,
target_proj=self.projection,
target_res=regrid_shape,
target_extent=target_extent,
mask_extrapolated=True,
)
# As a workaround to a matplotlib limitation, turn any images
# which are RGB with a mask into RGBA images with an alpha
# channel.
if (isinstance(img, np.ma.MaskedArray) and
img.shape[2:3] == (3, ) and
img.mask is not False):
old_img = img
img = np.zeros(img.shape[:2] + (4, ), dtype=img.dtype)
img[:, :, 0:3] = old_img
# Put an alpha channel in if the image was masked.
img[:, :, 3] = ~ np.any(old_img.mask, axis=2)
if img.dtype.kind == 'u':
img[:, :, 3] *= 255
result = matplotlib.axes.Axes.imshow(self, img, *args,
extent=extent, **kwargs)
# clip the image. This does not work as the patch moves with mouse
# movement, but the clip path doesn't
# This could definitely be fixed in matplotlib
# if result.get_clip_path() in [None, self.patch]:
# # image does not already have clipping set, clip to axes patch
# result.set_clip_path(self.outline_patch)
return result
def gridlines(self, crs=None, draw_labels=False, xlocs=None,
ylocs=None, **kwargs):
"""
Automatically adds gridlines to the axes, in the given coordinate
system, at draw time.
Kwargs:
* crs
The :class:`cartopy._crs.CRS` defining the coordinate system in
which gridlines are drawn.
Default is :class:`cartopy.crs.PlateCarree`.
* draw_labels
Label gridlines like axis ticks, around the edge.
* xlocs
An iterable of gridline locations or a
:class:`matplotlib.ticker.Locator` instance which will be used to
determine the locations of the gridlines in the x-coordinate of
the given CRS. Defaults to None, which implies automatic locating
of the gridlines.
* ylocs
An iterable of gridline locations or a
:class:`matplotlib.ticker.Locator` instance which will be used to
determine the locations of the gridlines in the y-coordinate of
the given CRS. Defaults to None, which implies automatic locating
of the gridlines.
Returns:
A :class:`cartopy.mpl.gridliner.Gridliner` instance.
All other keywords control line properties. These are passed through
to :class:`matplotlib.collections.Collection`.
"""
if crs is None:
crs = ccrs.PlateCarree()
from cartopy.mpl.gridliner import Gridliner
if xlocs is not None and not isinstance(xlocs, mticker.Locator):
xlocs = mticker.FixedLocator(xlocs)
if ylocs is not None and not isinstance(ylocs, mticker.Locator):
ylocs = mticker.FixedLocator(ylocs)
gl = Gridliner(
self, crs=crs, draw_labels=draw_labels, xlocator=xlocs,
ylocator=ylocs, collection_kwargs=kwargs)
self._gridliners.append(gl)
return gl
def _gen_axes_spines(self, locations=None, offset=0.0, units='inches'):
# generate some axes spines, as some Axes super class machinery
# requires them. Just make them invisible
spines = matplotlib.axes.Axes._gen_axes_spines(self,
locations=locations,
offset=offset,
units=units)
for spine in spines.values():
spine.set_visible(False)
return spines
def _boundary(self):
"""
Adds the map's boundary to this GeoAxes, attaching the appropriate
artists to :data:`.outline_patch` and :data:`.background_patch`.
.. note::
The boundary is not the ``axes.patch``. ``axes.patch``
is made invisible by this method - its only remaining
purpose is to provide a rectilinear clip patch for
all Axes artists.
"""
# Hide the old "background" patch used by matplotlib - it is not
# used by cartopy's GeoAxes.
self.patch.set_facecolor((1, 1, 1, 0))
self.patch.set_edgecolor((0.5, 0.5, 0.5))
self.patch.set_visible(False)
self.background_patch = None
self.outline_patch = None
path, = cpatch.geos_to_path(self.projection.boundary)
# Get the outline path in terms of self.transData
proj_to_data = self.projection._as_mpl_transform(self) - self.transData
trans_path = proj_to_data.transform_path(path)
# Set the boundary - we can make use of the rectangular clipping.
self.set_boundary(trans_path, use_as_clip_path=False)
# Attach callback events for when the xlim or ylim are changed. This
# is what triggers the patches to be re-clipped at draw time.
self.callbacks.connect('xlim_changed', _trigger_patch_reclip)
self.callbacks.connect('ylim_changed', _trigger_patch_reclip)
def set_boundary(self, path, transform=None, use_as_clip_path=True):
"""
Given a path, update the :data:`.outline_patch` and
:data:`.background_patch` to take its shape.
Parameters
----------
path : :class:`matplotlib.path.Path`
The path of the desired boundary.
transform : None or :class:`matplotlib.transforms.Transform`
The coordinate system of the given path. Currently this must be
convertible to data coordinates, and therefore cannot extend beyond
the limits of the axes' projection.
use_as_clip_path : bool
Whether axes.patch should be updated. Updating axes.patch means
that any artists subsequently created will inherit clipping from
this path, rather than the standard unit square in axes
coordinates.
"""
if transform is None:
transform = self.transData
if isinstance(transform, cartopy.crs.CRS):
transform = transform._as_mpl_transform(self)
if self.background_patch is None:
background = matplotlib.patches.PathPatch(path, edgecolor='none',
facecolor='white',
zorder=-1, clip_on=False,
transform=transform)
else:
background = matplotlib.patches.PathPatch(path, zorder=-1,
clip_on=False)
background.update_from(self.background_patch)
self.background_patch.remove()
background.set_transform(transform)
if self.outline_patch is None:
outline = matplotlib.patches.PathPatch(path, edgecolor='black',
facecolor='none',
zorder=2.5, clip_on=False,
transform=transform)
else:
outline = matplotlib.patches.PathPatch(path, zorder=2.5,
clip_on=False)
outline.update_from(self.outline_patch)
self.outline_patch.remove()
outline.set_transform(transform)
# Attach the original path to the patches. This will be used each time
# a new clipped path is calculated.
outline.orig_path = path
background.orig_path = path
# Attach a "reclip" attribute, which determines if the patch's path is
# reclipped before drawing. A callback is used to change the "reclip"
# state.
outline.reclip = True
background.reclip = True
# Add the patches to the axes, and also make them available as
# attributes.
self.background_patch = background
self.outline_patch = outline
if use_as_clip_path:
self.patch = background
with self.hold_limits():
self.add_patch(outline)
self.add_patch(background)
def contour(self, *args, **kwargs):
"""
Add the "transform" keyword to :func:`~matplotlib.pyplot.contour'.
Extra kwargs:
transform - a :class:`~cartopy.crs.Projection`.
"""
t = kwargs.get('transform', None)
if t is None:
t = self.projection
if isinstance(t, ccrs.CRS) and not isinstance(t, ccrs.Projection):
raise ValueError('invalid transform:'
' Spherical contouring is not supported - '
' consider using PlateCarree/RotatedPole.')
if isinstance(t, ccrs.Projection):
kwargs['transform'] = t._as_mpl_transform(self)
else:
kwargs['transform'] = t
result = matplotlib.axes.Axes.contour(self, *args, **kwargs)
self.autoscale_view()
return result
def contourf(self, *args, **kwargs):
"""
Add the "transform" keyword to :func:`~matplotlib.pyplot.contourf'.
Extra kwargs:
transform - a :class:`~cartopy.crs.Projection`.
"""
t = kwargs.get('transform', None)
if t is None:
t = self.projection
if isinstance(t, ccrs.CRS) and not isinstance(t, ccrs.Projection):
raise ValueError('invalid transform:'
' Spherical contouring is not supported - '
' consider using PlateCarree/RotatedPole.')
if isinstance(t, ccrs.Projection):
kwargs['transform'] = t = t._as_mpl_transform(self)
else:
kwargs['transform'] = t
# Set flag to indicate correcting orientation of paths if not ccw
if isinstance(t, mtransforms.Transform):
for sub_trans, _ in t._iter_break_from_left_to_right():
if isinstance(sub_trans, InterProjectionTransform):
if not hasattr(sub_trans, 'force_path_ccw'):
sub_trans.force_path_ccw = True
result = matplotlib.axes.Axes.contourf(self, *args, **kwargs)
# We need to compute the dataLim correctly for contours.
if matplotlib.__version__ >= '1.4':
extent = mtransforms.Bbox.union([col.get_datalim(self.transData)
for col in result.collections])
self.dataLim.update_from_data_xy(extent.get_points())
self.autoscale_view()
return result
def scatter(self, *args, **kwargs):
"""
Add the "transform" keyword to :func:`~matplotlib.pyplot.scatter'.
Extra kwargs:
transform - a :class:`~cartopy.crs.Projection`.
"""
t = kwargs.get('transform', None)
# Keep this bit - even at mpl v1.2
if t is None:
t = self.projection
if hasattr(t, '_as_mpl_transform'):
kwargs['transform'] = t._as_mpl_transform(self)
# exclude Geodetic as a vaild source CS
if (isinstance(kwargs.get('transform', None),
InterProjectionTransform) and
kwargs['transform'].source_projection.is_geodetic()):
raise ValueError('Cartopy cannot currently do spherical '
'contouring. The source CRS cannot be a '
'geodetic, consider using the cyllindrical form '
'(PlateCarree or RotatedPole).')
result = matplotlib.axes.Axes.scatter(self, *args, **kwargs)
self.autoscale_view()
return result
def pcolormesh(self, *args, **kwargs):
"""
Add the "transform" keyword to :func:`~matplotlib.pyplot.pcolormesh'.
Extra kwargs:
transform - a :class:`~cartopy.crs.Projection`.
"""
t = kwargs.get('transform', None)
if t is None:
t = self.projection
if isinstance(t, ccrs.CRS) and not isinstance(t, ccrs.Projection):
raise ValueError('invalid transform:'
' Spherical pcolormesh is not supported - '
' consider using PlateCarree/RotatedPole.')
kwargs.setdefault('transform', t)
result = self._pcolormesh_patched(*args, **kwargs)
self.autoscale_view()
return result
def _pcolormesh_patched(self, *args, **kwargs):
"""
A temporary, modified duplicate of
:func:`~matplotlib.pyplot.pcolormesh'.
This function contains a workaround for a matplotlib issue
and will be removed once the issue has been resolved.
https://github.com/matplotlib/matplotlib/pull/1314
"""
import warnings
import numpy as np
import numpy.ma as ma
import matplotlib as mpl
import matplotlib.cbook as cbook
import matplotlib.colors as mcolors
import matplotlib.cm as cm
from matplotlib import docstring
import matplotlib.transforms as transforms
import matplotlib.artist as artist
from matplotlib.artist import allow_rasterization
import matplotlib.backend_bases as backend_bases
import matplotlib.path as mpath
import matplotlib.mlab as mlab
import matplotlib.collections as mcoll
if not self._hold:
self.cla()
alpha = kwargs.pop('alpha', None)
norm = kwargs.pop('norm', None)
cmap = kwargs.pop('cmap', None)
vmin = kwargs.pop('vmin', None)
vmax = kwargs.pop('vmax', None)
shading = kwargs.pop('shading', 'flat').lower()
antialiased = kwargs.pop('antialiased', False)
kwargs.setdefault('edgecolors', 'None')
allmatch = (shading == 'gouraud')
X, Y, C = self._pcolorargs('pcolormesh', *args, allmatch=allmatch)
Ny, Nx = X.shape
# convert to one dimensional arrays
C = C.ravel()
X = X.ravel()
Y = Y.ravel()
coords = np.zeros(((Nx * Ny), 2), dtype=float)
coords[:, 0] = X
coords[:, 1] = Y
collection = mcoll.QuadMesh(
Nx - 1, Ny - 1, coords,
antialiased=antialiased, shading=shading, **kwargs)
collection.set_alpha(alpha)
collection.set_array(C)
if norm is not None:
assert(isinstance(norm, mcolors.Normalize))
collection.set_cmap(cmap)
collection.set_norm(norm)
collection.set_clim(vmin, vmax)
collection.autoscale_None()
self.grid(False)
# Transform from native to data coordinates?
t = collection._transform
if (not isinstance(t, mtransforms.Transform) and
hasattr(t, '_as_mpl_transform')):
t = t._as_mpl_transform(self.axes)
if t and any(t.contains_branch_seperately(self.transData)):
trans_to_data = t - self.transData
pts = np.vstack([X, Y]).T.astype(np.float)
transformed_pts = trans_to_data.transform(pts)
X = transformed_pts[..., 0]
Y = transformed_pts[..., 1]
########################
# PATCH
# XXX Non-standard matplotlib thing.
no_inf = (X != np.inf) & (Y != np.inf)
X = X[no_inf]
Y = Y[no_inf]
# END OF PATCH
##############
minx = np.amin(X)
maxx = np.amax(X)
miny = np.amin(Y)
maxy = np.amax(Y)
corners = (minx, miny), (maxx, maxy)
########################
# PATCH
# XXX Non-standard matplotlib thing.
collection._corners = mtransforms.Bbox(corners)
collection.get_datalim = lambda transData: collection._corners
# END OF PATCH
##############
self.update_datalim(corners)
self.add_collection(collection)
self.autoscale_view()
########################
# PATCH
# XXX Non-standard matplotlib thing.
# Handle a possible wrap around for rectangular projections.
t = kwargs.get('transform', None)
if isinstance(t, ccrs.CRS):
wrap_proj_types = (ccrs._RectangularProjection,
ccrs._WarpedRectangularProjection,
ccrs.InterruptedGoodeHomolosine,
ccrs.Mercator)
if isinstance(t, wrap_proj_types) and \
isinstance(self.projection, wrap_proj_types):
C = C.reshape((Ny - 1, Nx - 1))
transformed_pts = transformed_pts.reshape((Ny, Nx, 2))
# compute the vertical line angles of the pcolor in
# transformed coordinates
with np.errstate(invalid='ignore'):
horizontal_vert_angles = np.arctan2(
np.diff(transformed_pts[..., 0], axis=1),
np.diff(transformed_pts[..., 1], axis=1)
)
# if the change in angle is greater than 90 degrees (absolute),
# then mark it for masking later on.
dx_horizontal = np.diff(horizontal_vert_angles)
to_mask = ((np.abs(dx_horizontal) > np.pi / 2) |
np.isnan(dx_horizontal))
if np.any(to_mask):
if collection.get_cmap()._rgba_bad[3] != 0.0:
warnings.warn("The colormap's 'bad' has been set, but "
"in order to wrap pcolormesh across the "
"map it must be fully transparent.")
# at this point C has a shape of (Ny-1, Nx-1), to_mask has
# a shape of (Ny, Nx-2) and pts has a shape of (Ny*Nx, 2)
mask = np.zeros(C.shape, dtype=np.bool)
# mask out the neighbouring cells if there was a cell
# found with an angle change of more than pi/2 . NB.
# Masking too much only has a detrimental impact on
# performance.
to_mask_y_shift = to_mask[:-1, :]
mask[:, :-1][to_mask_y_shift] = True
mask[:, 1:][to_mask_y_shift] = True
to_mask_x_shift = to_mask[1:, :]
mask[:, :-1][to_mask_x_shift] = True
mask[:, 1:][to_mask_x_shift] = True
C_mask = getattr(C, 'mask', None)
if C_mask is not None:
dmask = mask | C_mask
else:
dmask = mask
# create the masked array to be used with this pcolormesh
pcolormesh_data = np.ma.array(C, mask=mask)
collection.set_array(pcolormesh_data.ravel())
# now that the pcolormesh has masked the bad values,
# create a pcolor with just those values that were masked
pcolor_data = pcolormesh_data.copy()
# invert the mask
pcolor_data.mask = ~pcolor_data.mask
# remember to re-apply the original data mask to the array
if C_mask is not None:
pcolor_data.mask = pcolor_data.mask | C_mask
pts = pts.reshape((Ny, Nx, 2))
if np.any(~pcolor_data.mask):
# plot with slightly lower zorder to avoid odd issue
# where the main plot is obscured
zorder = collection.zorder - .1
kwargs.pop('zorder', None)
kwargs.setdefault('snap', False)
pcolor_col = self.pcolor(pts[..., 0], pts[..., 1],
pcolor_data, zorder=zorder,
**kwargs)
pcolor_col.set_cmap(cmap)
pcolor_col.set_norm(norm)
pcolor_col.set_clim(vmin, vmax)
# scale the data according to the *original* data
pcolor_col.norm.autoscale_None(C)
# put the pcolor_col on the pcolormesh collection so
# that if really necessary, users can do things post
# this method
collection._wrapped_collection_fix = pcolor_col
# Clip the QuadMesh to the projection boundary, which is required
# to keep the shading inside the projection bounds.
collection.set_clip_path(self.outline_patch)
# END OF PATCH
##############
return collection
def pcolor(self, *args, **kwargs):
"""
Add the "transform" keyword to :func:`~matplotlib.pyplot.pcolor'.
Extra kwargs:
transform - a :class:`~cartopy.crs.Projection`.
"""
t = kwargs.get('transform', None)
if t is None:
t = self.projection
if isinstance(t, ccrs.CRS) and not isinstance(t, ccrs.Projection):
raise ValueError('invalid transform:'
' Spherical pcolor is not supported - '
' consider using PlateCarree/RotatedPole.')
kwargs.setdefault('transform', t)
result = matplotlib.axes.Axes.pcolor(self, *args, **kwargs)
# Update the datalim for this pcolor.
limits = result.get_datalim(self.axes.transData)
self.axes.update_datalim(limits)
self.autoscale_view()
return result
def quiver(self, x, y, u, v, *args, **kwargs):
"""
Plot a field of arrows.
Extra Kwargs:
* transform: :class:`cartopy.crs.Projection` or matplotlib transform
The coordinate system in which the vectors are defined.
* regrid_shape: int or 2-tuple of ints
If given, specifies that the points where the arrows are
located will be interpolated onto a regular grid in
projection space. If a single integer is given then that
will be used as the minimum grid length dimension, while the
other dimension will be scaled up according to the target
extent's aspect ratio. If a pair of ints are given they
determine the grid length in the x and y directions
respectively.
* target_extent: 4-tuple
If given, specifies the extent in the target CRS that the
regular grid defined by *regrid_shape* will have. Defaults
to the current extent of the map projection.
See :func:`matplotlib.pyplot.quiver` for details on arguments
and other keyword arguments.
.. note::
The vector components must be defined as grid eastward and
grid northward.
"""
t = kwargs.get('transform', None)
if t is None:
t = self.projection
if isinstance(t, ccrs.CRS) and not isinstance(t, ccrs.Projection):
raise ValueError('invalid transform:'
' Spherical quiver is not supported - '
' consider using PlateCarree/RotatedPole.')
if isinstance(t, ccrs.Projection):
kwargs['transform'] = t._as_mpl_transform(self)
else:
kwargs['transform'] = t
regrid_shape = kwargs.pop('regrid_shape', None)
target_extent = kwargs.pop('target_extent',
self.get_extent(self.projection))
if regrid_shape is not None:
# If regridding is required then we'll be handling transforms
# manually and plotting in native coordinates.
regrid_shape = self._regrid_shape_aspect(regrid_shape,
target_extent)
if args:
# Interpolate color array as well as vector components.
x, y, u, v, c = vector_scalar_to_grid(
t, self.projection, regrid_shape, x, y, u, v, args[0],
target_extent=target_extent)
args = (c,) + args[1:]
else:
x, y, u, v = vector_scalar_to_grid(
t, self.projection, regrid_shape, x, y, u, v,
target_extent=target_extent)
kwargs.pop('transform', None)
elif t != self.projection:
# Transform the vectors if the projection is not the same as the
# data transform.
if (x.ndim == 1 and y.ndim == 1) and (x.shape != u.shape):
x, y = np.meshgrid(x, y)
u, v = self.projection.transform_vectors(t, x, y, u, v)
return matplotlib.axes.Axes.quiver(self, x, y, u, v, *args, **kwargs)
def barbs(self, x, y, u, v, *args, **kwargs):
"""
Plot a 2-D field of barbs.
Extra Kwargs:
* transform: :class:`cartopy.crs.Projection` or matplotlib transform
The coordinate system in which the vectors are defined.
* regrid_shape: int or 2-tuple of ints
If given, specifies that the points where the arrows are
located will be interpolated onto a regular grid in
projection space. If a single integer is given then that
will be used as the minimum grid length dimension, while the
other dimension will be scaled up according to the target
extent's aspect ratio. If a pair of ints are given they
determine the grid length in the x and y directions
respectively.
* target_extent: 4-tuple
If given, specifies the extent in the target CRS that the
regular grid defined by *regrid_shape* will have. Defaults
to the current extent of the map projection.
See :func:`matplotlib.pyplot.barbs` for details on arguments
and keyword arguments.
.. note::
The vector components must be defined as grid eastward and
grid northward.
"""
t = kwargs.get('transform', None)
if t is None:
t = self.projection
if isinstance(t, ccrs.CRS) and not isinstance(t, ccrs.Projection):
raise ValueError('invalid transform:'
' Spherical barbs are not supported - '
' consider using PlateCarree/RotatedPole.')
if isinstance(t, ccrs.Projection):
kwargs['transform'] = t._as_mpl_transform(self)
else:
kwargs['transform'] = t
regrid_shape = kwargs.pop('regrid_shape', None)
target_extent = kwargs.pop('target_extent',
self.get_extent(self.projection))
if regrid_shape is not None:
# If regridding is required then we'll be handling transforms
# manually and plotting in native coordinates.
regrid_shape = self._regrid_shape_aspect(regrid_shape,
target_extent)
if args:
# Interpolate color array as well as vector components.
x, y, u, v, c = vector_scalar_to_grid(
t, self.projection, regrid_shape, x, y, u, v, args[0],
target_extent=target_extent)
args = (c,) + args[1:]
else:
x, y, u, v = vector_scalar_to_grid(
t, self.projection, regrid_shape, x, y, u, v,
target_extent=target_extent)
kwargs.pop('transform', None)
elif t != self.projection:
# Transform the vectors if the projection is not the same as the
# data transform.
if x.ndim == 1 and y.ndim == 1:
x, y = np.meshgrid(x, y)
u, v = self.projection.transform_vectors(t, x, y, u, v)
return matplotlib.axes.Axes.barbs(self, x, y, u, v, *args, **kwargs)
def streamplot(self, x, y, u, v, **kwargs):
"""
Draws streamlines of a vector flow.
Extra Kwargs:
* transform: :class:`cartopy.crs.Projection` or matplotlib transform
The coordinate system in which the vector field is defined.
See :func:`matplotlib.pyplot.streamplot` for details on arguments
and keyword arguments.
.. note::
The vector components must be defined as grid eastward and
grid northward.
"""
t = kwargs.pop('transform', None)
if t is None:
t = self.projection
if isinstance(t, ccrs.CRS) and not isinstance(t, ccrs.Projection):
raise ValueError('invalid transform:'
' Spherical streamplot is not supported - '
' consider using PlateCarree/RotatedPole.')
# Regridding is required for streamplot, it must have an evenly spaced
# grid to work correctly. Choose our destination grid based on the
# density keyword. The grid need not be bigger than the grid used by
# the streamplot integrator.
density = kwargs.get('density', 1)
if np.isscalar(density):
regrid_shape = [int(30 * density)] * 2
else:
regrid_shape = [int(25 * d) for d in density]
# The color and linewidth keyword arguments can be arrays so they will
# need to be gridded also.
c = kwargs.get('color', None)
l = kwargs.get('linewidth', None)
scalars = []
color_array = isinstance(c, np.ndarray)
linewidth_array = isinstance(l, np.ndarray)
if color_array:
scalars.append(c)
if linewidth_array:
scalars.append(l)
# Do the regridding including any scalar fields.
target_extent = self.get_extent(self.projection)
gridded = vector_scalar_to_grid(t, self.projection, regrid_shape,
x, y, u, v, *scalars,
target_extent=target_extent)
x, y, u, v = gridded[:4]
# If scalar fields were regridded then replace the appropriate keyword
# arguments with the gridded arrays.
scalars = list(gridded[4:])
if linewidth_array:
kwargs['linewidth'] = scalars.pop()
if color_array:
kwargs['color'] = ma.masked_invalid(scalars.pop())
with warnings.catch_warnings():
# The workaround for nan values in streamplot colors gives rise to
# a warning which is not at all important so it is hidden from the
# user to avoid confusion.
message = 'Warning: converting a masked element to nan.'
warnings.filterwarnings('ignore', message=message,
category=UserWarning)
sp = matplotlib.axes.Axes.streamplot(self, x, y, u, v, **kwargs)
return sp
def add_wmts(self, wmts, layer_name, **kwargs):
"""
Add the specified WMTS layer to the axes.
This function requires owslib and PIL to work.
Args:
* wmts - The URL of the WMTS, or an
owslib.wmts.WebMapTileService instance.
* layer_name - The name of the layer to use.
All other keywords are passed through to the construction of the
image artist. See :meth:`~matplotlib.axes.Axes.imshow()` for
more details.
"""
from cartopy.io.ogc_clients import WMTSRasterSource
wmts = WMTSRasterSource(wmts, layer_name)
return self.add_raster(wmts, **kwargs)
def add_wms(self, wms, layers, wms_kwargs=None, **kwargs):
"""
Add the specified WMS layer to the axes.
This function requires owslib and PIL to work.
Parameters
----------
wms : string or :class:`owslib.wms.WebMapService` instance
The web map service URL or owslib WMS instance to use.
layers : string or iterable of string
The name of the layer(s) to use.
wms_kwargs : dict or None
Passed through to the
:class:`~cartopy.io.ogc_clients.WMSRasterSource`
constructor's ``getmap_extra_kwargs`` for defining getmap time
keyword arguments.
All other keywords are passed through to the construction of the
image artist. See :meth:`~matplotlib.axes.Axes.imshow()` for
more details.
"""
from cartopy.io.ogc_clients import WMSRasterSource
wms = WMSRasterSource(wms, layers, getmap_extra_kwargs=wms_kwargs)
return self.add_raster(wms, **kwargs)
# Define the GeoAxesSubplot class, so that a type(ax) will emanate from
# cartopy.mpl.geoaxes, not matplotlib.axes.
class GeoAxesSubplot(matplotlib.axes.SubplotBase, GeoAxes):
_axes_class = GeoAxes
try:
matplotlib.axes._subplots._subplot_classes[GeoAxes] = GeoAxesSubplot
except AttributeError:
matplotlib.axes._subplot_classes[GeoAxes] = GeoAxesSubplot
def _trigger_patch_reclip(event):
"""
Defines an event callback for a GeoAxes which forces the outline and
background patches to be re-clipped next time they are drawn.
"""
axes = event.axes
# trigger the outline and background patches to be re-clipped
axes.outline_patch.reclip = True
axes.background_patch.reclip = True
Cartopy-0.14.2/lib/cartopy/mpl/gridliner.py 0000644 0013740 0002103 00000042462 12700747662 020342 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2011 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
from __future__ import (absolute_import, division, print_function)
import matplotlib
import matplotlib.collections as mcollections
import matplotlib.text as mtext
import matplotlib.ticker as mticker
import matplotlib.transforms as mtrans
import numpy as np
import six
import cartopy
from cartopy.crs import Projection, _RectangularProjection
degree_locator = mticker.MaxNLocator(nbins=9, steps=[1, 2, 3, 6, 15, 18])
_DEGREE_SYMBOL = u'\u00B0'
def _fix_lons(lons):
"""
Fix the given longitudes into the range ``[-180, 180]``.
"""
lons = np.array(lons, copy=False, ndmin=1)
fixed_lons = ((lons + 180) % 360) - 180
# Make the positive 180s positive again.
fixed_lons[(fixed_lons == -180) & (lons > 0)] *= -1
return fixed_lons
def _lon_heimisphere(longitude):
"""Return the hemisphere (E, W or '' for 0) for the given longitude."""
longitude = _fix_lons(longitude)
if longitude > 0:
hemisphere = 'E'
elif longitude < 0:
hemisphere = 'W'
else:
hemisphere = ''
return hemisphere
def _lat_heimisphere(latitude):
"""Return the hemisphere (N, S or '' for 0) for the given latitude."""
if latitude > 0:
hemisphere = 'N'
elif latitude < 0:
hemisphere = 'S'
else:
hemisphere = ''
return hemisphere
def _east_west_formatted(longitude, num_format='g'):
fmt_string = u'{longitude:{num_format}}{degree}{hemisphere}'
return fmt_string.format(longitude=abs(longitude), num_format=num_format,
hemisphere=_lon_heimisphere(longitude),
degree=_DEGREE_SYMBOL)
def _north_south_formatted(latitude, num_format='g'):
fmt_string = u'{latitude:{num_format}}{degree}{hemisphere}'
return fmt_string.format(latitude=abs(latitude), num_format=num_format,
hemisphere=_lat_heimisphere(latitude),
degree=_DEGREE_SYMBOL)
#: A formatter which turns longitude values into nice longitudes such as 110W
LONGITUDE_FORMATTER = mticker.FuncFormatter(lambda v, pos:
_east_west_formatted(v))
#: A formatter which turns longitude values into nice longitudes such as 45S
LATITUDE_FORMATTER = mticker.FuncFormatter(lambda v, pos:
_north_south_formatted(v))
class Gridliner(object):
# NOTE: In future, one of these objects will be add-able to a GeoAxes (and
# maybe even a plain old mpl axes) and it will call the "_draw_gridliner"
# method on draw. This will enable automatic gridline resolution
# determination on zoom/pan.
def __init__(self, axes, crs, draw_labels=False, xlocator=None,
ylocator=None, collection_kwargs=None):
"""
Object used by :meth:`cartopy.mpl.geoaxes.GeoAxes.gridlines`
to add gridlines and tick labels to a map.
Args:
* axes
The :class:`cartopy.mpl.geoaxes.GeoAxes` object to be drawn on.
* crs
The :class:`cartopy.crs.CRS` defining the coordinate system that
the gridlines are drawn in.
* draw_labels
Toggle whether to draw labels. For finer control, attributes of
:class:`Gridliner` may be modified individually.
* xlocator
A :class:`matplotlib.ticker.Locator` instance which will be used
to determine the locations of the gridlines in the x-coordinate of
the given CRS. Defaults to None, which implies automatic locating
of the gridlines.
* ylocator
A :class:`matplotlib.ticker.Locator` instance which will be used
to determine the locations of the gridlines in the y-coordinate of
the given CRS. Defaults to None, which implies automatic locating
of the gridlines.
* collection_kwargs
Dictionary controlling line properties, passed to
:class:`matplotlib.collections.Collection`.
"""
self.axes = axes
#: The :class:`~matplotlib.ticker.Locator` to use for the x
#: gridlines and labels.
self.xlocator = xlocator or degree_locator
#: The :class:`~matplotlib.ticker.Locator` to use for the y
#: gridlines and labels.
self.ylocator = ylocator or degree_locator
#: The :class:`~matplotlib.ticker.Formatter` to use for the x labels.
self.xformatter = mticker.ScalarFormatter()
self.xformatter.create_dummy_axis()
#: The :class:`~matplotlib.ticker.Formatter` to use for the y labels.
self.yformatter = mticker.ScalarFormatter()
self.yformatter.create_dummy_axis()
#: Whether to draw labels on the top of the map.
self.xlabels_top = draw_labels
#: Whether to draw labels on the bottom of the map.
self.xlabels_bottom = draw_labels
#: Whether to draw labels on the left hand side of the map.
self.ylabels_left = draw_labels
#: Whether to draw labels on the right hand side of the map.
self.ylabels_right = draw_labels
#: Whether to draw the x gridlines.
self.xlines = True
#: Whether to draw the y gridlines.
self.ylines = True
#: A dictionary passed through to ``ax.text`` on x label creation
#: for styling of the text labels.
self.xlabel_style = {}
#: A dictionary passed through to ``ax.text`` on y label creation
#: for styling of the text labels.
self.ylabel_style = {}
self.crs = crs
# if the user specifies tick labels at this point, check if they can
# be drawn. The same check will take place at draw time in case
# public attributes are changed after instantiation.
if draw_labels:
self._assert_can_draw_ticks()
#: The number of interpolation points which are used to draw the
#: gridlines.
self.n_steps = 30
#: A dictionary passed through to
#: ``matplotlib.collections.LineCollection`` on grid line creation.
self.collection_kwargs = collection_kwargs
#: The x gridlines which were created at draw time.
self.xline_artists = []
#: The y gridlines which were created at draw time.
self.yline_artists = []
#: The x labels which were created at draw time.
self.xlabel_artists = []
#: The y labels which were created at draw time.
self.ylabel_artists = []
def _crs_transform(self):
"""
Get the drawing transform for our gridlines.
.. note::
this depends on the transform of our 'axes', so it may change
dynamically.
"""
transform = self.crs
if not isinstance(transform, mtrans.Transform):
transform = transform._as_mpl_transform(self.axes)
return transform
def _add_gridline_label(self, value, axis, upper_end):
"""
Create a Text artist on our axes for a gridline label.
Args:
* value
Coordinate value of this gridline. The text contains this
value, and is positioned centred at that point.
* axis
which axis the label is on: 'x' or 'y'.
* upper_end
If True, place at the maximum of the "other" coordinate (Axes
coordinate == 1.0). Else 'lower' end (Axes coord = 0.0).
"""
transform = self._crs_transform()
shift_dist_points = 5 # A margin from the map edge.
if upper_end is False:
shift_dist_points = -shift_dist_points
if axis == 'x':
x = value
y = 1.0 if upper_end else 0.0
h_align = 'center'
v_align = 'bottom' if upper_end else 'top'
tr_x = transform
tr_y = self.axes.transAxes + \
mtrans.ScaledTranslation(
0.0,
shift_dist_points * (1.0 / 72),
self.axes.figure.dpi_scale_trans)
str_value = self.xformatter(value)
user_label_style = self.xlabel_style
elif axis == 'y':
y = value
x = 1.0 if upper_end else 0.0
v_align = 'center'
h_align = 'left' if upper_end else 'right'
tr_y = transform
tr_x = self.axes.transAxes + \
mtrans.ScaledTranslation(
shift_dist_points * (1.0 / 72),
0.0,
self.axes.figure.dpi_scale_trans)
str_value = self.yformatter(value)
user_label_style = self.ylabel_style
else:
raise ValueError(
"Unknown axis, {!r}, must be either 'x' or 'y'".format(axis))
# Make a 'blended' transform for label text positioning.
# One coord is geographic, and the other a plain Axes
# coordinate with an appropriate offset.
label_transform = mtrans.blended_transform_factory(
x_transform=tr_x, y_transform=tr_y)
label_style = {'verticalalignment': v_align,
'horizontalalignment': h_align,
}
label_style.update(user_label_style)
# Create and add a Text artist with these properties
text_artist = mtext.Text(x, y, str_value,
clip_on=False,
transform=label_transform, **label_style)
if axis == 'x':
self.xlabel_artists.append(text_artist)
elif axis == 'y':
self.ylabel_artists.append(text_artist)
self.axes.add_artist(text_artist)
def _draw_gridliner(self, nx=None, ny=None, background_patch=None):
"""Create Artists for all visible elements and add to our Axes."""
x_lim, y_lim = self._axes_domain(nx=nx, ny=ny,
background_patch=background_patch)
transform = self._crs_transform()
rc_params = matplotlib.rcParams
n_steps = self.n_steps
x_ticks = self.xlocator.tick_values(x_lim[0], x_lim[1])
y_ticks = self.ylocator.tick_values(y_lim[0], y_lim[1])
# XXX this bit is cartopy specific. (for circular longitudes)
# Purpose: omit plotting the last x line, as it may overlap the first.
x_gridline_points = x_ticks[:]
crs = self.crs
if (isinstance(crs, Projection) and
isinstance(crs, _RectangularProjection) and
abs(np.diff(x_lim)) == abs(np.diff(crs.x_limits))):
x_gridline_points = x_gridline_points[:-1]
collection_kwargs = self.collection_kwargs
if collection_kwargs is None:
collection_kwargs = {}
collection_kwargs = collection_kwargs.copy()
collection_kwargs['transform'] = transform
# XXX doesn't gracefully handle lw vs linewidth aliases...
collection_kwargs.setdefault('color', rc_params['grid.color'])
collection_kwargs.setdefault('linestyle', rc_params['grid.linestyle'])
collection_kwargs.setdefault('linewidth', rc_params['grid.linewidth'])
if self.xlines:
lines = []
for x in x_gridline_points:
l = list(zip(np.zeros(n_steps) + x,
np.linspace(min(y_ticks), max(y_ticks), n_steps)))
lines.append(l)
x_lc = mcollections.LineCollection(lines, **collection_kwargs)
self.xline_artists.append(x_lc)
self.axes.add_collection(x_lc, autolim=False)
if self.ylines:
lines = []
for y in y_ticks:
l = list(zip(np.linspace(min(x_ticks), max(x_ticks), n_steps),
np.zeros(n_steps) + y))
lines.append(l)
y_lc = mcollections.LineCollection(lines, **collection_kwargs)
self.yline_artists.append(y_lc)
self.axes.add_collection(y_lc, autolim=False)
#################
# Label drawing #
#################
# Trim outside-area points from the label coords.
# Tickers may round *up* the desired range to something tidy, not
# all of which is necessarily visible. We must be stricter with
# our texts, as they are drawn *without clipping*.
x_label_points = [x for x in x_ticks if x_lim[0] <= x <= x_lim[1]]
y_label_points = [y for y in y_ticks if y_lim[0] <= y <= y_lim[1]]
if self.xlabels_bottom or self.xlabels_top:
self._assert_can_draw_ticks()
self.xformatter.set_locs(x_label_points)
for x in x_label_points:
if self.xlabels_bottom:
self._add_gridline_label(x, axis='x', upper_end=False)
if self.xlabels_top:
self._add_gridline_label(x, axis='x', upper_end=True)
if self.ylabels_left or self.ylabels_right:
self._assert_can_draw_ticks()
self.yformatter.set_locs(y_label_points)
for y in y_label_points:
if self.ylabels_left:
self._add_gridline_label(y, axis='y', upper_end=False)
if self.ylabels_right:
self._add_gridline_label(y, axis='y', upper_end=True)
def _assert_can_draw_ticks(self):
"""
Check to see if ticks can be drawn. Either returns True or raises
an exception.
"""
# Check labelling is supported, currently a limited set of options.
if not isinstance(self.crs, cartopy.crs.PlateCarree):
raise TypeError('Cannot label {crs.__class__.__name__} gridlines.'
' Only PlateCarree gridlines are currently '
'supported.'.format(crs=self.crs))
if not isinstance(self.axes.projection,
(cartopy.crs.PlateCarree, cartopy.crs.Mercator)):
raise TypeError('Cannot label gridlines on a '
'{prj.__class__.__name__} plot. Only PlateCarree'
' and Mercator plots are currently '
'supported.'.format(prj=self.axes.projection))
return True
def _axes_domain(self, nx=None, ny=None, background_patch=None):
"""Returns x_range, y_range"""
DEBUG = False
transform = self._crs_transform()
ax_transform = self.axes.transAxes
desired_trans = ax_transform - transform
nx = nx or 30
ny = ny or 30
x = np.linspace(1e-9, 1 - 1e-9, nx)
y = np.linspace(1e-9, 1 - 1e-9, ny)
x, y = np.meshgrid(x, y)
coords = np.concatenate([x.flatten()[:, None],
y.flatten()[:, None]],
1)
in_data = desired_trans.transform(coords)
ax_to_bkg_patch = self.axes.transAxes - \
background_patch.get_transform()
ok = np.zeros(in_data.shape[:-1], dtype=np.bool)
# XXX Vectorise contains_point
for i, val in enumerate(in_data):
# convert the coordinates of the data to the background
# patches coordinates
background_coord = ax_to_bkg_patch.transform(coords[i:i + 1, :])
bkg_patch_contains = background_patch.get_path().contains_point
if bkg_patch_contains(background_coord[0, :]):
color = 'r'
ok[i] = True
else:
color = 'b'
if DEBUG:
import matplotlib.pyplot as plt
plt.plot(coords[i, 0], coords[i, 1], 'o' + color,
clip_on=False, transform=ax_transform)
# plt.text(coords[i, 0], coords[i, 1], str(val), clip_on=False,
# transform=ax_transform, rotation=23,
# horizontalalignment='right')
inside = in_data[ok, :]
# If there were no data points in the axes we just use the x and y
# range of the projection.
if inside.size == 0:
x_range = self.crs.x_limits
y_range = self.crs.y_limits
else:
x_range = np.nanmin(inside[:, 0]), np.nanmax(inside[:, 0])
y_range = np.nanmin(inside[:, 1]), np.nanmax(inside[:, 1])
# XXX Cartopy specific thing. Perhaps make this bit a specialisation
# in a subclass...
crs = self.crs
if isinstance(crs, Projection):
x_range = np.clip(x_range, *crs.x_limits)
y_range = np.clip(y_range, *crs.y_limits)
# if the limit is >90% of the full x limit, then just use the full
# x limit (this makes circular handling better)
prct = np.abs(np.diff(x_range) / np.diff(crs.x_limits))
if prct > 0.9:
x_range = crs.x_limits
return x_range, y_range
Cartopy-0.14.2/lib/cartopy/mpl/patch.py 0000644 0013740 0002103 00000021537 12700747662 017462 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2011 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
"""
Provides shapely geometry <-> matplotlib path support.
See also `Shapely Geometric Objects `_
and `Matplotlib Path API `_.
.. see_also_shapely:
http://toblerity.org/shapely/manual.html#geometric-objects
"""
from __future__ import (absolute_import, division, print_function)
import numpy as np
import matplotlib.path
from matplotlib.path import Path
import shapely.geometry as sgeom
def geos_to_path(shape):
"""
Creates a list of :class:`matplotlib.path.Path` objects that describe
a shape.
Args:
* shape
A list, tuple or single instance of any of the following
types: :class:`shapely.geometry.point.Point`,
:class:`shapely.geometry.linestring.LineString`,
:class:`shapely.geometry.polygon.Polygon`,
:class:`shapely.geometry.multipoint.MultiPoint`,
:class:`shapely.geometry.multipolygon.MultiPolygon`,
:class:`shapely.geometry.multilinestring.MultiLineString`,
:class:`shapely.geometry.collection.GeometryCollection`,
or any type with a _as_mpl_path() method.
Returns:
A list of :class:`matplotlib.path.Path` objects.
"""
if isinstance(shape, (list, tuple)):
paths = []
for shp in shape:
paths.extend(geos_to_path(shp))
return paths
if isinstance(shape, (sgeom.LineString, sgeom.Point)):
return [Path(np.vstack(shape.xy).T)]
elif isinstance(shape, sgeom.Polygon):
def poly_codes(poly):
codes = np.ones(len(poly.xy[0])) * Path.LINETO
codes[0] = Path.MOVETO
return codes
if shape.is_empty:
return []
vertices = np.concatenate([np.array(shape.exterior.xy)] +
[np.array(ring.xy) for ring in
shape.interiors], 1).T
codes = np.concatenate([poly_codes(shape.exterior)] +
[poly_codes(ring) for ring in shape.interiors])
return [Path(vertices, codes)]
elif isinstance(shape, (sgeom.MultiPolygon, sgeom.GeometryCollection,
sgeom.MultiLineString, sgeom.MultiPoint)):
paths = []
for geom in shape.geoms:
paths.extend(geos_to_path(geom))
return paths
elif hasattr(shape, '_as_mpl_path'):
vertices, codes = shape._as_mpl_path()
return [Path(vertices, codes)]
else:
raise ValueError('Unsupported shape type {}.'.format(type(shape)))
def path_segments(path, transform=None, remove_nans=False, clip=None,
quantize=False, simplify=False, curves=False,
stroke_width=1.0, snap=False):
"""
Creates an array of vertices and a corresponding array of codes from a
:class:`matplotlib.path.Path`.
Args:
* path
A :class:`matplotlib.path.Path` instance.
Kwargs:
See :func:`matplotlib.path.iter_segments` for details of the keyword
arguments.
Returns:
A (vertices, codes) tuple, where vertices is a numpy array of
coordinates, and codes is a numpy array of matplotlib path codes.
See :class:`matplotlib.path.Path` for information on the types of
codes and their meanings.
"""
# XXX assigned to avoid a ValueError inside the mpl C code...
a = transform, remove_nans, clip, quantize, simplify, curves
# Series of cleanups and conversions to the path e.g. it
# can convert curved segments to line segments.
vertices, codes = matplotlib.path.cleanup_path(path, transform,
remove_nans, clip,
snap, stroke_width,
simplify, curves)
# Remove the final vertex (with code 0)
return vertices[:-1, :], codes[:-1]
# Matplotlib v1.3+ deprecates the use of matplotlib.path.cleanup_path. Instead
# there is a method on a Path instance to simplify this.
if hasattr(matplotlib.path.Path, 'cleaned'):
_path_segments_doc = path_segments.__doc__
def path_segments(path, **kwargs):
pth = path.cleaned(**kwargs)
return pth.vertices[:-1, :], pth.codes[:-1]
path_segments.__doc__ = _path_segments_doc
def path_to_geos(path, force_ccw=False):
"""
Creates a list of Shapely geometric objects from a
:class:`matplotlib.path.Path`.
Args:
* path
A :class:`matplotlib.path.Path` instance.
Kwargs:
* force_ccw
Boolean flag determining whether the path can be inverted to enforce
ccw.
Returns:
A list of :class:`shapely.geometry.polygon.Polygon`,
:class:`shapely.geometry.linestring.LineString` and/or
:class:`shapely.geometry.multilinestring.MultiLineString` instances.
"""
# Convert path into numpy array of vertices (and associated codes)
path_verts, path_codes = path_segments(path, curves=False)
# Split into subarrays such that each subarray consists of connected
# line segments based on the start of each one being marked by a
# matplotlib MOVETO code.
verts_split_inds = np.where(path_codes == Path.MOVETO)[0]
verts_split = np.split(path_verts, verts_split_inds)
codes_split = np.split(path_codes, verts_split_inds)
# Iterate through the vertices generating a list of
# (external_geom, [internal_polygons]) tuples.
other_result_geoms = []
collection = []
for path_verts, path_codes in zip(verts_split, codes_split):
if len(path_verts) == 0:
continue
# XXX A path can be given which does not end with close poly, in that
# situation, we have to guess?
verts_same_as_first = np.all(path_verts[0, :] == path_verts[1:, :],
axis=1)
if all(verts_same_as_first):
geom = sgeom.Point(path_verts[0, :])
elif (path_verts.shape[0] > 2 and
(path_codes[-1] == Path.CLOSEPOLY or
verts_same_as_first[-1])):
if path_codes[-1] == Path.CLOSEPOLY:
geom = sgeom.Polygon(path_verts[:-1, :])
else:
geom = sgeom.Polygon(path_verts)
else:
geom = sgeom.LineString(path_verts)
# If geom is a Polygon and is contained within the last geom in
# collection, add it to its list of internal polygons, otherwise
# simply append it as a new external geom.
if geom.is_empty:
pass
elif (len(collection) > 0 and
isinstance(collection[-1][0], sgeom.Polygon) and
isinstance(geom, sgeom.Polygon) and
collection[-1][0].contains(geom.exterior)):
collection[-1][1].append(geom.exterior)
elif isinstance(geom, sgeom.Point):
other_result_geoms.append(geom)
else:
collection.append((geom, []))
# Convert each (external_geom, [internal_polygons]) pair into a
# a shapely Polygon that encapsulates the internal polygons, if the
# external geom is a LineString leave it alone.
geom_collection = []
for external_geom, internal_polys in collection:
if internal_polys:
# XXX worry about islands within lakes
geom = sgeom.Polygon(external_geom.exterior, internal_polys)
else:
geom = external_geom
# Correctly orientate the polygon (ccw)
if isinstance(geom, sgeom.Polygon):
if force_ccw and not geom.exterior.is_ccw:
geom = sgeom.polygon.orient(geom)
geom_collection.append(geom)
# If the geom_collection only contains LineStrings combine them
# into a single MultiLinestring.
if geom_collection and all(isinstance(geom, sgeom.LineString) for
geom in geom_collection):
geom_collection = [sgeom.MultiLineString(geom_collection)]
# Remove any zero area Polygons
def not_zero_poly(geom):
return ((isinstance(geom, sgeom.Polygon) and not geom._is_empty and
geom.area != 0) or
not isinstance(geom, sgeom.Polygon))
result = list(filter(not_zero_poly, geom_collection))
return result + other_result_geoms
Cartopy-0.14.2/lib/cartopy/mpl/slippy_image_artist.py 0000644 0013740 0002103 00000004250 12700747662 022424 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2014 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
"""
Defines the SlippyImageArtist class, which interfaces with
:class:`cartopy.io.RasterSource` instances at draw time, for interactive
dragging and zooming of raster data.
"""
from __future__ import (absolute_import, division, print_function)
from matplotlib.image import AxesImage
import matplotlib.artist
class SlippyImageArtist(AxesImage):
"""
A subclass of :class:`~matplotlib.image.AxesImage` which provides an
interface for getting a raster from the given object with interactive
slippy map type functionality.
Kwargs are passed to the AxesImage constructor.
"""
def __init__(self, ax, raster_source, **kwargs):
self.raster_source = raster_source
super(SlippyImageArtist, self).__init__(ax, **kwargs)
self.set_clip_path(ax.outline_patch)
@matplotlib.artist.allow_rasterization
def draw(self, renderer, *args, **kwargs):
if not self.get_visible():
return
ax = self.axes
window_extent = ax.get_window_extent()
[x1, y1], [x2, y2] = ax.viewLim.get_points()
located_images = self.raster_source.fetch_raster(
ax.projection, extent=[x1, x2, y1, y2],
target_resolution=(window_extent.width, window_extent.height))
for img, extent in located_images:
self.set_array(img)
with ax.hold_limits():
self.set_extent(extent)
super(SlippyImageArtist, self).draw(renderer, *args, **kwargs)
Cartopy-0.14.2/lib/cartopy/mpl/ticker.py 0000644 0013740 0002103 00000024375 12700747662 017647 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2014 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
"""This module contains tools for handling tick marks in cartopy."""
from __future__ import (absolute_import, division, print_function)
from matplotlib.ticker import Formatter
import cartopy.crs as ccrs
from cartopy.mpl.geoaxes import GeoAxes
class _PlateCarreeFormatter(Formatter):
"""
Base class for formatting ticks on geographical axes using a
rectangular projection (e.g. Plate Carree, Mercator).
"""
_target_projection = ccrs.PlateCarree()
def __init__(self, degree_symbol=u'\u00B0', number_format='g',
transform_precision=1e-8):
"""
Base class for simpler implementation of specialised formatters
for latitude and longitude axes.
"""
self._degree_symbol = degree_symbol
self._number_format = number_format
self._transform_precision = transform_precision
def __call__(self, value, pos=None):
if not isinstance(self.axis.axes, GeoAxes):
raise TypeError("This formatter can only be "
"used with cartopy axes.")
# We want to produce labels for values in the familiar Plate Carree
# projection, so convert the tick values from their own projection
# before formatting them.
source = self.axis.axes.projection
if not isinstance(source, (ccrs._RectangularProjection,
ccrs.Mercator)):
raise TypeError("This formatter cannot be used with "
"non-rectangular projections.")
projected_value = self._apply_transform(value, self._target_projection,
source)
# Round the transformed value using a given precision for display
# purposes. Transforms can introduce minor rounding errors that make
# the tick values look bad, these need to be accounted for.
f = 1. / self._transform_precision
projected_value = round(f * projected_value) / f
# Return the formatted values, the formatter has both the re-projected
# tick value and the original axis value available to it.
return self._format_value(projected_value, value)
def _format_value(self, value, original_value):
hemisphere = self._hemisphere(value, original_value)
fmt_string = u'{value:{number_format}}{degree}{hemisphere}'
return fmt_string.format(value=abs(value),
number_format=self._number_format,
degree=self._degree_symbol,
hemisphere=hemisphere)
def _apply_transform(self, value, target_proj, source_crs):
"""
Given a single value, a target projection and a source CRS,
transforms the value from the source CRS to the target
projection, returning a single value.
"""
raise NotImplementedError("A subclass must implement this method.")
def _hemisphere(self, value, value_source_crs):
"""
Given both a tick value in the Plate Carree projection and the
same value in the source CRS returns a string indicating the
hemisphere that the value is in.
Must be over-ridden by the derived class.
"""
raise NotImplementedError("A subclass must implement this method.")
class LatitudeFormatter(_PlateCarreeFormatter):
"""Tick formatter for latitude axes."""
def __init__(self, degree_symbol=u'\u00B0', number_format='g',
transform_precision=1e-8):
"""
Tick formatter for a latitude axis.
The axis must be part of an axes defined on a rectangular
projection (e.g. Plate Carree, Mercator).
.. note::
A formatter can only be used for one axis. A new formatter
must be created for every axis that needs formatted labels.
Kwargs:
* degree_symbol (string):
The character(s) used to represent the degree symbol in the
tick labels. Defaults to u'\u00B0' which is the unicode
degree symbol. Can be an empty string if no degree symbol is
desired.
* number_format (string):
Format string to represent the tick values. Defaults to 'g'.
* transform_precision (float):
Sets the precision (in degrees) to which transformed tick
values are rounded. The default is 1e-7, and should be
suitable for most use cases. To control the appearance of
tick labels use the *number_format* keyword.
Examples:
Label latitudes from -90 to 90 on a Plate Carree projection::
ax = plt.axes(projection=PlateCarree())
ax.set_global()
ax.set_yticks([-90, -60, -30, 0, 30, 60, 90],
crs=ccrs.PlateCarree())
lat_formatter = LatitudeFormatter()
ax.yaxis.set_major_formatter(lat_formatter)
Label latitudes from -80 to 80 on a Mercator projection, this
time omitting the degree symbol::
ax = plt.axes(projection=Mercator())
ax.set_global()
ax.set_yticks([-90, -60, -30, 0, 30, 60, 90],
crs=ccrs.PlateCarree())
lat_formatter = LatitudeFormatter(degree_symbol='')
ax.yaxis.set_major_formatter(lat_formatter)
"""
super(LatitudeFormatter, self).__init__(
degree_symbol=degree_symbol,
number_format=number_format,
transform_precision=transform_precision)
def _apply_transform(self, value, target_proj, source_crs):
return target_proj.transform_point(0, value, source_crs)[1]
def _hemisphere(self, value, value_source_crs):
if value > 0:
hemisphere = 'N'
elif value < 0:
hemisphere = 'S'
else:
hemisphere = ''
return hemisphere
class LongitudeFormatter(_PlateCarreeFormatter):
"""Tick formatter for a longitude axis."""
def __init__(self,
zero_direction_label=False,
dateline_direction_label=False,
degree_symbol=u'\u00B0',
number_format='g',
transform_precision=1e-8):
"""
Create a formatter for longitude values.
The axis must be part of an axes defined on a rectangular
projection (e.g. Plate Carree, Mercator).
.. note::
A formatter can only be used for one axis. A new formatter
must be created for every axis that needs formatted labels.
Kwargs:
* zero_direction_label (False | True):
If *True* a direction label (E or W) will be drawn next to
longitude labels with the value 0. If *False* then these
labels will not be drawn. Defaults to *False* (no direction
labels).
* dateline_direction_label (False | True):
If *True* a direction label (E or W) will be drawn next to
longitude labels with the value 180. If *False* then these
labels will not be drawn. Defaults to *False* (no direction
labels).
* degree_symbol (string):
The symbol used to represent degrees. Defaults to u'\u00B0'
which is the unicode degree symbol.
* number_format (string):
Format string to represent the longitude values. Defaults to
'g'.
* transform_precision (float):
Sets the precision (in degrees) to which transformed tick
values are rounded. The default is 1e-7, and should be
suitable for most use cases. To control the appearance of
tick labels use the *number_format* keyword.
Examples:
Label longitudes from -180 to 180 on a Plate Carree projection
with a central longitude of 0::
ax = plt.axes(projection=PlateCarree())
ax.set_global()
ax.set_xticks([-180, -120, -60, 0, 60, 120, 180],
crs=ccrs.PlateCarree())
lon_formatter = LongitudeFormatter()
ax.xaxis.set_major_formatter(lon_formatter)
Label longitudes from 0 to 360 on a Plate Carree projection
with a central longitude of 180::
ax = plt.axes(projection=PlateCarree(central_longitude=180))
ax.set_global()
ax.set_xticks([0, 60, 120, 180, 240, 300, 360],
crs=ccrs.PlateCarree())
ont_formatter = LongitudeFormatter()
ax.xaxis.set_major_formatter(lon_formatter)
"""
super(LongitudeFormatter, self).__init__(
degree_symbol=degree_symbol,
number_format=number_format,
transform_precision=transform_precision)
self._zero_direction_labels = zero_direction_label
self._dateline_direction_labels = dateline_direction_label
def _apply_transform(self, value, target_proj, source_crs):
return target_proj.transform_point(value, 0, source_crs)[0]
def _hemisphere(self, value, value_source_crs):
# Perform basic hemisphere detection.
if value < 0:
hemisphere = 'W'
elif value > 0:
hemisphere = 'E'
else:
hemisphere = ''
# Correct for user preferences:
if value == 0 and self._zero_direction_labels:
# Use the original tick value to determine the hemisphere.
if value_source_crs < 0:
hemisphere = 'E'
else:
hemisphere = 'W'
if value in (-180, 180) and not self._dateline_direction_labels:
hemisphere = ''
return hemisphere
Cartopy-0.14.2/lib/cartopy/sphinxext/ 0000755 0013740 0002103 00000000000 12706121712 017227 5 ustar itpe avd 0000000 0000000 Cartopy-0.14.2/lib/cartopy/sphinxext/__init__.py 0000644 0013740 0002103 00000001437 12700747662 021361 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2011 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
from __future__ import (absolute_import, division, print_function)
Cartopy-0.14.2/lib/cartopy/sphinxext/gallery.py 0000644 0013740 0002103 00000024002 12700747662 021252 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2013 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
from __future__ import (absolute_import, division, print_function)
import os.path
import sys
from cartopy.sphinxext.summarise_package import walk_module
import cartopy.tests
def out_of_date(original_fname, target_fname):
"""
Checks to see if the ``target_fname`` exists, and if so, whether
the modification timestamp suggests that ``original_fname`` is newer
than ``target_fname``.
"""
return (not os.path.exists(target_fname) or
os.path.getmtime(original_fname) > os.path.getmtime(target_fname))
def same_contents(fname, contents_str):
"""
Checks to see if the given fname contains the contents given by
``contents_str``. The result could be used to determine if the
contents need to be written to the given fname.
"""
if os.path.exists(fname):
with open(fname, 'r') as fh:
return fh.read() == contents_str
return False
def parent_module(module):
"""
Returns the direct module ascendent of the given module.
For example, giving a module ``a.b.c`` would return the
``a.b`` module.
If the module is a top level package, None will be returned.
.. note::
Requires the __name__ attribute on the given module
to be correctly defined and the parent module to be importable.
>>> import numpy.ma.core
>>> from cartopy.sphinxext.gallery import parent_module
>>> parent_module(numpy.ma.core) # doctest: +ELLIPSIS
"""
result = None
name = module.__name__
by_dot = name.split('.')
if len(by_dot) > 1:
parent_name = '.'.join(by_dot[:-1])
result = get_module(parent_name)
return result
def get_module(mod_name):
"""
Return the module instance of the given name, by importing
it into the system.
"""
__import__(mod_name)
return sys.modules[mod_name]
def safe_mod_name_and_fname(module_name, ancestor_module_name):
"""
Returns a safe module name (for linking too) and safe filename (suffixed
with ".rst") for the given module name, relative to the given ancestor
module.
>>> from cartopy.sphinxext.gallery import safe_mod_name_and_fname
>>> safe_mod_name_and_fname('numpy.ma.core', 'numpy')
('ma-core', 'ma/core.rst')
"""
mod = get_module(module_name)
ancestor_package = get_module(ancestor_module_name)
safe_fname = os.path.relpath(os.path.splitext(mod.__file__)[0],
os.path.dirname(ancestor_package.__file__))
safe_name = safe_fname.replace(os.path.sep, '-')
safe_fname = safe_fname + '.rst'
return safe_name, safe_fname
def examples_code(examples_mod_name,
source_directory,
output_directory='examples'):
"""
Generates the rst code for the given examples module.
examples_mod_name - the name of the parent (importable) module which
should be recursively walked when looking for
examples
source_directory - the path to the top level source directory containing
the rst content of the documentation
output_directory - the directory for the output to be put in. Should be
relative to the source_directory
"""
for mod_name, root_dir, fname, _ in walk_module(examples_mod_name):
if fname.startswith('__init__'):
continue
rst, rst_fname = individual_example_rst(mod_name, examples_mod_name,
output_directory)
rst_fname = os.path.join(source_directory, output_directory, rst_fname)
py_fname = os.path.join(source_directory, output_directory,
os.path.splitext(rst_fname)[0] + '.py')
if not os.path.isdir(os.path.dirname(py_fname)):
os.makedirs(os.path.dirname(py_fname))
if out_of_date(os.path.join(root_dir, fname), py_fname):
with open(os.path.join(root_dir, fname), 'r') as in_fh:
with open(py_fname, 'w') as out_fh:
for line in in_fh:
# Crudely remove the __tags__ line.
if line.startswith('__tags__ = '):
continue
out_fh.write(line)
if not same_contents(rst_fname, rst):
with open(rst_fname, 'w') as fh:
fh.write(rst)
def gallery_code(examples_mod_name):
"""
Returns rst code suitable for generating a html gallery using sphinx.
examples_mod_name - the name of the importable (sub)module which contains
the examples
"""
# Store a dictionary mapping tag_name to (mod_name, mod_instance, tags)
examples_by_tag = {}
for mod_name, _, _, _ in walk_module(examples_mod_name):
if mod_name != examples_mod_name:
__import__(mod_name)
mod = sys.modules[mod_name]
tags = getattr(mod, '__tags__', ['Miscellanea'])
for tag in tags:
examples_by_tag.setdefault(tag, []).append((mod_name, mod))
result = ['Gallery',
'=======',
'Tags:\n',
'.. container:: inline-paragraphs\n'
]
examples_by_tag = sorted(iter(examples_by_tag.items()),
key=lambda pair: (pair[0] == 'Miscellanea',
pair[0]))
for tag, _ in examples_by_tag:
result.append('\t:ref:`gallery-tag-{}`\n'.format(tag))
for tag, mod_list in examples_by_tag:
result.extend(['.. _gallery-tag-{}:\n'.format(tag),
'{}'.format(tag),
'-' * len(tag) + '\n',
'.. container:: gallery_images\n'])
for (mod_name, mod) in mod_list:
safe_name, _ = safe_mod_name_and_fname(mod_name,
examples_mod_name)
# XXX The path is currently determined out of process by
# the plot directive. It would be nice to figure out the
# naming scheme to handle multiple plots in a single example.
img_path = 'examples/{}_00_00.png'.format(
mod_name.split('.')[-1])
thumb_path = 'examples/{}_00_00.thumb.png'.format(
mod_name.split('.')[-1])
entry = ["|image_{}|_\n".format(safe_name),
".. |image_{}| image:: {}".format(safe_name, thumb_path),
# XXX Hard-codes the html - rst cannot do nested inline
# elements (very well).
".. _image_{}: examples/{}.html".format(
safe_name, safe_name)]
result.extend(['\n\n\t' + line for line in entry])
return '\n'.join(result)
def individual_example_rst(example_mod_name, examples_mod_name,
output_directory):
"""
Generates the rst code for the given example and suggests a sensible
rst filename.
example_mod_name - the name of the importable submodule which contains
the individual example which is to be documented
examples_mod_name - the name of the importable (sub)module which contains
the examples
output_directory - is a path to the examples output directory, relative
to the source directory.
"""
mod = get_module(example_mod_name)
safe_name, safe_fname = safe_mod_name_and_fname(example_mod_name,
examples_mod_name)
fname_base = os.path.splitext(safe_fname)[0] + '.py'
example_code_fname = os.path.join(output_directory, fname_base)
result = ['.. _examples-{}:\n'.format(safe_name)]
if mod.__doc__:
result.append(mod.__doc__ + '\n')
else:
result.extend(['{} example'.format(safe_name),
'-' * (len(safe_name) + 8) + '\n'])
result.extend(['.. plot:: {}\n'.format(example_code_fname),
'.. literalinclude:: {}\n'.format(fname_base)])
return '\n'.join(result), safe_fname
def gen_gallery(app):
"""Produces the gallery rst file."""
example_package_name = app.config.examples_package_name
fname = app.config.gallery_name
if example_package_name is None:
raise ValueError('A config value for gallery_package_name should be '
'defined.')
outdir = app.builder.srcdir
fname = os.path.join(outdir, fname)
gallery_rst = gallery_code(example_package_name)
if not same_contents(fname, gallery_rst):
with open(fname, 'w') as fh:
fh.write(gallery_rst)
def gen_examples(app):
"""Produces the examples directory."""
example_package_name = app.config.examples_package_name
source_dir = app.builder.srcdir
examples_code(example_package_name, source_dir, 'examples')
@cartopy.tests.not_a_nose_fixture
def setup(app):
app.connect('builder-inited', gen_gallery)
app.connect('builder-inited', gen_examples)
# Allow users to define a config value to determine the name of the
# gallery rst file (with file extension included)
app.add_config_value('gallery_name', 'gallery.rst', 'env')
# Allow users to define a config value to determine the name of the
# importable examples (sub)package
app.add_config_value('examples_package_name', None, 'env')
Cartopy-0.14.2/lib/cartopy/sphinxext/summarise_package.py 0000644 0013740 0002103 00000016547 12700747662 023312 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2011 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
from __future__ import (absolute_import, division, print_function)
import inspect
import itertools
import os
import sys
import warnings
import six
import cartopy.tests
def walk_module(mod_name, exclude_folders=None):
"""
Recursively walks the given module name.
Returns:
A generator of::
(fully_qualified_import_name,
root_directory_of_subpackage,
fname_in_root_directory,
sub_folders_in_root_directory)
"""
__import__(mod_name)
mod = sys.modules[mod_name]
mod_dir = os.path.dirname(mod.__file__)
exclude_folders = exclude_folders or []
for root, folders, files in os.walk(mod_dir):
for folder in exclude_folders:
try:
folders.remove(folder)
except ValueError:
pass
# only allow python packages
if '__init__.py' not in files:
del folders[:]
continue
# Sort by filename and folder name.
files.sort()
folders.sort()
def is_py_src(fname):
root, ext = os.path.splitext(fname)
return ext in ('.py', '.so')
files = filter(is_py_src, files)
for fname in files:
sub_mod_name = mod_name
relpath = os.path.relpath(root, mod_dir)
if relpath == '.':
relpath = ''
for sub_mod in [f for f in relpath.split(os.path.sep) if f]:
sub_mod_name += '.' + sub_mod
if fname != '__init__.py':
sub_mod_name += '.' + os.path.splitext(fname)[0]
yield sub_mod_name, root, fname, folders
def objects_to_document(module_name):
"""
Creates a generator of (obj_name, obj) that the given module of the
given name should document.
The module name may be any importable, including submodules
(e.g. ``cartopy.io``)
"""
try:
__import__(module_name)
except ImportError:
warnings.warn('Failed to document {}'.format(module_name))
return []
module = sys.modules[module_name]
elems = dir(module)
if '__all__' in elems:
document_these = [(obj, getattr(module, obj))
for obj in module.__all__]
else:
document_these = [(obj, getattr(module, obj)) for obj in elems
if not inspect.ismodule(getattr(module, obj)) and
not obj.startswith('_')]
def is_from_this_module(x):
return getattr(x[1], '__module__', '') == module_name
document_these = filter(is_from_this_module, document_these)
document_these = sorted(document_these,
key=lambda x: (str(type(x[1])),
not x[0].isupper(),
x[0]))
# allow a developer to add other things to the documentation
if hasattr(module, '__document_these__'):
extra_objects_to_document = tuple((obj, getattr(module, obj))
for obj in module.__document_these__)
document_these = extra_objects_to_document + tuple(document_these)
return document_these
def main(package_name, exclude_folders=None):
"""
Return a string containing the rst that documents the given package name.
"""
result = ''
mod_walk = walk_module(package_name, exclude_folders=exclude_folders)
for mod, _, fname, folders in mod_walk:
for folder in folders:
if folder.startswith('_'):
folders.remove(folder)
if fname.startswith('_') and fname != '__init__.py':
continue
result += '\n'
result += mod + '\n'
result += '*' * len(mod) + '\n'
result += '\n'
result += '.. currentmodule:: {}\n'.format(mod) + '\n'
mod_objects = objects_to_document(mod)
if mod_objects:
result += '.. csv-table::\n' + '\n'
table_elements = itertools.cycle(('\n\t', ) + (', ', ) * 3)
for table_elem, (obj_name, _) in zip(table_elements, mod_objects):
result += '{}:py:obj:`{}`'.format(table_elem, obj_name)
result += '\n'
return result
def gen_summary_rst(app):
"""
Creates the rst file to summarise the desired packages.
"""
package_names = app.config.summarise_package_names
exclude_dirs = app.config.summarise_package_exclude_directories
fnames = app.config.summarise_package_fnames
if isinstance(package_names, six.string_types):
package_names = [package_names]
if package_names is None:
raise ValueError('Please define a config value containing a list '
'of packages to summarise.')
if exclude_dirs is None:
exclude_dirs = [None] * len(package_names)
else:
exception = ValueError('Please provide a list of exclude directory '
'lists (one list for each package to '
'summarise).')
if len(exclude_dirs) != len(package_names):
raise exception
for exclude_dirs_individual in exclude_dirs:
if isinstance(exclude_dirs_individual, six.string_types):
raise exception
if fnames is None:
fnames = ['outline_of_{}.rst'.format(package_name)
for package_name in package_names]
else:
if isinstance(fnames, six.string_types) or \
len(fnames) != len(package_names):
raise TypeError('Please provide a list of filenames for each of '
'the packages which are to be summarised.')
outdir = app.builder.srcdir
for package_name, out_fname, exclude_folders in zip(package_names,
fnames,
exclude_dirs):
out_fpath = os.path.join(outdir, out_fname)
content = main(package_name, exclude_folders=exclude_folders)
with open(out_fpath, 'w') as fh:
fh.write(content)
@cartopy.tests.not_a_nose_fixture
def setup(app):
"""
Defined the Sphinx application interface for the summary generation.
"""
app.connect('builder-inited', gen_summary_rst)
# Allow users to define a config value to determine the names to summarise
app.add_config_value('summarise_package_names', None, 'env')
# Allow users to define a config value to determine the folders to exclude
app.add_config_value('summarise_package_exclude_directories', None, 'env')
# Allow users to define a config value to determine name of the output file
app.add_config_value('summarise_package_fnames', None, 'env')
Cartopy-0.14.2/lib/cartopy/tests/ 0000755 0013740 0002103 00000000000 12706121712 016337 5 ustar itpe avd 0000000 0000000 Cartopy-0.14.2/lib/cartopy/tests/crs/ 0000755 0013740 0002103 00000000000 12706121712 017126 5 ustar itpe avd 0000000 0000000 Cartopy-0.14.2/lib/cartopy/tests/crs/__init__.py 0000644 0013740 0002103 00000001523 12700747662 021254 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2013 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
"""
Tests for specific Cartopy CRS subclasses.
"""
from __future__ import (absolute_import, division, print_function)
Cartopy-0.14.2/lib/cartopy/tests/crs/test_albers_equal_area.py 0000644 0013740 0002103 00000013546 12700747662 024213 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2015 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
"""
Tests for the Albers Equal Area coordinate system.
"""
from __future__ import (absolute_import, division, print_function)
import unittest
import numpy as np
from numpy.testing import assert_almost_equal
from nose.tools import assert_equal
import cartopy.crs as ccrs
class TestAlbersEqualArea(unittest.TestCase):
def test_default(self):
aea = ccrs.AlbersEqualArea()
expected = ('+ellps=WGS84 +proj=aea +lon_0=0.0 +lat_0=0.0 '
'+x_0=0.0 +y_0=0.0 +lat_1=20.0 +lat_2=50.0 +no_defs')
assert_equal(aea.proj4_init, expected)
assert_almost_equal(np.array(aea.x_limits),
[-17702759.799178038, 17702759.799178038],
decimal=0)
assert_almost_equal(np.array(aea.y_limits),
[-4782937.05107294, 15922623.93176938],
decimal=4)
def test_eccentric_globe(self):
globe = ccrs.Globe(semimajor_axis=1000, semiminor_axis=500,
ellipse=None)
aea = ccrs.AlbersEqualArea(globe=globe)
expected = ('+a=1000 +b=500 +proj=aea +lon_0=0.0 +lat_0=0.0 '
'+x_0=0.0 +y_0=0.0 +lat_1=20.0 +lat_2=50.0 +no_defs')
assert_equal(aea.proj4_init, expected)
assert_almost_equal(np.array(aea.x_limits),
[-2323.47073363411, 2323.47073363411],
decimal=-2)
assert_almost_equal(np.array(aea.y_limits),
[-572.556243423972, 2402.36176984391],
decimal=10)
def test_eastings(self):
aea_offset = ccrs.AlbersEqualArea(false_easting=1234,
false_northing=-4321)
expected = ('+ellps=WGS84 +proj=aea +lon_0=0.0 +lat_0=0.0 '
'+x_0=1234 +y_0=-4321 +lat_1=20.0 +lat_2=50.0 +no_defs')
assert_equal(aea_offset.proj4_init, expected)
def test_standard_parallels(self):
aea = ccrs.AlbersEqualArea(standard_parallels=(13, 37))
expected = ('+ellps=WGS84 +proj=aea +lon_0=0.0 +lat_0=0.0 '
'+x_0=0.0 +y_0=0.0 +lat_1=13 +lat_2=37 +no_defs')
assert_equal(aea.proj4_init, expected)
aea = ccrs.AlbersEqualArea(standard_parallels=(13, ))
expected = ('+ellps=WGS84 +proj=aea +lon_0=0.0 +lat_0=0.0 '
'+x_0=0.0 +y_0=0.0 +lat_1=13 +no_defs')
assert_equal(aea.proj4_init, expected)
aea = ccrs.AlbersEqualArea(standard_parallels=13)
expected = ('+ellps=WGS84 +proj=aea +lon_0=0.0 +lat_0=0.0 '
'+x_0=0.0 +y_0=0.0 +lat_1=13 +no_defs')
assert_equal(aea.proj4_init, expected)
def test_sphere_transform(self):
# USGS Professional Paper 1395, pg 291
globe = ccrs.Globe(semimajor_axis=1.0, semiminor_axis=1.0,
ellipse=None)
lat_1 = 29 + 30 / 60
lat_2 = 45 + 30 / 60
aea = ccrs.AlbersEqualArea(central_latitude=23.0,
central_longitude=-96.0,
standard_parallels=(lat_1, lat_2),
globe=globe)
geodetic = aea.as_geodetic()
expected = ('+a=1.0 +b=1.0 +proj=aea +lon_0=-96.0 +lat_0=23.0 '
'+x_0=0.0 +y_0=0.0 +lat_1=29.5 +lat_2=45.5 +no_defs')
assert_equal(aea.proj4_init, expected)
assert_almost_equal(np.array(aea.x_limits),
[-2.6525072042232, 2.6525072042232],
decimal=3)
assert_almost_equal(np.array(aea.y_limits),
[-1.09628087472359, 2.39834724057551],
decimal=10)
result = aea.transform_point(-75.0, 35.0, geodetic)
assert_almost_equal(result, [0.2952720, 0.2416774])
def test_ellipsoid_transform(self):
# USGS Professional Paper 1395, pp 292 -- 293
globe = ccrs.Globe(semimajor_axis=6378206.4,
flattening=1 - np.sqrt(1 - 0.00676866),
ellipse=None)
lat_1 = 29 + 30 / 60
lat_2 = 45 + 30 / 60
aea = ccrs.AlbersEqualArea(central_latitude=23.0,
central_longitude=-96.0,
standard_parallels=(lat_1, lat_2),
globe=globe)
geodetic = aea.as_geodetic()
expected = ('+a=6378206.4 +f=0.003390076308689371 +proj=aea '
'+lon_0=-96.0 +lat_0=23.0 +x_0=0.0 +y_0=0.0 '
'+lat_1=29.5 +lat_2=45.5 +no_defs')
assert_equal(aea.proj4_init, expected)
assert_almost_equal(np.array(aea.x_limits),
[-16900972.674607, 16900972.674607],
decimal=-3)
assert_almost_equal(np.array(aea.y_limits),
[-6971893.11311231, 15298166.8919989],
decimal=1)
result = aea.transform_point(-75.0, 35.0, geodetic)
assert_almost_equal(result, [1885472.7, 1535925.0], decimal=1)
if __name__ == '__main__':
import nose
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)
Cartopy-0.14.2/lib/cartopy/tests/crs/test_azimuthal_equidistant.py 0000644 0013740 0002103 00000027402 12700747662 025170 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2015 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
from __future__ import (absolute_import, division, print_function)
import unittest
import numpy as np
from numpy.testing import assert_almost_equal, assert_array_almost_equal
from nose.tools import assert_equal
import cartopy.crs as ccrs
class TestAzimuthalEquidistant(unittest.TestCase):
def test_default(self):
aeqd = ccrs.AzimuthalEquidistant()
expected = ('+ellps=WGS84 +proj=aeqd +lon_0=0.0 '
'+lat_0=0.0 +x_0=0.0 +y_0=0.0 +no_defs')
assert_equal(aeqd.proj4_init, expected)
assert_almost_equal(np.array(aeqd.x_limits),
[-20037508.34278924, 20037508.34278924], decimal=6)
assert_almost_equal(np.array(aeqd.y_limits),
[-20037508.34278924, 20037508.34278924], decimal=6)
def test_eccentric_globe(self):
globe = ccrs.Globe(semimajor_axis=1000, semiminor_axis=500,
ellipse=None)
aeqd = ccrs.AzimuthalEquidistant(globe=globe)
expected = ('+a=1000 +b=500 +proj=aeqd +lon_0=0.0 +lat_0=0.0 '
'+x_0=0.0 +y_0=0.0 +no_defs')
assert_equal(aeqd.proj4_init, expected)
assert_almost_equal(np.array(aeqd.x_limits),
[-3141.59265359, 3141.59265359], decimal=6)
assert_almost_equal(np.array(aeqd.y_limits),
[-1570.796326795, 1570.796326795], decimal=6)
def test_eastings(self):
aeqd_offset = ccrs.AzimuthalEquidistant(false_easting=1234,
false_northing=-4321)
expected = ('+ellps=WGS84 +proj=aeqd +lon_0=0.0 +lat_0=0.0 '
'+x_0=1234 +y_0=-4321 +no_defs')
assert_equal(aeqd_offset.proj4_init, expected)
assert_almost_equal(np.array(aeqd_offset.x_limits),
[-20036274.34278924, 20038742.34278924], decimal=6)
assert_almost_equal(np.array(aeqd_offset.y_limits),
[-20041829.34278924, 20033187.34278924], decimal=6)
def test_grid(self):
# USGS Professional Paper 1395, pp 196--197, Table 30
globe = ccrs.Globe(ellipse=None,
semimajor_axis=1.0, semiminor_axis=1.0)
aeqd = ccrs.AzimuthalEquidistant(central_latitude=0.0,
central_longitude=0.0,
globe=globe)
geodetic = aeqd.as_geodetic()
expected = ('+a=1.0 +b=1.0 +proj=aeqd +lon_0=0.0 +lat_0=0.0 '
'+x_0=0.0 +y_0=0.0 +no_defs')
assert_equal(aeqd.proj4_init, expected)
assert_almost_equal(np.array(aeqd.x_limits),
[-3.14159265, 3.14159265], decimal=6)
assert_almost_equal(np.array(aeqd.y_limits),
[-3.14159265, 3.14159265], decimal=6)
lats, lons = np.mgrid[0:100:10, 0:100:10]
result = aeqd.transform_points(geodetic, lons.ravel(), lats.ravel())
expected_x = np.array([
[0.00000, 0.17453, 0.34907, 0.52360, 0.69813,
0.87266, 1.04720, 1.22173, 1.39626, 1.57080],
[0.00000, 0.17275, 0.34546, 0.51807, 0.69054,
0.86278, 1.03472, 1.20620, 1.37704, 1.54693],
[0.00000, 0.16736, 0.33454, 0.50137, 0.66762,
0.83301, 0.99719, 1.15965, 1.31964, 1.47607],
[0.00000, 0.15822, 0.31607, 0.47314, 0.62896,
0.78296, 0.93436, 1.08215, 1.22487, 1.36035],
[0.00000, 0.14511, 0.28959, 0.43276, 0.57386,
0.71195, 0.84583, 0.97392, 1.09409, 1.20330],
[0.00000, 0.12765, 0.25441, 0.37931, 0.50127,
0.61904, 0.73106, 0.83535, 0.92935, 1.00969],
[0.00000, 0.10534, 0.20955, 0.31145, 0.40976,
0.50301, 0.58948, 0.66711, 0.73343, 0.78540],
[0.00000, 0.07741, 0.15362, 0.22740, 0.29744,
0.36234, 0.42056, 0.47039, 0.50997, 0.53724],
[0.00000, 0.04281, 0.08469, 0.12469, 0.16188,
0.19529, 0.22399, 0.24706, 0.26358, 0.27277],
[0.00000, 0.00000, 0.00000, 0.00000, 0.00000,
0.00000, 0.00000, 0.00000, 0.00000, 0.00000],
]).ravel()
assert_almost_equal(result[:, 0], expected_x, decimal=5)
expected_y = np.array([
[0.00000, 0.00000, 0.00000, 0.00000, 0.00000,
0.00000, 0.00000, 0.00000, 0.00000, 0.00000],
[0.17453, 0.17541, 0.17810, 0.18270, 0.18943,
0.19859, 0.21067, 0.22634, 0.24656, 0.27277],
[0.34907, 0.35079, 0.35601, 0.36497, 0.37803,
0.39579, 0.41910, 0.44916, 0.48772, 0.53724],
[0.52360, 0.52606, 0.53355, 0.54634, 0.56493,
0.59010, 0.62291, 0.66488, 0.71809, 0.78540],
[0.69813, 0.70119, 0.71046, 0.72626, 0.74912,
0.77984, 0.81953, 0.86967, 0.93221, 1.00969],
[0.87266, 0.87609, 0.88647, 0.90408, 0.92938,
0.96306, 1.00602, 1.05942, 1.12464, 1.20330],
[1.04720, 1.05068, 1.06119, 1.07891, 1.10415,
1.13733, 1.17896, 1.22963, 1.28993, 1.36035],
[1.22173, 1.22481, 1.23407, 1.24956, 1.27137,
1.29957, 1.33423, 1.37533, 1.42273, 1.47607],
[1.39626, 1.39829, 1.40434, 1.41435, 1.42823,
1.44581, 1.46686, 1.49104, 1.51792, 1.54693],
[1.57080, 1.57080, 1.57080, 1.57080, 1.57080,
1.57080, 1.57080, 1.57080, 1.57080, 1.57080],
]).ravel()
assert_almost_equal(result[:, 1], expected_y, decimal=5)
def test_sphere_transform(self):
# USGS Professional Paper 1395, pg 337
globe = ccrs.Globe(ellipse=None,
semimajor_axis=3.0, semiminor_axis=3.0)
aeqd = ccrs.AzimuthalEquidistant(central_latitude=40.0,
central_longitude=-100.0,
globe=globe)
geodetic = aeqd.as_geodetic()
expected = ('+a=3.0 +b=3.0 +proj=aeqd +lon_0=-100.0 +lat_0=40.0 '
'+x_0=0.0 +y_0=0.0 +no_defs')
assert_equal(aeqd.proj4_init, expected)
assert_almost_equal(np.array(aeqd.x_limits),
[-9.42477796, 9.42477796], decimal=6)
assert_almost_equal(np.array(aeqd.y_limits),
[-9.42477796, 9.42477796], decimal=6)
result = aeqd.transform_point(100.0, -20.0, geodetic)
assert_array_almost_equal(result, [-5.8311398, 5.5444634])
def test_ellipsoid_polar_transform(self):
# USGS Professional Paper 1395, pp 338--339
globe = ccrs.Globe(ellipse=None, semimajor_axis=6378388.0,
flattening=1 - np.sqrt(1 - 0.00672267))
aeqd = ccrs.AzimuthalEquidistant(central_latitude=90.0,
central_longitude=-100.0,
globe=globe)
geodetic = aeqd.as_geodetic()
expected = ('+a=6378388.0 +f=0.003367003355798981 +proj=aeqd '
'+lon_0=-100.0 +lat_0=90.0 +x_0=0.0 +y_0=0.0 +no_defs')
assert_equal(aeqd.proj4_init, expected)
assert_almost_equal(np.array(aeqd.x_limits),
[-20038296.88254529, 20038296.88254529], decimal=6)
assert_almost_equal(np.array(aeqd.y_limits),
# TODO: This is wrong. Globe.semiminor_axis does
# not account for flattening.
# [-19970827.86969727, 19970827.86969727]
[-20038296.88254529, 20038296.88254529], decimal=6)
result = aeqd.transform_point(5.0, 80.0, geodetic)
assert_array_almost_equal(result, [1078828.3, 289071.2], decimal=1)
def test_ellipsoid_guam_transform(self):
# USGS Professional Paper 1395, pp 339--340
globe = ccrs.Globe(ellipse=None, semimajor_axis=6378206.4,
flattening=1 - np.sqrt(1 - 0.00676866))
lat_0 = 13 + (28 + 20.87887 / 60) / 60
lon_0 = 144 + (44 + 55.50254 / 60) / 60
aeqd = ccrs.AzimuthalEquidistant(central_latitude=lat_0,
central_longitude=lon_0,
false_easting=50000.0,
false_northing=50000.0,
globe=globe)
geodetic = aeqd.as_geodetic()
expected = ('+a=6378206.4 +f=0.003390076308689371 +proj=aeqd '
'+lon_0=144.7487507055556 +lat_0=13.47246635277778 '
'+x_0=50000.0 +y_0=50000.0 +no_defs')
assert_equal(aeqd.proj4_init, expected)
assert_almost_equal(np.array(aeqd.x_limits),
[-19987726.36931940, 20087726.36931940], decimal=6)
assert_almost_equal(np.array(aeqd.y_limits),
# TODO: This is wrong. Globe.semiminor_axis does
# not account for flattening.
# [-19919796.94787477, 20019796.94787477]
[-19987726.36931940, 20087726.36931940], decimal=6)
pt_lat = 13 + (20 + 20.53846 / 60) / 60
pt_lon = 144 + (38 + 7.19265 / 60) / 60
result = aeqd.transform_point(pt_lon, pt_lat, geodetic)
# The paper uses an approximation, so we cannot match it exactly,
# hence, decimal=1, not 2.
assert_array_almost_equal(result, [37712.48, 35242.00], decimal=1)
def test_ellipsoid_micronesia_transform(self):
# USGS Professional Paper 1395, pp 340--341
globe = ccrs.Globe(ellipse=None, semimajor_axis=6378206.4,
flattening=1 - np.sqrt(1 - 0.00676866))
lat_0 = 15 + (11 + 5.6830 / 60) / 60
lon_0 = 145 + (44 + 29.9720 / 60) / 60
aeqd = ccrs.AzimuthalEquidistant(central_latitude=lat_0,
central_longitude=lon_0,
false_easting=28657.52,
false_northing=67199.99,
globe=globe)
geodetic = aeqd.as_geodetic()
expected = ('+a=6378206.4 +f=0.003390076308689371 +proj=aeqd '
'+lon_0=145.7416588888889 +lat_0=15.18491194444444 '
'+x_0=28657.52 +y_0=67199.99000000001 +no_defs')
assert_equal(aeqd.proj4_init, expected)
assert_almost_equal(np.array(aeqd.x_limits),
[-20009068.84931940, 20066383.88931940], decimal=6)
assert_almost_equal(np.array(aeqd.y_limits),
# TODO: This is wrong. Globe.semiminor_axis does
# not account for flattening.
# [-19902596.95787477, 20036996.93787477]
[-19970526.37931940, 20104926.35931940], decimal=6)
pt_lat = 15 + (14 + 47.4930 / 60) / 60
pt_lon = 145 + (47 + 34.9080 / 60) / 60
result = aeqd.transform_point(pt_lon, pt_lat, geodetic)
assert_array_almost_equal(result, [34176.20, 74017.88], decimal=2)
if __name__ == '__main__':
import nose
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)
Cartopy-0.14.2/lib/cartopy/tests/crs/test_geostationary.py 0000644 0013740 0002103 00000005636 12700747662 023455 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2013 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
"""
Tests for the Geostationary projection.
"""
from __future__ import (absolute_import, division, print_function)
import unittest
from numpy.testing import assert_almost_equal
from nose.tools import assert_equal
import cartopy.crs as ccrs
class TestGeostationary(unittest.TestCase):
def check_proj4_params(self, crs, expected):
pro4_params = sorted(crs.proj4_init.split(' +'))
assert_equal(expected, pro4_params)
def test_default(self):
geos = ccrs.Geostationary()
expected = ['+ellps=WGS84', 'h=35785831', 'lat_0=0', 'lon_0=0.0',
'no_defs', 'proj=geos', 'units=m', 'x_0=0', 'y_0=0']
self.check_proj4_params(geos, expected)
assert_almost_equal(geos.boundary.bounds,
(-5372584.78443894, -5372584.78443894,
5372584.78443894, 5372584.78443894),
decimal=4)
def test_eccentric_globe(self):
globe = ccrs.Globe(semimajor_axis=10000, semiminor_axis=5000,
ellipse=None)
geos = ccrs.Geostationary(satellite_height=50000,
globe=globe)
expected = ['+a=10000', 'b=5000', 'h=50000', 'lat_0=0', 'lon_0=0.0',
'no_defs', 'proj=geos', 'units=m', 'x_0=0', 'y_0=0']
self.check_proj4_params(geos, expected)
assert_almost_equal(geos.boundary.bounds,
(-8257.4338, -4532.9943, 8257.4338, 4532.9943),
decimal=4)
def test_eastings(self):
geos = ccrs.Geostationary(false_easting=5000000,
false_northing=-125000,)
expected = ['+ellps=WGS84', 'h=35785831', 'lat_0=0', 'lon_0=0.0',
'no_defs', 'proj=geos', 'units=m', 'x_0=5000000',
'y_0=-125000']
self.check_proj4_params(geos, expected)
assert_almost_equal(geos.boundary.bounds,
(-372584.78443894, -5497584.78443894,
10372584.78443894, 5247584.78443894),
decimal=4)
if __name__ == '__main__':
import nose
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)
Cartopy-0.14.2/lib/cartopy/tests/crs/test_lambert_azimuthal_equal_area.py 0000644 0013740 0002103 00000005471 12700747662 026445 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2015 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
from __future__ import (absolute_import, division, print_function)
import unittest
import numpy as np
from numpy.testing import assert_almost_equal
from nose.tools import assert_equal
import cartopy.crs as ccrs
class TestLambertAzimuthalEqualArea(unittest.TestCase):
def test_default(self):
crs = ccrs.LambertAzimuthalEqualArea()
expected = ('+ellps=WGS84 +proj=laea +lon_0=0.0 '
'+lat_0=0.0 +x_0=0.0 +y_0=0.0 +no_defs')
assert_equal(crs.proj4_init, expected)
assert_almost_equal(np.array(crs.x_limits),
[-12755636.1863, 12755636.1863],
decimal=4)
assert_almost_equal(np.array(crs.y_limits),
[-12727770.598700099, 12727770.598700099],
decimal=4)
def test_eccentric_globe(self):
globe = ccrs.Globe(semimajor_axis=1000, semiminor_axis=500,
ellipse=None)
crs = ccrs.LambertAzimuthalEqualArea(globe=globe)
expected = ('+a=1000 +b=500 +proj=laea +lon_0=0.0 +lat_0=0.0 '
'+x_0=0.0 +y_0=0.0 +no_defs')
assert_equal(crs.proj4_init, expected)
assert_almost_equal(np.array(crs.x_limits),
[-1999.9, 1999.9], decimal=1)
assert_almost_equal(np.array(crs.y_limits),
[-1380.17298647, 1380.17298647], decimal=4)
def test_offset(self):
crs = ccrs.LambertAzimuthalEqualArea()
crs_offset = ccrs.LambertAzimuthalEqualArea(false_easting=1234,
false_northing=-4321)
expected = ('+ellps=WGS84 +proj=laea +lon_0=0.0 +lat_0=0.0 '
'+x_0=1234 +y_0=-4321 +no_defs')
assert_equal(crs_offset.proj4_init, expected)
assert_equal(tuple(np.array(crs.x_limits) + 1234),
crs_offset.x_limits)
assert_equal(tuple(np.array(crs.y_limits) - 4321),
crs_offset.y_limits)
if __name__ == '__main__':
import nose
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)
Cartopy-0.14.2/lib/cartopy/tests/crs/test_lambert_conformal.py 0000644 0013740 0002103 00000010314 12700747662 024240 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2011 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
from __future__ import (absolute_import, division, print_function)
from numpy.testing import assert_array_almost_equal
from nose.tools import assert_equal, assert_not_equal, assert_true
import unittest
import cartopy.crs as ccrs
def test_defaults():
crs = ccrs.LambertConformal()
assert_equal(crs.proj4_init, ('+ellps=WGS84 +proj=lcc +lon_0=-96.0 '
'+lat_0=39.0 +x_0=0.0 +y_0=0.0 +lat_1=33 '
'+lat_2=45 +no_defs'))
def test_default_with_cutoff():
crs = ccrs.LambertConformal(cutoff=-80)
crs2 = ccrs.LambertConformal(cutoff=-80)
default = ccrs.LambertConformal()
assert_equal(crs.proj4_init, ('+ellps=WGS84 +proj=lcc +lon_0=-96.0 '
'+lat_0=39.0 +x_0=0.0 +y_0=0.0 +lat_1=33 '
'+lat_2=45 +no_defs'))
# Check the behaviour of !=, == and (not ==) for the different cutoffs.
assert_equal(crs, crs2)
assert_true(crs != default)
assert_not_equal(crs, default)
assert_not_equal(hash(crs), hash(default))
assert_equal(hash(crs), hash(crs2))
assert_array_almost_equal(crs.y_limits,
(-49788019.81831982, 30793476.08487709))
def test_specific_lambert():
# This projection comes from EPSG Projection 3034 - ETRS89 / ETRS-LCC.
crs = ccrs.LambertConformal(central_longitude=10,
standard_parallels=(35, 65),
central_latitude=52,
false_easting=4000000,
false_northing=2800000,
globe=ccrs.Globe(ellipse='GRS80'))
assert_equal(crs.proj4_init, ('+ellps=GRS80 +proj=lcc +lon_0=10 '
'+lat_0=52 +x_0=4000000 +y_0=2800000 '
'+lat_1=35 +lat_2=65 +no_defs'))
class Test_LambertConformal_standard_parallels(unittest.TestCase):
def test_single_value(self):
crs = ccrs.LambertConformal(standard_parallels=[1.])
assert_equal(crs.proj4_init, ('+ellps=WGS84 +proj=lcc +lon_0=-96.0 '
'+lat_0=39.0 +x_0=0.0 +y_0=0.0 '
'+lat_1=1.0 +no_defs'))
def test_no_parallel(self):
with self.assertRaisesRegexp(ValueError, '1 or 2 standard parallels'):
ccrs.LambertConformal(standard_parallels=[])
def test_too_many_parallel(self):
with self.assertRaisesRegexp(ValueError, '1 or 2 standard parallels'):
ccrs.LambertConformal(standard_parallels=[1, 2, 3])
def test_single_spole(self):
s_pole_crs = ccrs.LambertConformal(standard_parallels=[-1.])
assert_array_almost_equal(s_pole_crs.x_limits,
(-19840440, 19840440.),
decimal=0)
assert_array_almost_equal(s_pole_crs.y_limits,
(-370239953, -8191953),
decimal=0)
def test_single_npole(self):
n_pole_crs = ccrs.LambertConformal(standard_parallels=[1.])
assert_array_almost_equal(n_pole_crs.x_limits,
(-20222156, 20222156),
decimal=0)
assert_array_almost_equal(n_pole_crs.y_limits,
(-8164817, 360848720),
decimal=0)
if __name__ == '__main__':
import nose
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)
Cartopy-0.14.2/lib/cartopy/tests/crs/test_mercator.py 0000644 0013740 0002103 00000005212 12700747662 022367 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2013 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
from __future__ import (absolute_import, division, print_function)
import unittest
from numpy.testing import assert_almost_equal
from nose.tools import assert_equal, assert_true, assert_not_equal
import cartopy.crs as ccrs
def test_default():
crs = ccrs.Mercator()
assert_equal(crs.proj4_init, ('+ellps=WGS84 +proj=merc +lon_0=0.0 +k=1 '
'+units=m +no_defs'))
assert_almost_equal(crs.boundary.bounds,
[-20037508, -15496571, 20037508, 18764656], decimal=0)
def test_eccentric_globe():
globe = ccrs.Globe(semimajor_axis=10000, semiminor_axis=5000,
ellipse=None)
crs = ccrs.Mercator(globe=globe, min_latitude=-40, max_latitude=40)
assert_equal(crs.proj4_init, ('+a=10000 +b=5000 +proj=merc +lon_0=0.0 '
'+k=1 +units=m +no_defs'))
assert_almost_equal(crs.boundary.bounds,
[-31415.93, -2190.5, 31415.93, 2190.5], decimal=2)
assert_almost_equal(crs.x_limits, [-31415.93, 31415.93], decimal=2)
assert_almost_equal(crs.y_limits, [-2190.5, 2190.5], decimal=2)
def test_equality():
default = ccrs.Mercator()
crs = ccrs.Mercator(min_latitude=0)
crs2 = ccrs.Mercator(min_latitude=0)
# Check the == and != operators.
assert_equal(crs, crs2)
assert_not_equal(crs, default)
assert_true(crs != default)
assert_not_equal(hash(crs), hash(default))
assert_equal(hash(crs), hash(crs2))
def test_central_longitude():
cl = 10.0
crs = ccrs.Mercator(central_longitude=cl)
proj4_str = ('+ellps=WGS84 +proj=merc +lon_0={} +k=1 '
'+units=m +no_defs'.format(cl))
assert_equal(crs.proj4_init, proj4_str)
assert_almost_equal(crs.boundary.bounds,
[-20037508, -15496570, 20037508, 18764656], decimal=0)
if __name__ == '__main__':
import nose
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)
Cartopy-0.14.2/lib/cartopy/tests/crs/test_robinson.py 0000644 0013740 0002103 00000007511 12705644356 022411 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2013 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
'''
Tests for Robinson projection.
For now, mostly tests the workaround for a specific problem.
Problem report in : https://github.com/SciTools/cartopy/issues/23
Fix covered in : https://github.com/SciTools/cartopy/pull/277
'''
from __future__ import (absolute_import, division, print_function)
import sys
import unittest
from nose.tools import assert_true, assert_false, assert_equal
import numpy as np
from numpy.testing import assert_array_almost_equal as assert_arr_almost_eq
import cartopy.crs as ccrs
_NAN = float('nan')
_CRS_PC = ccrs.PlateCarree()
_CRS_ROB = ccrs.Robinson()
# Increase tolerance if using older proj.4 releases
_TOL = -1 if ccrs.PROJ4_VERSION < (4, 9) else 7
def test_transform_point():
# this way has always worked
result = _CRS_ROB.transform_point(35.0, 70.0, _CRS_PC)
assert_arr_almost_eq(result, (2376187.27182751, 7275317.81573085), _TOL)
# this always did something, but result has altered
result = _CRS_ROB.transform_point(_NAN, 70.0, _CRS_PC)
assert_true(np.all(np.isnan(result)))
# this used to crash + is now fixed
result = _CRS_ROB.transform_point(35.0, _NAN, _CRS_PC)
assert_true(np.all(np.isnan(result)))
def test_transform_points():
# these always worked
result = _CRS_ROB.transform_points(_CRS_PC,
np.array([35.0]),
np.array([70.0]))
assert_arr_almost_eq(result,
[[2376187.27182751, 7275317.81573085, 0]], _TOL)
result = _CRS_ROB.transform_points(_CRS_PC,
np.array([35.0]),
np.array([70.0]),
np.array([0.0]))
assert_arr_almost_eq(result,
[[2376187.27182751, 7275317.81573085, 0]], _TOL)
# this always did something, but result has altered
result = _CRS_ROB.transform_points(_CRS_PC,
np.array([_NAN]),
np.array([70.0]))
assert_true(np.all(np.isnan(result)))
# this used to crash + is now fixed
result = _CRS_ROB.transform_points(_CRS_PC,
np.array([35.0]),
np.array([_NAN]))
assert_true(np.all(np.isnan(result)))
# multipoint case
x = np.array([10.0, 21.0, 0.0, 77.7, _NAN, 0.0])
y = np.array([10.0, _NAN, 10.0, 77.7, 55.5, 0.0])
z = np.array([10.0, 0.0, 0.0, _NAN, 55.5, 0.0])
expect_result = np.array(
[[9.40422591e+05, 1.06952091e+06, 1.00000000e+01],
[11.1, 11.2, 11.3],
[0.0, 1069520.91213902, 0.0],
[22.1, 22.2, 22.3],
[33.1, 33.2, 33.3],
[0.0, 0.0, 0.0]])
result = _CRS_ROB.transform_points(_CRS_PC, x, y, z)
assert_equal(result.shape, (6, 3))
assert_true(np.all(np.isnan(result[[1, 3, 4], :])))
result[[1, 3, 4], :] = expect_result[[1, 3, 4], :]
assert_false(np.any(np.isnan(result)))
assert_true(np.allclose(result, expect_result))
if __name__ == '__main__':
import nose
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)
Cartopy-0.14.2/lib/cartopy/tests/crs/test_rotated_geodetic.py 0000644 0013740 0002103 00000003125 12700747662 024061 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2014 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
"""
Tests for the Transverse Mercator projection, including OSGB and OSNI.
"""
from __future__ import (absolute_import, division, print_function)
import unittest
from numpy.testing import assert_almost_equal
from nose.tools import assert_equal
import cartopy.crs as ccrs
class TestRotatedPole(unittest.TestCase):
def check_proj4_params(self, crs, expected):
pro4_params = sorted(crs.proj4_init.split(' +'))
assert_equal(expected, pro4_params)
def test_default(self):
geos = ccrs.RotatedPole(60, 50, 80)
expected = ['+ellps=WGS84', 'lon_0=240', 'no_defs', 'o_lat_p=50',
'o_lon_p=80', 'o_proj=latlon', 'proj=ob_tran',
'to_meter=0.0174532925199433']
self.check_proj4_params(geos, expected)
if __name__ == '__main__':
import nose
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)
Cartopy-0.14.2/lib/cartopy/tests/crs/test_rotated_pole.py 0000644 0013740 0002103 00000003127 12700747662 023237 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2014 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
"""
Tests for the Rotated Geodetic coordinate system.
"""
from __future__ import (absolute_import, division, print_function)
import unittest
from numpy.testing import assert_almost_equal
from nose.tools import assert_equal
import cartopy.crs as ccrs
class TestRotatedGeodetic(unittest.TestCase):
def check_proj4_params(self, crs, expected):
pro4_params = sorted(crs.proj4_init.split(' +'))
assert_equal(expected, pro4_params)
def test_default(self):
geos = ccrs.RotatedGeodetic(30, 15, 27)
expected = ['+datum=WGS84', 'ellps=WGS84', 'lon_0=210', 'no_defs',
'o_lat_p=15', 'o_lon_p=27', 'o_proj=latlon',
'proj=ob_tran', 'to_meter=0.0174532925199433']
self.check_proj4_params(geos, expected)
if __name__ == '__main__':
import nose
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)
Cartopy-0.14.2/lib/cartopy/tests/crs/test_sinusoidal.py 0000644 0013740 0002103 00000006732 12700747662 022735 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
from __future__ import (absolute_import, division, print_function)
import unittest
import numpy as np
from numpy.testing import assert_almost_equal
from nose.tools import assert_equal
import cartopy.crs as ccrs
class TestSinusoidal(unittest.TestCase):
def test_default(self):
crs = ccrs.Sinusoidal()
expected = ('+ellps=WGS84 +proj=sinu +lon_0=0.0 '
'+x_0=0.0 +y_0=0.0 +no_defs')
assert_equal(crs.proj4_init, expected)
assert_almost_equal(np.array(crs.x_limits),
[-20037508.3428, 20037508.3428],
decimal=4)
assert_almost_equal(np.array(crs.y_limits),
[-10001965.7293, 10001965.7293],
decimal=4)
def test_eccentric_globe(self):
globe = ccrs.Globe(semimajor_axis=1000, semiminor_axis=500,
ellipse=None)
crs = ccrs.Sinusoidal(globe=globe)
expected = ('+a=1000 +b=500 +proj=sinu +lon_0=0.0 +x_0=0.0 '
'+y_0=0.0 +no_defs')
assert_equal(crs.proj4_init, expected)
assert_almost_equal(np.array(crs.x_limits),
[-3141.59, 3141.59], decimal=2)
assert_almost_equal(np.array(crs.y_limits),
[-1216.60, 1216.60], decimal=2)
def test_offset(self):
crs = ccrs.Sinusoidal()
crs_offset = ccrs.Sinusoidal(false_easting=1234,
false_northing=-4321)
expected = ('+ellps=WGS84 +proj=sinu +lon_0=0.0 +x_0=1234 '
'+y_0=-4321 +no_defs')
assert_equal(crs_offset.proj4_init, expected)
assert_equal(tuple(np.array(crs.x_limits) + 1234),
crs_offset.x_limits)
assert_equal(tuple(np.array(crs.y_limits) - 4321),
crs_offset.y_limits)
def test_MODIS(self):
# Testpoints verified with MODLAND Tile Calculator
# http://landweb.nascom.nasa.gov/cgi-bin/developer/tilemap.cgi
# Settings: Sinusoidal, Global map coordinates, Forward mapping
crs = ccrs.Sinusoidal.MODIS
lons = np.array([-180, -50, 40, 180])
lats = np.array([-89.999, 30, 20, 89.999])
expected_x = np.array([-349.33, -4814886.99,
4179566.79, 349.33])
expected_y = np.array([-10007443.48, 3335851.56,
2223901.04, 10007443.48])
assert_almost_equal(crs.transform_points(crs.as_geodetic(),
lons, lats),
np.c_[expected_x, expected_y, [0, 0, 0, 0]],
decimal=2)
if __name__ == '__main__':
import nose
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)
Cartopy-0.14.2/lib/cartopy/tests/crs/test_stereographic.py 0000644 0013740 0002103 00000006330 12700747662 023414 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2013 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
from __future__ import (absolute_import, division, print_function)
import unittest
import numpy as np
from numpy.testing import assert_almost_equal
from nose.tools import assert_equal
import cartopy.crs as ccrs
class TestStereographic(unittest.TestCase):
def test_default(self):
stereo = ccrs.Stereographic()
expected = ('+ellps=WGS84 +proj=stere +lat_0=0.0 '
'+lon_0=0.0 +x_0=0.0 +y_0=0.0 +no_defs')
assert_equal(stereo.proj4_init, expected)
assert_almost_equal(np.array(stereo.x_limits),
[-5e7, 5e7], decimal=4)
assert_almost_equal(np.array(stereo.y_limits),
[-5e7, 5e7], decimal=4)
def test_eccentric_globe(self):
globe = ccrs.Globe(semimajor_axis=1000, semiminor_axis=500,
ellipse=None)
stereo = ccrs.Stereographic(globe=globe)
expected = ('+a=1000 +b=500 +proj=stere +lat_0=0.0 +lon_0=0.0 '
'+x_0=0.0 +y_0=0.0 +no_defs')
assert_equal(stereo.proj4_init, expected)
# The limits in this test are sensible values, but are by no means
# a "correct" answer - they mean that plotting the crs results in a
# reasonable map.
assert_almost_equal(np.array(stereo.x_limits),
[-7839.27971444, 7839.27971444], decimal=4)
assert_almost_equal(np.array(stereo.y_limits),
[-3932.82587779, 3932.82587779], decimal=4)
def test_true_scale(self):
# The "true_scale_latitude" parameter to Stereographic appears
# meaningless. This test just ensures that the correct proj4
# string is being created. (#339)
stereo = ccrs.Stereographic(true_scale_latitude=10)
expected = ('+ellps=WGS84 +proj=stere +lat_0=0.0 +lon_0=0.0 '
'+x_0=0.0 +y_0=0.0 +lat_ts=10 +no_defs')
assert_equal(stereo.proj4_init, expected)
def test_eastings(self):
stereo = ccrs.Stereographic()
stereo_offset = ccrs.Stereographic(false_easting=1234,
false_northing=-4321)
expected = ('+ellps=WGS84 +proj=stere +lat_0=0.0 +lon_0=0.0 '
'+x_0=1234 +y_0=-4321 +no_defs')
assert_equal(stereo_offset.proj4_init, expected)
assert_equal(tuple(np.array(stereo.x_limits) + 1234),
stereo_offset.x_limits)
if __name__ == '__main__':
import nose
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)
Cartopy-0.14.2/lib/cartopy/tests/crs/test_transverse_mercator.py 0000644 0013740 0002103 00000011130 12700747662 024637 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2013 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
"""
Tests for the Transverse Mercator projection, including OSGB and OSNI.
"""
from __future__ import (absolute_import, division, print_function)
import unittest
import numpy as np
import numpy.testing
import cartopy.crs as ccrs
class TestTransverseMercator(unittest.TestCase):
def setUp(self):
self.point_a = (-3.474083, 50.727301)
self.point_b = (0.5, 50.5)
self.src_crs = ccrs.PlateCarree()
def test_default(self):
proj = ccrs.TransverseMercator()
res = proj.transform_point(*self.point_a, src_crs=self.src_crs)
np.testing.assert_array_almost_equal(res, (-245269.53180633,
5627508.74354959))
res = proj.transform_point(*self.point_b, src_crs=self.src_crs)
np.testing.assert_array_almost_equal(res, (35474.63566645,
5596583.41949901))
def test_osgb_vals(self):
proj = ccrs.TransverseMercator(central_longitude=-2,
central_latitude=49,
scale_factor=0.9996012717,
false_easting=400000,
false_northing=-100000,
globe=ccrs.Globe(datum='OSGB36',
ellipse='airy'))
res = proj.transform_point(*self.point_a, src_crs=self.src_crs)
np.testing.assert_array_almost_equal(res, (295971.28667707,
93064.27666368))
res = proj.transform_point(*self.point_b, src_crs=self.src_crs)
np.testing.assert_array_almost_equal(res, (577274.98380140,
69740.49227181))
def test_nan(self):
proj = ccrs.TransverseMercator()
res = proj.transform_point(0.0, float('nan'), src_crs=self.src_crs)
self.assertTrue(np.all(np.isnan(res)))
res = proj.transform_point(float('nan'), 0.0, src_crs=self.src_crs)
self.assertTrue(np.all(np.isnan(res)))
class TestOSGB(unittest.TestCase):
def setUp(self):
self.point_a = (-3.474083, 50.727301)
self.point_b = (0.5, 50.5)
self.src_crs = ccrs.PlateCarree()
self.nan = float('nan')
def test_default(self):
proj = ccrs.OSGB()
res = proj.transform_point(*self.point_a, src_crs=self.src_crs)
np.testing.assert_array_almost_equal(res, (295971.28667707,
93064.27666368))
res = proj.transform_point(*self.point_b, src_crs=self.src_crs)
np.testing.assert_array_almost_equal(res, (577274.98380140,
69740.49227181))
def test_nan(self):
proj = ccrs.OSGB()
res = proj.transform_point(0.0, float('nan'), src_crs=self.src_crs)
self.assertTrue(np.all(np.isnan(res)))
res = proj.transform_point(float('nan'), 0.0, src_crs=self.src_crs)
self.assertTrue(np.all(np.isnan(res)))
class TestOSNI(unittest.TestCase):
def setUp(self):
self.point_a = (-6.826286, 54.725116)
self.src_crs = ccrs.PlateCarree()
self.nan = float('nan')
def test_default(self):
proj = ccrs.OSNI()
res = proj.transform_point(*self.point_a, src_crs=self.src_crs)
np.testing.assert_array_almost_equal(res, (275614.87105610,
386984.15347340))
def test_nan(self):
proj = ccrs.OSNI()
res = proj.transform_point(0.0, float('nan'), src_crs=self.src_crs)
self.assertTrue(np.all(np.isnan(res)))
res = proj.transform_point(float('nan'), 0.0, src_crs=self.src_crs)
self.assertTrue(np.all(np.isnan(res)))
if __name__ == '__main__':
import nose
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)
Cartopy-0.14.2/lib/cartopy/tests/io/ 0000755 0013740 0002103 00000000000 12706121712 016746 5 ustar itpe avd 0000000 0000000 Cartopy-0.14.2/lib/cartopy/tests/io/__init__.py 0000644 0013740 0002103 00000001437 12700747662 021100 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2014 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
from __future__ import (absolute_import, division, print_function)
Cartopy-0.14.2/lib/cartopy/tests/io/test_downloaders.py 0000644 0013740 0002103 00000017531 12700747662 022723 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2011 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
from __future__ import (absolute_import, division, print_function)
import contextlib
import os
import shutil
import tempfile
import warnings
from nose.tools import assert_equal, assert_raises
import cartopy
import cartopy.io as cio
from cartopy.io.shapereader import NEShpDownloader
from cartopy.tests.mpl.test_caching import CallCounter
def test_Downloader_data():
di = cio.Downloader('https://testing.com/{category}/{name}.zip',
os.path.join('{data_dir}', '{category}',
'shape.shp'),
'/project/foobar/{category}/sample.shp')
replacement_dict = {'category': 'example',
'name': 'test',
'data_dir': os.path.join('/wibble', 'foo', 'bar')}
assert_equal(di.url(replacement_dict),
'https://testing.com/example/test.zip')
assert_equal(di.target_path(replacement_dict),
os.path.join('/wibble', 'foo', 'bar', 'example', 'shape.shp')
)
assert_equal(di.pre_downloaded_path(replacement_dict),
'/project/foobar/example/sample.shp'
)
@contextlib.contextmanager
def config_replace(replacement_dict):
"""
Provides a context manager to replace the ``cartopy.config['downloaders']``
dict with the given dictionary. Great for testing purposes!
"""
downloads_orig = cartopy.config['downloaders']
cartopy.config['downloaders'] = replacement_dict
yield
cartopy.config['downloaders'] = downloads_orig
@contextlib.contextmanager
def download_to_temp():
"""
Context manager which defaults the "data_dir" to a temporary directory
which is automatically cleaned up on exit.
"""
old_downloads_dict = cartopy.config['downloaders'].copy()
old_dir = cartopy.config['data_dir']
tmp_dir = tempfile.mkdtemp(suffix='_cartopy_data')
cartopy.config['data_dir'] = tmp_dir
try:
yield tmp_dir
cartopy.config['downloaders'] = old_downloads_dict
cartopy.config['data_dir'] = old_dir
finally:
shutil.rmtree(tmp_dir)
def test_from_config():
generic_url = 'https://example.com/generic_ne/{name}.zip'
land_downloader = cio.Downloader(generic_url, '', '')
generic_ne_downloader = cio.Downloader(generic_url, '', '')
ocean_spec = ('shapefile', 'natural_earth', '110m', 'physical', 'ocean')
land_spec = ('shapefile', 'natural_earth', '110m', 'physical', 'land')
generic_spec = ('shapefile', 'natural_earth')
target_config = {land_spec: land_downloader,
generic_spec: generic_ne_downloader,
}
with config_replace(target_config):
# ocean spec is not explicitly in the config, but a subset of it is,
# so check that an appropriate downloader is returned
r = cio.Downloader.from_config(ocean_spec)
# check the resulting download item produces a sensible url.
assert_equal(r.url({'name': 'ocean'}),
'https://example.com/generic_ne/ocean.zip')
downloaders = cio.config['downloaders']
r = cio.Downloader.from_config(land_spec)
assert r is land_downloader
def test_downloading_simple_ascii():
# downloads a file from the Google APIs. (very high uptime and file will
# always be there - if this goes down, most of the internet would break!)
# to test the downloading mechanisms.
file_url = 'https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/{name}.js'
format_dict = {'name': 'jquery'}
with download_to_temp() as tmp_dir:
target_template = os.path.join(tmp_dir, '{name}.txt')
tmp_fname = target_template.format(**format_dict)
dnld_item = cio.Downloader(file_url, target_template)
assert_equal(dnld_item.target_path(format_dict), tmp_fname)
with warnings.catch_warnings(record=True) as w:
assert_equal(dnld_item.path(format_dict), tmp_fname)
assert len(w) == 1, ('Expected a single download warning to be '
'raised. Got {}.'.format(len(w)))
assert issubclass(w[0].category, cio.DownloadWarning)
with open(tmp_fname, 'r') as fh:
_ = fh.readline()
assert_equal(" * jQuery JavaScript Library v1.8.2\n",
fh.readline())
# check that calling path again doesn't try re-downloading
with CallCounter(dnld_item, 'acquire_resource') as counter:
assert_equal(dnld_item.path(format_dict), tmp_fname)
assert counter.count == 0, 'Item was re-downloaded.'
def test_natural_earth_downloader():
# downloads a file to a temporary location, and uses that temporary
# location, then:
# * Checks that the file is only downloaded once even when calling
# multiple times
# * Checks that shapefiles have all the necessary files when downloaded
# * Checks that providing a path in a download item gets used rather
# than triggering another download
tmp_dir = tempfile.mkdtemp()
shp_path_template = os.path.join(tmp_dir,
'{category}_{resolution}_{name}.shp')
# picking a small-ish file to speed up download times, the file itself
# isn't important - it is the download mechanism that is.
format_dict = {'category': 'physical',
'name': 'rivers_lake_centerlines',
'resolution': '110m'}
try:
dnld_item = NEShpDownloader(target_path_template=shp_path_template)
# check that the file gets downloaded the first time path is called
with CallCounter(dnld_item, 'acquire_resource') as counter:
shp_path = dnld_item.path(format_dict)
assert counter.count == 1, 'Item not downloaded.'
assert_equal(shp_path_template.format(**format_dict), shp_path)
# check that calling path again doesn't try re-downloading
with CallCounter(dnld_item, 'acquire_resource') as counter:
assert_equal(dnld_item.path(format_dict), shp_path)
assert counter.count == 0, 'Item was re-downloaded.'
# check that we have the shp and the shx
exts = ['.shp', '.shx']
for ext in exts:
stem = os.path.splitext(shp_path)[0]
msg = "Shapefile's {0} file doesn't exist in {1}{0}".format(ext,
stem)
assert os.path.exists(stem + ext), msg
# check that providing a pre downloaded path actually works
pre_dnld = NEShpDownloader(target_path_template='/not/a/real/file.txt',
pre_downloaded_path_template=shp_path
)
# check that the pre_dnld downloader doesn't re-download, but instead
# uses the path of the previously downloaded item
with CallCounter(pre_dnld, 'acquire_resource') as counter:
assert_equal(pre_dnld.path(format_dict), shp_path)
assert counter.count == 0, 'Aquire resource called more than once.'
finally:
shutil.rmtree(tmp_dir)
if __name__ == '__main__':
import nose
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)
Cartopy-0.14.2/lib/cartopy/tests/io/test_ogc_clients.py 0000644 0013740 0002103 00000021432 12700747662 022666 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2011 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
from __future__ import (absolute_import, division, print_function)
import cartopy.io.ogc_clients as ogc
from cartopy.io.ogc_clients import _OWSLIB_AVAILABLE
try:
from owslib.wfs import WebFeatureService
from owslib.wms import WebMapService
from owslib.wmts import ContentMetadata, WebMapTileService
except ImportError:
WebMapService = None
ContentMetadata = None
WebMapTileService = None
import unittest
import cartopy.crs as ccrs
try:
from unittest import mock
except ImportError:
import mock
import numpy as np
RESOLUTION = (30, 30)
@unittest.skipIf(not _OWSLIB_AVAILABLE, 'OWSLib is unavailable.')
class test_WMSRasterSource(unittest.TestCase):
URI = 'http://vmap0.tiles.osgeo.org/wms/vmap0'
layer = 'basic'
layers = ['basic', 'ocean']
projection = ccrs.PlateCarree()
def test_string_service(self):
source = ogc.WMSRasterSource(self.URI, self.layer)
self.assertIsInstance(source.service, WebMapService)
self.assertIsInstance(source.layers, list)
self.assertEqual(source.layers, [self.layer])
def test_wms_service_instance(self):
service = WebMapService(self.URI)
source = ogc.WMSRasterSource(service, self.layer)
self.assertIs(source.service, service)
def test_multiple_layers(self):
source = ogc.WMSRasterSource(self.URI, self.layers)
self.assertEqual(source.layers, self.layers)
def test_no_layers(self):
msg = 'One or more layers must be defined.'
with self.assertRaisesRegexp(ValueError, msg):
ogc.WMSRasterSource(self.URI, [])
def test_extra_kwargs_empty(self):
source = ogc.WMSRasterSource(self.URI, self.layer,
getmap_extra_kwargs={})
self.assertEqual(source.getmap_extra_kwargs, {})
def test_extra_kwargs_None(self):
source = ogc.WMSRasterSource(self.URI, self.layer,
getmap_extra_kwargs=None)
self.assertEqual(source.getmap_extra_kwargs, {'transparent': True})
def test_extra_kwargs_non_empty(self):
kwargs = {'another': 'kwarg'}
source = ogc.WMSRasterSource(self.URI, self.layer,
getmap_extra_kwargs=kwargs)
self.assertEqual(source.getmap_extra_kwargs, kwargs)
def test_supported_projection(self):
source = ogc.WMSRasterSource(self.URI, self.layer)
source.validate_projection(self.projection)
def test_unsupported_projection(self):
source = ogc.WMSRasterSource(self.URI, self.layer)
# Patch dict of known Proj->SRS mappings so that it does
# not include any of the available SRSs from the WMS.
with mock.patch.dict('cartopy.io.ogc_clients._CRS_TO_OGC_SRS',
{ccrs.OSGB(): 'EPSG:27700'},
clear=True):
msg = 'not available'
with self.assertRaisesRegexp(ValueError, msg):
source.validate_projection(ccrs.Miller())
def test_fetch_img(self):
source = ogc.WMSRasterSource(self.URI, self.layer)
extent = [-10, 10, 40, 60]
located_image, = source.fetch_raster(self.projection, extent,
RESOLUTION)
img = np.array(located_image.image)
self.assertEqual(img.shape, RESOLUTION + (4,))
# No transparency in this image.
self.assertEqual(img[:, :, 3].min(), 255)
self.assertEqual(extent, located_image.extent)
def test_fetch_img_different_projection(self):
source = ogc.WMSRasterSource(self.URI, self.layer)
extent = [-570000, 5100000, 870000, 3500000]
located_image, = source.fetch_raster(ccrs.Orthographic(), extent,
RESOLUTION)
img = np.array(located_image.image)
self.assertEqual(img.shape, RESOLUTION + (4,))
def test_multi_image_result(self):
source = ogc.WMSRasterSource(self.URI, self.layer)
crs = ccrs.PlateCarree(central_longitude=180)
extent = [-15, 25, 45, 85]
located_images = source.fetch_raster(crs, extent, RESOLUTION)
self.assertEqual(len(located_images), 2)
def test_float_resolution(self):
# The resolution (in pixels) should be cast to ints.
source = ogc.WMSRasterSource(self.URI, self.layer)
extent = [-570000, 5100000, 870000, 3500000]
located_image, = source.fetch_raster(self.projection, extent,
[19.5, 39.1])
img = np.array(located_image.image)
self.assertEqual(img.shape, (40, 20, 4))
@unittest.skipIf(not _OWSLIB_AVAILABLE, 'OWSLib is unavailable.')
class test_WMTSRasterSource(unittest.TestCase):
URI = 'https://map1c.vis.earthdata.nasa.gov/wmts-geo/wmts.cgi'
layer_name = 'VIIRS_CityLights_2012'
projection = ccrs.PlateCarree()
def test_string_service(self):
source = ogc.WMTSRasterSource(self.URI, self.layer_name)
self.assertIsInstance(source.wmts, WebMapTileService)
self.assertIsInstance(source.layer, ContentMetadata)
self.assertEqual(source.layer.name, self.layer_name)
def test_wmts_service_instance(self):
service = WebMapTileService(self.URI)
source = ogc.WMTSRasterSource(service, self.layer_name)
self.assertIs(source.wmts, service)
def test_supported_projection(self):
source = ogc.WMTSRasterSource(self.URI, self.layer_name)
source.validate_projection(self.projection)
def test_unsupported_projection(self):
source = ogc.WMTSRasterSource(self.URI, self.layer_name)
msg = 'Unable to find tile matrix for projection.'
with self.assertRaisesRegexp(ValueError, msg):
source.validate_projection(ccrs.Miller())
def test_fetch_img(self):
source = ogc.WMTSRasterSource(self.URI, self.layer_name)
extent = [-10, 10, 40, 60]
located_image, = source.fetch_raster(self.projection, extent,
RESOLUTION)
img = np.array(located_image.image)
self.assertEqual(img.shape, (512, 512, 4))
# No transparency in this image.
self.assertEqual(img[:, :, 3].min(), 255)
self.assertEqual((-180.0, 107.99999999999994,
-197.99999999999994, 90.0), located_image.extent)
@unittest.skipIf(not _OWSLIB_AVAILABLE, 'OWSLib is unavailable.')
class test_WFSGeometrySource(unittest.TestCase):
URI = 'https://nsidc.org/cgi-bin/atlas_south?service=WFS'
typename = 'land_excluding_antarctica'
native_projection = ccrs.Stereographic(central_latitude=-90,
true_scale_latitude=-71)
def test_string_service(self):
service = WebFeatureService(self.URI)
source = ogc.WFSGeometrySource(self.URI, self.typename)
self.assertIsInstance(source.service, type(service))
self.assertEqual(source.features, [self.typename])
def test_wfs_service_instance(self):
service = WebFeatureService(self.URI)
source = ogc.WFSGeometrySource(service, self.typename)
self.assertIs(source.service, service)
self.assertEqual(source.features, [self.typename])
def test_default_projection(self):
source = ogc.WFSGeometrySource(self.URI, self.typename)
self.assertEqual(source.default_projection(), self.native_projection)
def test_unsupported_projection(self):
source = ogc.WFSGeometrySource(self.URI, self.typename)
with self.assertRaisesRegexp(ValueError,
'Geometries are only available '
'in projection'):
source.fetch_geometries(ccrs.PlateCarree(), [-180, 180, -90, 90])
def test_fetch_geometries(self):
source = ogc.WFSGeometrySource(self.URI, self.typename)
# Extent covering New Zealand.
extent = (-99012, 1523166, -6740315, -4589165)
geoms = source.fetch_geometries(self.native_projection, extent)
self.assertEqual(len(geoms), 23)
if __name__ == '__main__':
import nose
nose.runmodule(argv=['-sv', '--with-doctest'], exit=False)
Cartopy-0.14.2/lib/cartopy/tests/io/test_srtm.py 0000644 0013740 0002103 00000012606 12700747662 021365 0 ustar itpe avd 0000000 0000000 # (C) British Crown Copyright 2011 - 2016, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see .
from __future__ import (absolute_import, division, print_function)
import unittest
import warnings
import numpy as np
from numpy.testing import assert_array_equal
import cartopy.crs as ccrs
import cartopy.io.srtm
from cartopy.tests.io.test_downloaders import download_to_temp
def _test_srtm_retrieve(Source, read_SRTM, max_, min_, pt):
# test that the download mechanism for SRTM works
with download_to_temp() as tmp_dir:
with warnings.catch_warnings(record=True) as w:
r = Source().srtm_fname(-4, 50)
assert len(w) == 1
assert issubclass(w[0].category, cartopy.io.DownloadWarning)
assert r.startswith(tmp_dir), 'File not downloaded to tmp dir'
img, _, _ = read_SRTM(r)
# check that the data is fairly sensible
msg = 'SRTM data has changed. Arbitrary value testing failed. Got {}.'
assert img.max() == max_, msg.format(img.max())
assert img.min() == min_, msg.format(img.min())
assert img[-10, 12] == pt, msg.format(img[-10, 12])
def test_srtm3_retrieve():
_test_srtm_retrieve(cartopy.io.srtm.SRTM3Source,
cartopy.io.srtm.read_SRTM3,
602, -34, 78)
def test_srtm1_retrieve():
_test_srtm_retrieve(cartopy.io.srtm.SRTM1Source,
cartopy.io.srtm.read_SRTM1,
602, -37, 50)
def _test_srtm_out_of_range(Source, shape):
# Somewhere over the pacific the elevation should be 0.
img, _, _ = Source().combined(120, 2, 2, 2)
assert_array_equal(img, np.zeros(np.array(shape) * 2))
def test_srtm3_out_of_range():
_test_srtm_out_of_range(cartopy.io.srtm.SRTM3Source, (1201, 1201))
def test_srtm1_out_of_range():
_test_srtm_out_of_range(cartopy.io.srtm.SRTM1Source, (3601, 3601))
class TestSRTMSource__single_tile(unittest.TestCase):
def _out_of_range(self, source):
msg = 'No srtm tile found for those coordinates.'
with self.assertRaisesRegexp(ValueError, msg):
source.single_tile(-25, 50)
def test_out_of_range3(self):
self._out_of_range(cartopy.io.srtm.SRTM3Source())
def test_out_of_range1(self):
self._out_of_range(cartopy.io.srtm.SRTM1Source())
def _in_range(self, source, shape):
img, crs, extent = source.single_tile(-1, 50)
self.assertIsInstance(img, np.ndarray)
self.assertEqual(img.shape, shape)
self.assertEqual(img.dtype, np.dtype('>i2'))
self.assertEqual(crs, ccrs.PlateCarree())
self.assertEqual(extent, (-1, 0, 50, 51))
def test_in_range3(self):
self._in_range(cartopy.io.srtm.SRTM3Source(), (1201, 1201))
def test_in_range1(self):
self._in_range(cartopy.io.srtm.SRTM1Source(), (3601, 3601))
def _zeros(self, source):
_, _, extent = source.single_tile(0, 50)
self.assertEqual(extent, (0, 1, 50, 51))
def test_zeros3(self):
self._zeros(cartopy.io.srtm.SRTM3Source())
def test_zeros1(self):
self._zeros(cartopy.io.srtm.SRTM1Source())
class TestSRTMSource__combined(unittest.TestCase):
def _trivial(self, source):
e_img, e_crs, e_extent = source.single_tile(-3, 50)
r_img, r_crs, r_extent = source.combined(-3, 50, 1, 1)
assert_array_equal(e_img, r_img)
self.assertEqual(e_crs, r_crs)
self.assertEqual(e_extent, r_extent)
def test_trivial3(self):
self._trivial(cartopy.io.srtm.SRTM3Source())
def test_trivial1(self):
self._trivial(cartopy.io.srtm.SRTM1Source())
def _2by2(self, source):
e_img, _, e_extent = source.combined(-1, 50, 2, 1)
self.assertEqual(e_extent, (-1, 1, 50, 51))
imgs = [source.single_tile(-1, 50)[0],
source.single_tile(0, 50)[0]]
assert_array_equal(np.hstack(imgs), e_img)
def test_2by2_3(self):
self._2by2(cartopy.io.srtm.SRTM3Source())
def test_2by2_1(self):
self._2by2(cartopy.io.srtm.SRTM1Source())
class TestSRTM3Source_fetch_raster(unittest.TestCase):
def _as_combined(self, source):
e_img, e_crs, e_extent = source.combined(-1, 50, 2, 1)
imgs = source.fetch_raster(ccrs.PlateCarree(),
(-0.9, 0.1, 50.1, 50.999),
None)
self.assertEqual(len(imgs), 1)
r_img, r_extent = imgs[0]
self.assertEqual(e_extent, r_extent)
assert_array_equal(e_img[::-1, :], r_img)
def test_as_combined3(self):
self._as_combined(cartopy.io.srtm.SRTM3Source())
def test_as_combined1(self):
self._as_combined(cartopy.io.srtm.SRTM1Source())
if __name__ == '__main__':
import nose
nose.runmodule(argv=['-sv', '--with-doctest'], exit=False)
Cartopy-0.14.2/lib/cartopy/tests/mpl/ 0000755 0013740 0002103 00000000000 12706121712 017127 5 ustar itpe avd 0000000 0000000 Cartopy-0.14.2/lib/cartopy/tests/mpl/baseline_images/ 0000755 0013740 0002103 00000000000 12706121712 022236 5 ustar itpe avd 0000000 0000000 Cartopy-0.14.2/lib/cartopy/tests/mpl/baseline_images/mpl/ 0000755 0013740 0002103 00000000000 12706121712 023026 5 ustar itpe avd 0000000 0000000 Cartopy-0.14.2/lib/cartopy/tests/mpl/baseline_images/mpl/test_crs/ 0000755 0013740 0002103 00000000000 12706121712 024654 5 ustar itpe avd 0000000 0000000 Cartopy-0.14.2/lib/cartopy/tests/mpl/baseline_images/mpl/test_crs/lambert_conformal_south.png 0000644 0013740 0002103 00000277111 12520135744 032307 0 ustar itpe avd 0000000 0000000 PNG
IHDR X vp sBIT|d pHYs a a?i IDATxwXg5tk%QDEc"E#5F%+vc ( ~?oj\Wtv
fsDD@DDDDDJ """"H DD(DGG#..iiiHOOmc `bboz(QlmmaaaJ}!(h4DDDѣGxybo{U^_
_c6K)m}*VҥK1, DD ""