Projection Problems when Displaying an Image on a Map with Cartopy - python

I have some satellite image data I would like to display using Cartopy. I have successfully followed the image example detailed here. Resulting in this code:
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
fig = plt.figure(figsize=(12, 12))
img_extent = (-77, -59, 9, 26)
ax = plt.axes(projection=ccrs.PlateCarree())
# image data coming from server, code not shown
ax.imshow(img, origin='upper', extent=img_extent)
ax.set_xmargin(0.05)
ax.set_ymargin(0.10)
# mark a known place to help us geo-locate ourselves
ax.plot(-117.1625, 32.715, 'bo', markersize=7)
ax.text(-117, 33, 'San Diego')
ax.coastlines()
ax.gridlines()
plt.show()
This code generates the following image
My problem is that the satellite image data is not in the PlateCarree projection, but the Mercator projection.
But when I get the axis object with
ax = plt.axes(projection=ccrs.Mercator())
I lose the coastlines.
I saw the issue reported here. But
ax.set_global()
results in this image:
The data is not present, and San Diego is in the wrong location. Also the lat/lon extents have changed. What am I doing wrong?
Post Discussion Update
The main problem is that I had not properly specified the image extents in the target projection with the transform_points method. I also had to be specific about the coordinate reference system in the imshow method as Phil suggests. Here is the correct code:
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
proj = ccrs.Mercator()
fig = plt.figure(figsize=(12, 12))
extents = proj.transform_points(ccrs.Geodetic(),
np.array([-77, -59]),
np.array([9, 26]))
img_extents = (extents[0][0], extents[1][0], extents[0][6], extents[1][7] )
ax = plt.axes(projection=proj)
# image data coming from server, code not shown
ax.imshow(img, origin='upper', extent=img_extents,transform=proj)
ax.set_xmargin(0.05)
ax.set_ymargin(0.10)
# mark a known place to help us geo-locate ourselves
ax.plot(-117.1625, 32.715, 'bo', markersize=7, transform=ccrs.Geodetic())
ax.text(-117, 33, 'San Diego', transform=ccrs.Geodetic())
ax.coastlines()
ax.gridlines()
plt.show()
Resulting in this correctly geoprojected satellite image:

Ideally, try to always be specific about the coordinate reference system your data are in when plotting with cartopy (via the transform keyword). This will mean you can just switch projections in your script and the data will automatically be put in the correct place.
So in your case, the plt.imshow should have a transform=ccrs.Mercator() keyword argument (you may need a a more specific parameterised Mercator instance). If your extents are in Geodetic (lats and lons) you will have to transform the bounding box into the mercator coordinates, but other than that, everything else should work as expected.
NOTE: I'm going to go and update the example to include the transform argument ;-) (PR: https://github.com/SciTools/cartopy/pull/343)
HTH

Related

Why is the Ocean filler feature on Cartopy not working for my map?

I am trying to make a plot of Europe with Cartopy and I want to fill in the ocean a different colour to the land. I believe I should be able to sue the line of code:
ax.add_feature(ctp.feature.OCEAN, facecolor=(0.5,0.5,0.5))
in order to do this, however when I do the map shown below is produced. This is clearly not what I would like to achieve. Is anyone able to help me and see what is wrong with my code?
I have put the code in below for it to be reproduced locally.
import cartopy as ctp
import cartopy.crs as ccrs
import cartopy.io.shapereader as shpreader
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 10))
ax = plt.axes(projection=ccrs.EuroPP())
ax.add_feature(ctp.feature.BORDERS, linestyle='-', alpha=1)
ax.coastlines(resolution='10m')
ax.add_feature(ctp.feature.OCEAN,facecolor=(0.5,0.5,0.5))
ax.coastlines()
ax.gridlines()
plt.show()
The projection ccrs.EuroPP() source is a subclass of UTM, which is a subclass of transversemercator. The complexities of subclassing may be the cause of the bad result. You can try my code that uses straight forward projection TransverseMercator(central_longitude=10) which should be nearly equivalent with EuroPP. The values in ax.set_extent() can be adjusted to get the areas you want on the map.
plt.figure(figsize=(10, 10))
# Start- relevant code
pp_euro = ccrs.TransverseMercator(central_longitude=10)
ax = plt.axes(projection=pp_euro)
ax.set_extent([-1800000, 2050000, 4000000, 8000000], crs=pp_euro)
# End- relevant code
ax.add_feature(ctp.feature.BORDERS, linestyle='-', alpha=1)
ax.coastlines(resolution='10m')
ax.add_feature(ctp.feature.OCEAN,facecolor=(0.5,0.5,0.5))
ax.coastlines()
ax.gridlines(draw_labels=True)
plt.show()

add a image with given coordination (lat, lon) on the cartopy map

I want to mark some location (lat,lon) on a cartopy map by a small image/icon. how can I do that ?
import cartopy.crs as crs
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(10, 5))
ax = plt.axes(projection=crs.PlateCarree())
ax.stock_img()
img = plt.imread('flag.png') #the image I want to add on the map
plt.show()
I found a source here: https://gist.github.com/secsilm/37db690ab9716f768d1a1e43d3f53e3f
But it doesn't work for me, the map show up without any flag. Is there any other way to do that ?
Thank a lot!.
The following worked just fine for me:
import matplotlib.pyplot as plt
import cartopy.crs as crs
from matplotlib.offsetbox import AnnotationBbox, OffsetImage
# Read image
lat = 39
lon = -95
img = plt.imread('/Users/rmay/Downloads/flag.png')
# Plot the map
fig = plt.figure(figsize=(10, 5))
ax = plt.axes(projection=crs.PlateCarree())
ax.coastlines()
ax.stock_img()
# Use `zoom` to control the size of the image
imagebox = OffsetImage(img, zoom=.1)
imagebox.image.axes = ax
ab = AnnotationBbox(imagebox, [lon, lat], pad=0, frameon=False)
ax.add_artist(ab)
You might want to try debugging by changing zoom to larger values or setting frameon to True. If you have further problem, be sure to post your values for lon/lat.
I tried the code in the gist you linked as it uses cartopy to georeference the image. I got the following result:
world map with china flag
Is this the effect you desired? The only thing I changed in the code copied from the gist is the picture. I used this one

Plotting gridded data with cartopy using NorthPolarStereo

I'm moving from Basemap to Cartopy and want to plot data for the Arctic Ocean that covers the pole.
I've decided to use the NorthPolarStereo() projection and am happy to use either pcolormesh or contourf. Unfortunately my field of data doesn't show up when I execute the following code:
import cartopy.crs as ccrso
from netCDF4 import Dataset
def import_envisat_field(year,
month):
data_dir = f'/media/robbie/Seagate Portable Drive/Envisat_thickness/{year}/'
file = f'ESACCI-SEAICE-L3C-SITHICK-RA2_ENVISAT-NH25KMEASE2-{year}{month}-fv2.0.nc'
data = Dataset(data_dir+file)
return(data)
# Import data
data = import_envisat_field("2003","02")
# Make plot
fig = plt.figure(figsize=[10, 5])
ax = plt.axes(projection=ccrs.NorthPolarStereo())
ax.add_feature(cartopy.feature.OCEAN, zorder=0)
ax.add_feature(cartopy.feature.LAND, zorder=1, edgecolor='black')
extent = 2500000
ax.set_extent((-extent,
extent,
-extent,
extent),
crs=ccrs.NorthPolarStereo())
ax.gridlines()
lon = np.array(data['lon'])
lat = np.array(data['lat'])
field = np.array(data['sea_ice_thickness'])[0]
print(lon.shape,lat.shape,field.shape)
# This print command gives (432, 432) (432, 432) (432, 432)
plt.pcolormesh(lon, lat, field,zorder=2,
transform=ccrs.NorthPolarStereo())
plt.show()
The data plots in a straightforward way using Basemap, but executing the above code just gives me a nice picture of the Arctic ocean but without my data on it.
I've also tried replacing plt.pcolormesh with ax.pcolormesh but that didn't work either.
Cartopy output:
Basemap output with the same data:
If your data coordinates are latitude and longitude you need to use the PlateCarree transform:
plt.pcolormesh(lon, lat, field,zorder=2, transform=ccrs.PlateCarree())
The transform describes the data coordinates and is independent from the projection you'd like to plot on. See this guide in the Cartopy documentation for more details https://scitools.org.uk/cartopy/docs/latest/tutorials/understanding_transform.html

Python Matplotlib Basemap - how to set zoom level

I have a dataframe with locations given as longitude and latitude coordinates (in degrees). Those locations are around New York. Therefore I setup a Basemap in Python that nicely shows all those locations. Works fine!
But: the map is drawn inline and it's very tiny. How can I force that figure to be let's say 3 times larger (zoom=3).
Here's the code. The data is from the Kaggle Two Sigma Rental Listing challenge.
%matplotlib inline
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
# New York Central Park
# Longitude: -73.968285
# Latitude: 40.785091
m = Basemap(projection='merc',llcrnrlat=40,urcrnrlat=42,\
llcrnrlon=-75, urcrnrlon=-72, resolution='i', area_thresh=50, lat_0=40.78, lon_0=-73.96)
m.drawmapboundary()
m.drawcoastlines(color='black', linewidth=0.4)
m.drawrivers(color='blue')
m.fillcontinents(color='lightgray')
lons = df['longitude'].values
lats = df['latitude'].values
x,y = m(lons, lats)
# r = red; o = circle marker (see: http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.plot)
m.plot(x, y, 'ro', markersize=4)
plt.show()
normally it would be as simple as:
plt.figure(figsize=(20,10))
How do you change the size of figures drawn with matplotlib?
but there are some other options too, see:
How to maximize a plt.show() window using Python
also to get the current size (for the purpose of "zoom")
How to get matplotlib figure size
regarding the specific issue:
the figure is inline inside a Jupyter notebook
before creating or plotting the map/figure:
import matplotlib
matplotlib.rcParams['figure.figsize'] = (30,30)

Overlay coastlines on a matplotlib plot

I'm looking to overlay some coastlines on graph representing an area. The area is defined by the box:
Top: 3900000
Bottom: 3450000
Left: 300000
Right: 800000
with the coordinate system WGS_1984_UTM_Zone_36N.
I've tried using mpl_toolkits.basemap however I can't work out how to specify that area as the ESPG code (32636) is not accepted by Basemap, and when I attempt to manually insert the projection parameters (m = Basemap(projection='tmerc', k_0=0.9996, lat_0=0, lon_0=33, llcrnrx=300000, llcrnry=3450000, urcrnrx=800000, urcrnry=3900000) it still requires a lat long boundary box.
Is there a another way to define that area in Basemap?
Thanks!
Edit: I'm trying to return an area of coastline defined by a box that is in the utm system, using lat/long values for the extremities of the box would result in over/underlap of the area covered by the coastlines when converted back into the utm system (I think, please correct me if I'm wrong).
Try cartopy and its new epsg feature:
projection = ccrs.epsg(32636)
fig, ax = plt.subplots(figsize=(5, 5),
subplot_kw=dict(projection=projection))
ax.coastlines(resolution='10m')
Here is a notebook with an example:
http://nbviewer.ipython.org/gist/ocefpaf/832cf7917c21da229564
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
# make sure the value of resolution is a lowercase L,
# for 'low', not a numeral 1
map = Basemap(projection='merc', lat_0=57, lon_0=-135,
resolution = 'h', area_thresh = 0.1,
llcrnrlon=-136.25, llcrnrlat=56,
urcrnrlon=-134.25, urcrnrlat=57.75)
map.drawcoastlines()
map.drawcountries()
map.fillcontinents(color='coral')
map.drawmapboundary()
map.drawmeridians(np.arange(0, 360, 30))
map.drawparallels(np.arange(-90, 90, 30))
plt.show()
All at this link https://peak5390.wordpress.com/2012/12/08/matplotlib-basemap-tutorial-making-a-simple-map/

Categories

Resources