How to add custom shapefile to map using cartopy - cartopy

Using basemap I used to add my custom boundary shapefile like this:
map = Basemap(..)
map.readshapefile(file.shp, 'attribute', drawbounds=True)
How can I do the same using cartopy?
I tried this:
ax.add_feature(cfeature.shapereader.Polygon('file.shp'))
but that's not working..

There is currently no ShapefileFeature class (though that would be easy enough to create, and would probably make a lot of sense) so if you really want to use the feature interface then there is a hoop to jump through:
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from cartopy.io.shapereader import Reader
from cartopy.feature import ShapelyFeature
fname = '50m_glaciated_areas.shp'
ax = plt.axes(projection=ccrs.Robinson())
shape_feature = ShapelyFeature(Reader(fname).geometries(),
ccrs.PlateCarree(), facecolor='none')
ax.add_feature(shape_feature)
plt.show()
Alternatively, you could just use the add_geometries method, which is not making use of the feature interface (and so, in the future, will not be optimised to read from disk only the geometries which are actually being drawn as would be the case with a ShapefileFeature class):
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from cartopy.io.shapereader import Reader
fname = '50m_glaciated_areas.shp'
ax = plt.axes(projection=ccrs.Robinson())
ax.add_geometries(Reader(fname).geometries(),
ccrs.PlateCarree(),
facecolor='white', hatch='xxxx')
plt.show()
HTH

Related

Plotting a rasterio raster on a Cartopy GeoAxes

I've seen a few other questions on this topic, but the library has changed enough that the answers to those no longer seem to apply.
Rasterio used to include an example for plotting a rasterio raster on a Cartopy GeoAxes. The example went roughly like this:
import matplotlib.pyplot as plt
import rasterio
from rasterio import plot
import cartopy
import cartopy.crs as ccrs
world = rasterio.open(r"../tests/data/world.rgb.tif")
fig = plt.figure(figsize=(20, 12))
ax = plt.axes(projection=ccrs.InterruptedGoodeHomolosine())
ax.set_global()
plot.show(world, origin='upper', transform=ccrs.PlateCarree(), interpolation=None, ax=ax)
ax.coastlines()
ax.add_feature(cartopy.feature.BORDERS)
However, this code no longer draws the raster. Instead, I get something like this:
It should look like this:
When I asked about this in the rasterio issues tracker, they told me the example was deprecated (and deleted the example). Still, I wonder if there's some way to do what I'm trying to do. Can anyone point me in the right direction?
I think you may want to read the data to a numpy.ndarray and plot it using ax.imshow, where ax is your cartopy.GeoAxes (as you have it already). I offer an example of what I mean, below.
I clipped a small chunk of Landsat surface temperature and some agricultural fields for this example. Get them on this drive link.
Note fields are in WGS 84 (epsg 4326), Landsat image is in UTM Zone 12 (epsg 32612), and I want my map in Lambert Conformal Conic. Cartopy makes this easy.
import numpy as np
import cartopy.crs as ccrs
from cartopy.io.shapereader import Reader
from cartopy.feature import ShapelyFeature
import rasterio
import matplotlib.pyplot as plt
def cartopy_example(raster, shapefile):
with rasterio.open(raster, 'r') as src:
raster_crs = src.crs
left, bottom, right, top = src.bounds
landsat = src.read()[0, :, :]
landsat = np.ma.masked_where(landsat <= 0,
landsat,
copy=True)
landsat = (landsat - np.min(landsat)) / (np.max(landsat) - np.min(landsat))
proj = ccrs.LambertConformal(central_latitude=40,
central_longitude=-110)
fig = plt.figure(figsize=(20, 16))
ax = plt.axes(projection=proj)
ax.set_extent([-110.8, -110.4, 45.3, 45.6], crs=ccrs.PlateCarree())
shape_feature = ShapelyFeature(Reader(shapefile).geometries(),
ccrs.PlateCarree(), edgecolor='blue')
ax.add_feature(shape_feature, facecolor='none')
ax.imshow(landsat, transform=ccrs.UTM(raster_crs['zone']),
cmap='inferno',
extent=(left, right, bottom, top))
plt.savefig('surface_temp.png')
feature_source = 'fields.shp'
raster_source = 'surface_temperature_32612.tif'
cartopy_example(raster_source, feature_source)
The trick with Cartopy is to remember to use the projection keyword for your axes object, as this renders the map in a nice projection of your choice (LCC in my case). Use transform keyword to indicate what projection system your data is in, so Cartopy knows how to render it.
No need of rasterio. Get a bluemarble image, then plot it.
Here is the working code:
import cartopy
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
fig = plt.figure(figsize=(10, 5))
ax = plt.axes(projection=ccrs.InterruptedGoodeHomolosine())
# source of the image:
# https://eoimages.gsfc.nasa.gov/images/imagerecords/73000/73909/world.topo.bathy.200412.3x5400x2700.jpg
fname = "./world.topo.bathy.200412.3x5400x2700.jpg"
img_origin = 'lower'
img = plt.imread(fname)
img = img[::-1]
ax.imshow(img, origin=img_origin, transform=ccrs.PlateCarree(), extent=[-180, 180, -90, 90])
ax.coastlines()
ax.add_feature(cartopy.feature.BORDERS)
ax.set_global()
plt.show()
The output plot:

pycharm highlights the code ax.set_zlabel('Z')

I want to know why IDE pycharm(2018.1.1) highlighting the code ax.set_zlabel('Z')
with hint unresolved attribute reference 'set_zlabel' for class Axes
but the code run normally.
these are import packages
import numpy as np
from scipy.stats import multivariate_normal
from sklearn.mixture import GaussianMixture
from mpl_toolkits.mplot3d import Axes3D
import matplotlib as mpl
import matplotlib.pyplot as plt
from sklearn.metrics.pairwise import pairwise_distances_argmin
Interestingly, PyCharm doesn't complain for me with the following code, but I'm guessing that's an effect of a newer version (I'm using matplotlib 3.0.0 and PyCharm 2018.1.4).
In any case, I believe the problem comes from the fact that PyCharm might not know that e.g. add_subplot() can return different objects depending on the projection used.
fig = plt.figure()
ax1 = fig.add_subplot(111)
type(ax1)
>>> matplotlib.axes._subplots.AxesSubplot
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax1 = fig.add_subplot(111, projection="3d")
type(ax1)
>>> matplotlib.axes._subplots.Axes3DSubplot
However, you can help PyCharm by providing "type hints" (see https://www.jetbrains.com/help/pycharm/type-hinting-in-product.html)
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax1 = fig.add_subplot(111, projection="3d") # type: Axes3D
ax1.set_zlabel("z-label")
or, if using Python 3+:
ax1: Axes3D = fig.add_subplot(111, projection="3d")

Misplaced annotation of text Cartopy

Ran into an interesting problem with the behavior of the text annotation functions in cartopy following the documentation which I don't think should be doing this - believe its related to how the text method takes the transform and applies it, perhaps similar to the issue shown here for .annotate (Why the annotate worked unexpected here in cartopy?). Basically no matter what is specified in terms of lat/lon and the transform it always plots at the center point of the plot. Code sample below:
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import cartopy.io.shapereader as shpreader
from matplotlib.colors import BoundaryNorm
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.patheffects as path_effects
def basic_map(proj):
fig = plt.figure(figsize=(12, 8))
view = fig.add_axes([0, 0, 1, 1], projection=proj)
view.set_extent([-120, -73, 23, 50])
view.add_feature(cfeature.STATES.with_scale('50m'))
view.add_feature(cfeature.OCEAN.with_scale('50m'),facecolor='white')
view.add_feature(cfeature.COASTLINE.with_scale('50m'))
view.add_feature(cfeature.BORDERS, linestyle=':')
return fig, view
proj = ccrs.AlbersEqualArea(central_longitude=-97.0000, central_latitude=38.0000)
fig, view = basic_map(prod)
view.text(-70,41, 'Northeast', color='black', fontsize=20, fontweight='bold',transform=proj,
path_effects=[path_effects.withSimplePatchShadow(),path_effects.PathPatchEffect(edgecolor='black', linewidth=0.6,facecolor='black')])

Control gridline spacing in seaborn

I'd like to change the spacing of the horizontal grid lines on a seaborn chart, I've tried setting the style with no luck:
seaborn.set_style("whitegrid", {
"ytick.major.size": 0.1,
"ytick.minor.size": 0.05,
'grid.linestyle': '--'
})
bar(range(len(data)),data,alpha=0.5)
plot(avg_line)
The gridlines are set automatically desipite me trying to overide the tick size
Any suggestions? Thanks!
you can set the tick locations explicitly later, and it will draw the grid at those locations.
The neatest way to do this is to use a MultpleLocator from the matplotlib.ticker module.
For example:
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
sns.set_style("whitegrid", {'grid.linestyle': '--'})
fig,ax = plt.subplots()
ax.bar(np.arange(0,50,1),np.random.rand(50)*0.016-0.004,alpha=0.5)
ax.yaxis.set_major_locator(ticker.MultipleLocator(0.005))
plt.show()
The OP asked about modifying tick distances in Seaborn.
If you are working in Seaborn and you use a plotting feature that returns an Axes object, then you can work with that just like any other Axes object in matplotlib. For example:
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.api as sm
from matplotlib.ticker import MultipleLocator
df = sm.datasets.get_rdataset("Guerry", "HistData").data
ax = sns.scatterplot('Literacy', 'Lottery', data=df)
ax.yaxis.set_major_locator(MultipleLocator(10))
ax.xaxis.set_major_locator(MultipleLocator(10))
plt.show()
Put if you are working with one of the Seaborn processes that involve FacetGrid objects, you will see precious little help on how to modify the tick marks without manually setting them. You have dig out the Axes object from the numpy array inside FacetGrid.axes .
import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib.ticker import MultipleLocator
tips = sns.load_dataset("tips")
g = sns.lmplot(x="total_bill", y="tip", hue="smoker", data=tips, )
g.axes[0][0].yaxis.set_major_locator(MultipleLocator(3))
Note the double subscript required. g is a FacetGrid object, which holds a two-dimensional numpy array of dtype=object, whose entries are matplotlib AxesSubplot objects.
If you are working with a FacetGrid that has multiple axes, then each one will have to be extracted and modified.

With SciPy dendrogram, can I change the linewidth?

I'm making a big dendrogram using SciPy and in the resulting dendrogram the line thickness makes it hard to see detail. I want to decrease the line thickness to make it easier to see and more MatLab like. Any suggestions?
I'm doing:
import scipy.cluster.hierarchy as hicl
from pylab import savefig
distance = #distance matrix
links = hicl.linkage(distance,method='average')
pden = hicl.dendrogram(links,color_threshold=optcutoff[0], ...
count_sort=True,no_labels=True)
savefig('foo.pdf')
And getting a result like this.
Matplotlib has a context manager now, which allows you to only override the default values temporarily, for that one plot:
import matplotlib.pyplot as plt
from scipy.cluster import hierarchy
distance = #distance matrix
links = hierarchy.linkage(distance, method='average')
# Temporarily override the default line width:
with plt.rc_context({'lines.linewidth': 0.5}):
pden = hierarchy.dendrogram(links, color_threshold=optcutoff[0], ...
count_sort=True, no_labels=True)
# linewidth is back to its default here...!
plt.savefig('foo.pdf')
See the Matplotlib configuration API for more details.
Set the default linewidth before calling dendrogram. For example:
import scipy.cluster.hierarchy as hicl
from pylab import savefig
import matplotlib
# Override the default linewidth.
matplotlib.rcParams['lines.linewidth'] = 0.5
distance = #distance matrix
links = hicl.linkage(distance,method='average')
pden = hicl.dendrogram(links,color_threshold=optcutoff[0], ...
count_sort=True,no_labels=True)
savefig('foo.pdf')
See Customizing matplotlib for more information.
set dendrogram on existing axes than change its artists using setp. It allow changing all parameters, that won't work if dendrogram is sent to axes or won't work with dendrogram at all like linestyle.
import matplotlib.pyplot as plt
import scipy.cluster.hierarchy as hicl
links = #linkage
fig,ax = plt.subplots()
hicl.dendrogram(links,ax=ax)
plt.setp(ax.collections,linewidth=3,linestyle=":", ...other line parameters...)