I'm trying to overplot some satellite image data on top of a OSM tile.
I can plot them both separately but can't seem to overplot and I think it's down to the projection.
I load the data and get the projection information
ds = gdal.Open(fname)
data = ds.ReadAsArray()
gt = ds.GetGeoTransform()
proj = ds.GetProjection()
data=data.astype(np.float64)
projcs = inproj.GetAuthorityCode('PROJCS')
projection = ccrs.epsg(projcs)
The projection is
_EPSGProjection(32611)
I then set up the plot
subplot_kw = dict(projection=projection)
fig, ax = plt.subplots(figsize=(12, 6), subplot_kw=subplot_kw)
Then get the OSM tile, set up the axes and add it
imagery = OSM()
ax = plt.axes(projection=imagery.crs)
ax.add_image(imagery, 14)
Finally I set the extent of the imagery data and add it with imshow
extent = (gt[0], gt[0] + ds.RasterXSize * gt[1],
gt[3] + ds.RasterYSize * gt[5], gt[3])
img=ax.imshow(data, extent=extent, origin='upper', cmap='jet', vmin=1, vmax=1.3, alpha=0.1, transform=imagery.crs)
This doesn't display the imagery data at all, just the OSM tile.
I think the problem is with the transform keyword but I don't know how to fix it.
The value of the transform argument should be the coordinate system the data you are plotting are represented in. It is not related to the projection you want to visualise on. You are using the OSM coordinate system as the transform for your image data, this is wrong. You should be using the projection you defined for the image via the epsg code.
Related
I have a 3d point cloud. I used matplotlib to draw a scatterplot representing the point cloud viewed from above. The point cloud is stored as a list of coordinates in meters. The output of matplotlib.pyplot.scatter is a png image.
In addition to saving the image, I want to save the correspondence pixels <-> meters. How to do that?
Here the code I use to make my image with matplotlib. I use a dataframe to manipulate the point cloud.
colors = np.array((self.cloud["red"], self.cloud["green"], self.cloud["blue"])).T
dpi = 72
print("dpi: ",dpi)
fig = plt.figure(figsize=(18000/dpi, 18000/dpi), dpi=dpi)
ax = plt.axes(projection='3d')
ax.view_init(elev=90., azim = 0)
ax.set_snap(True)
ax.scatter(
self.cloud["x"],
self.cloud["y"],
self.cloud["z"],
marker=MarkerStyle('.', fillstyle = 'full'),
facecolors=colors / 255,
zdir="z",
#to set a point to 1 pixel we use the relation (dpi/fig.dpi) but
#the problem of the point cloud is the fact that we didn't have a point at each pixel so we increase the size of a point
#the size is empiric so need to be careful
s = (25)**2,
)
plt.axis('off')
self.set_proper_aspect_ratio(ax)
fig.tight_layout()
plt.savefig(file_name, orientation = 'portrait', transparent = True, dpi=fig.dpi)
To find this distance i use this code:
inv = ax.transData.inverted()
#center_image is in pixel
distance_x = abs(inv.transform((center_image[0],center_image[1]))[0])
distance_y = abs(inv.transform((center_image[0],center_image[1]))[1])
For a project I need to create a visualization that draws a circle around some locations on a map. The visualization used Cartopy v.0.18.0 to render the map. It uses the GoogleTiles class to fetch and display the tiles in the relevant region, and the add_patch(Patch.Circle(..., transform=ccrs.PlateCarree())) method to draw the circle.
tiles = GoogleTiles()
fig = plt.figure(figsize=(15,15))
ax = fig.add_subplot(1, 1, 1, projection=tiles.crs)
ax.set_extent((-121.8,-122.55,37.25,37.85))
ax.add_image(tiles, 11)
ax.add_patch(Patch.Circle(xy=[-122.4015173428571, 37.78774634285715], radius = 0.021709041989311614 + 0.005, alpha=0.3, zorder=30, transform=ccrs.PlateCarree()))
plt.show()
However, although I tried several transform objects I either got a ellipse instead of a circle (e.g. using ccrs.PlateCarree()) or no circle at all (e.g. using ccrs.Mercator()).
I found several different solutions online (e.g. Drawing Circles with cartopy in orthographic projection), however, these were not for the Mercator projection and I sadly lack the projection/transformation knowledge to adapt these to my problem.
The only way I was able to produce a circular patch, was when I set the projection parameter on fig.add_subplot to ccrs.PlateCarree(). This, however, distorts the map and the labels become blured, so this is sadly not an acceptable solution.
As the project is due soon, a speedy reply would be much appreciated.
Thanks #swatchai this was the missing hint, so for those intested the code looks like this right now, and it does work! Hooray!
tiles = GoogleTiles()
fig = plt.figure(figsize=(15,15))
ax = fig.add_subplot(1, 1, 1, projection=tiles.crs)
ax.set_extent((-121.8,-122.55,37.25,37.85))
ax.add_image(tiles, 11)
# The diameter is in degrees in EPSG:4326 coordinates therefore, the degrees have
# to be converted to km. At 37N the degree latitude is 11.0977 km.
ax.tissot(rad_km=(0.021709041989311614 + 0.005) * 11.0977, lons=[-122.4015], lats=[37.7877], alpha=0.3)
plt.show()
When executing the above code the following warning is thrown but it has visible effect on the result:
/opt/conda/lib/python3.8/site-packages/cartopy/mpl/geoaxes.py:761: UserWarning: Approximating coordinate system <cartopy._crs.Geodetic object at 0x7fa4c7529770> with the PlateCarree projection.
warnings.warn('Approximating coordinate system {!r} with the '
So thanks again #swatchai you saved my day!
I'm trying to overlay a Lambert Conformal Conical satellite image onto a Holoviews interactive map. I can map the satellite image just fine, but I can't figure out how to translate this map onto a Holoviews map properly. Below is reproducible code where I grab data using the Unidata Siphon library.
Import Packages
from datetime import datetime
import matplotlib.pyplot as plt
from netCDF4 import Dataset
from siphon.catalog import TDSCatalog
import holoviews as hv
import geoviews as gv
import geoviews.feature as gf
from cartopy import crs
from cartopy import feature as cf
hv.extension('bokeh')
Grab data and create figure
date=datetime.utcnow()
idx=-2
regionstr = 'CONUS'
channelnum = 13
datestr = str(date.year) + "%02d"%date.month + "%02d"%date.day
channelstr = 'Channel' + "%02d"%channelnum
cat = TDSCatalog('http://thredds-test.unidata.ucar.edu/thredds/catalog/satellite/goes16/GOES16/' + regionstr +
'/' + channelstr + '/' + datestr + '/catalog.xml')
ds = cat.datasets[idx].remote_access(service='OPENDAP')
x = ds.variables['x'][:]
y = ds.variables['y'][:]
z = ds.variables['Sectorized_CMI'][:]
proj_var = ds.variables[ds.variables['Sectorized_CMI'].grid_mapping]
# Create a Globe specifying a spherical earth with the correct radius
globe = ccrs.Globe(ellipse='sphere', semimajor_axis=proj_var.semi_major,
semiminor_axis=proj_var.semi_minor)
proj = ccrs.LambertConformal(central_longitude=proj_var.longitude_of_central_meridian,
central_latitude=proj_var.latitude_of_projection_origin,
standard_parallels=[proj_var.standard_parallel],
globe=globe)
fig = plt.figure(figsize=(14, 10))
ax = fig.add_subplot(1, 1, 1, projection=proj)
ax.coastlines(resolution='50m', color='lightblue')
ax.add_feature(cf.STATES, linestyle=':', edgecolor='lightblue')
ax.add_feature(cf.BORDERS, linewidth=1, edgecolor='lightblue')
for im in ax.images:
im.remove()
im = ax.imshow(z, extent=(x.min(), x.max(), y.min(), y.max()), origin='upper', cmap='jet')
plt.colorbar(im)
Now plot an interactive image using Holoviews (which uses Bokeh backend)
goes = hv.Dataset((x, y, z),['Lat', 'Lon'], 'ABI13')
%opts Image (cmap='jet') [width=1000 height=800 xaxis='bottom' yaxis='left' colorbar=True toolbar='above' projection=proj]
goes.to.image()* gf.coastline().options(projection=crs.LambertConformal(central_longitude=proj_var.longitude_of_central_meridian,central_latitude=proj_var.latitude_of_projection_origin,standard_parallels=[proj_var.standard_parallel],globe=globe))
I must not be translating it properly, though I've found documentation on Holoviews with regards to Lambert Conformal Conical projection to be sparse. I'm open to using any other interactive map package. My main desire is to be able to plot relatively quickly, get state/country lines on the image properly, and be able to zoom in. I've tried folium but also fell into projection issues there.
So I think the main thing to understand, which is explained here: is how projections are declared. The elements (e.g. Image, Points etc.) in GeoViews have a parameter called crs which declares the coordinate system the data is in, while the projection plot option declares what to project the data to for display.
In your case I think you want to display the image in the same coordinate system it is already in (Lambert Conformal), so technically you don't have to declare the coordinate system (crs) on the element at all and can just use a hv.Image (which is entirely unaware of projections).
As far as I can tell your code should already work as expected if you are using GeoViews 1.5, but here is what I would do:
# Apply mask
masked = np.ma.filled(z, np.NaN)
# Declare image from data
goes = hv.Image((x, y, masked),['Lat', 'Lon'], 'ABI13')
# Declare some options
options = dict(width=1000, height=800, yaxis='left', colorbar=True,
toolbar='above', cmap='jet', projection=proj)
# Display plot
gf.ocean * gf.land * goes.options(**options) * gf.coastline.options(show_bounds=False)
Note how we declare the projection on the Image but not crs. If you do want to display the data in a different projection it is defined in, you do have to declare the crs and use a gv.Image. In this case I'd recommend using project_image with the fast option enabled (which might introduce some artifacts but is much faster):
# Apply mask
masked = np.ma.filled(z, np.NaN)
# Declare the gv.Image with the crs
goes = gv.Image((x, y, masked), ['Lat', 'Lon'], 'ABI13', crs=proj)
# Now regrid the data and apply the reprojection
projected_goes = gv.operation.project_image(goes, fast=False, projection=ccrs.GOOGLE_MERCATOR)
# Declare some options
options = dict(width=1000, height=800, yaxis='left', colorbar=True,
toolbar='above', cmap='jet')
# Display plot
projected_goes.options(**options) * gv.tile_sources.ESRI.options(show_bounds=False)
Another final tip, when you plot with bokeh all the data you are plotting will be sent to the browser, so when dealing with images any larger than you are already using I'd recommend using the holoviews' regrid operation which uses datashader to dynamically resize the image as you are zooming. To use that simply apply the operation to the image like so:
from holoviews.operation.datashader import regrid
regridded = regrid(goes)
This issue has been plaguing me for some time. I'm trying to handle some large amount of data that is in the form of a .fits file (on the order of 11000x9000 pixels). What I need to do is create a 'zoomed in' RA/Dec coordinate plot (ideally using astropy.wcs) for many objects on the sky with contours from one fits file, and greyscale (or heatmap from another).
My problem is that whenever I slice the data from the image (to my region of interest) I lose the association with the sky coordinates. This means that the sliced image isn't in the correct location.
I've adapted an example from the astropy docs to save you the pain of my data. (Note: I want the contours to cover more area than the image, whatever the solution is for this should work on both data)
Here is the code I am having trouble with:
from matplotlib import pyplot as plt
from astropy.io import fits
from astropy.wcs import WCS
from astropy.utils.data import download_file
import numpy as np
fits_file = 'http://data.astropy.org/tutorials/FITS-images/HorseHead.fits'
image_file = download_file(fits_file, cache=True)
hdu = fits.open(image_file)[0]
wmap = WCS(hdu.header)
data = hdu.data
fig = plt.figure()
ax1 = fig.add_subplot(121, projection=wmap)
ax2 = fig.add_subplot(122, projection=wmap)
# Scale input image
bottom, top = 0., 12000.
data = (((top - bottom) * (data - data.min())) / (data.max() - data.min())) + bottom
'''First plot'''
ax1.imshow(data, origin='lower', cmap='gist_heat_r')
# Now plot contours
xcont = np.arange(np.size(data, axis=1))
ycont = np.arange(np.size(data, axis=0))
colors = ['forestgreen','green', 'limegreen']
levels = [2000., 7000., 11800.]
ax1.contour(xcont, ycont, data, colors=colors, levels=levels, linewidths=0.5, smooth=16)
ax1.set_xlabel('RA')
ax1.set_ylabel('Dec')
ax1.set_title('Full image')
''' Second plot '''
datacut = data[250:650, 250:650]
ax2.imshow(datacut, origin='lower', cmap=cmap)
ax2.contour(xcont, ycont, data, colors=colors, levels=levels, linewidths=0.5, smooth=16)
ax2.set_xlabel('RA')
ax2.set_ylabel('')
ax2.set_title('Sliced image')
plt.show()
I tried using the WCS coords of my sliced chunk to fix this, but I'm not sure if I can pass it in anywhere!
pixcoords = wcs.wcs_pix2world(zip(*[range(250,650),range(250,650)]),1)
The good news is: You can simply slice your astropy.WCS as well which makes your task relativly trivial:
...
wmapcut = wmap[250:650, 250:650] # sliced here
datacut = data[250:650, 250:650]
ax2 = fig.add_subplot(122, projection=wmapcut) # use sliced wcs as projection
ax2.imshow(datacut, origin='lower', cmap='gist_heat_r')
# contour has to be sliced as well
ax2.contour(np.arange(datacut.shape[0]), np.arange(datacut.shape[1]), datacut,
colors=colors, levels=levels, linewidths=0.5, smooth=16)
...
If your files have different WCS you might need to do some reprojection (see for example reproject)
Esteemed experts, am back with a problem I presented about two months ago, I have been working on it since with no success. This concerns superposition of contours on a basemap. I have looked at numerous examples on this, e.g. the example here: http://nbviewer.ipython.org/github/Unidata/tds-python-workshop/blob/master/matplotlib.ipynb
A sample of the data is on one of my previous posts, here: Contours with map overlay on irregular grid in python.
After preparing the data, here are plotting methods:
# Setting the plot size and text
fig = plt.figure(figsize=(10,8))
lev = [15, 20, 25, 30, 35, 40,45]
norm1 = colors.BoundaryNorm(lev, 256)
# Draw filled contours
# 1. pcolor does not show the filled contours
#cs = plt.pcolor(x,y,zi, cmap = cm.jet, norm = norm1)
# 2. pcolormesh does not show the filled contours
#cs = plt.pcolormesh(x,y,zi, shading = "flat", cmap=cmap)
# 3. contourf does not show the filled contours
#cs = plt.contourf(xi, yi, zi) #, levels=np.linspace(zi.min(),zi.max(),5))
cs = plt.contourf(xi, yi, zi, cmap = cm.jet, levels = lev, norm = norm1)
# 4. Draw line contours with contour()
#cs = m.contour(x,y,zi,linewidths=1.2) # This works
plt.scatter(data.Lon, data.Lat, c=data.Z, s=100,
vmin=zi.min(), vmax=zi.max()) # Does not work at all
# Color bar
#cbar = m.colorbar(fig,location='right',pad="10%")
fig.colorbar(cs)
# Plot a title
plt.figtext(.5,.05,'Figure 1. Mean Rainfall Onset Dates',fontsize=12,ha='center')
plt.show()
Sorry I am not able to post the plot examples, but:
pcolor, pcolormesh and contourf above all give a map without any filled contours but with a colorbar
the above plots without the map object give filled contours including scatter plot (without map background)
contour gives the map with contour lines superposed:
I am baffled because this is an example copy-pasted from the example in the link quoted above.
Any hint as to a possible cause of the problem would be appreciated
Zilore Mumba
you need to use the basemap to plot the contours vs using matplotlib.pyplot. see my example for some of my code.
#Set basemap and grid
px,py=n.meshgrid(x,y)
m=Basemap(projection='merc',llcrnrlat=20,urcrnrlat=55,
llcrnrlon=230,urcrnrlon=305,resolution='l')
X,Y=m(px,py)
#Draw Latitude Lines
#labels[left,right,top,bottom] 1=True 0=False
parallels = n.arange(0.,90,10.)
m.drawparallels(parallels,labels=[1,0,0,0],fontsize=10,linewidth=0.)
# Draw Longitude Lines
#labels[left,right,top,bottom] 1=True 0=False
meridians = n.arange(180.,360.,10.)
m.drawmeridians(meridians,labels=[0,0,0,1],fontsize=10,linewidth=0)
#Draw Map
m.drawcoastlines()
m.drawcountries()
m.drawstates()
m.fillcontinents(color='grey',alpha=0.1,lake_color='aqua')
#Plot Contour lines and fill
levels=[5.0,5.1,5.2,5.3,5.4,5.6,5.7,5.8,5.9,6.0]
cs=m.contourf(px,py,thickness,levels,cmap=p.cm.RdBu,latlon=True,extend='both')
cs2=m.contour(px,py,thickness,levels,latlon=True,colors='k')
#Plot Streamlines
m.streamplot(px,py,U,V,latlon=True,color='k')
#Add Colorbar
cbar = p.colorbar(cs)
cbar.add_lines(cs2)
cbar.ax.set_ylabel('1000 hPa - 500 hPa Thickness (km)')
#Title
p.title('Geostrophic Winds with Geopotential Thickness')
p.show()
Without knowing how your data look like it's a bit difficult to answer your question, but I'll try anyway. You might want to grid your data, for example, with an histogram, then contour the results.
For example, if you're interested in plotting 2D contours of points that have coordinates (x,y) and a third property (z) you want to use for the colors, you might give this a try
from numpy import *
H=histogram2d(x,y,weights=z)
contourf(H[0].T,origin='lower')
But, like I said, it's hard to understand what you're looking for if you're not giving details about your data. Have a look at the matplotlib guide for more examples http://matplotlib.org/examples/pylab_examples/contourf_demo.html