Resampling and re-projecting weather satellite image - python

The current question is somewhat similar to a resampling question on:
Resample question on Stackoverflow
However, my specific problem is that I only have a partial satellite image and not the full image. As a result, I'm not sure how to proceed. Here's what I've tried so far:
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
def transform_extent_pts(extent_pts, map_proj, pt_crs):
xul, yul = map_proj.transform_point(
x = extent_pts[0],
y = extent_pts[3],
src_crs = pt_crs)
xlr, ylr = map_proj.transform_point(
x = extent_pts[1],
y = extent_pts[2],
src_crs = pt_crs)
return [xul, xlr, ylr, yul]
sat_image1 = ROOT
df = plt.imread(sat_image1)
# re-project to Mercator
map_proj = ccrs.Mercator()
# Image extent in Geostationary coordinates:
data_crs = ccrs.Geostationary(central_longitude=0.0)
ax2 = plt.axes(projection=data_crs)
img_extent_sat = ax2.get_extent(crs=data_crs)
img_extent_sat = [1.03*x for x in img_extent_sat]
#img_extent_sat=[-32.150361957, 30.150361957, 7.150361956, 42.150361956]
# Convert to Mercator
img_extent_merc = transform_extent_pts(img_extent_sat, map_proj, ccrs.Geodetic())
plt.close()
fig = plt.figure(figsize=(10,10))
ax = plt.axes(projection=map_proj)
ax.coastlines(color='blue')
ax.gridlines(color='black', alpha=0.5, linestyle='--', linewidth=0.75, draw_labels=True)
# Map extent in degrees (PlateCarree) coordinates:
map_extent_deg = (50., -20., -40., 40.) # African continent
map_extent_deg = (-31, 38.009232, 2.880476, 42)
# Convert to Mercator
map_extent_merc = transform_extent_pts(map_extent_deg, map_proj, ccrs.Geodetic())
ax.set_extent(map_extent_merc, map_proj)
plt.imshow(df, origin='upper', transform=data_crs, extent=img_extent_sat)
The main issue I'm facing is that the projection of my data seems to be incorrect, and there might also be a small misalignment. I'm now wondering how I can change the projection of my data. Could you provide some guidance on how to accomplish this?
I've tried a few things, but unfortunately the image is only available in PNG format. The "transform" function, such as using "geostationary," doesn't seem to work with this format. Are there any other options available? Can the projection be changed afterwards, or is that not possible? I assume that the exact coordinates can be obtained by shifting the image.
Perhaps the two red dots can be helpful. I know their coordinates, and they should align with the two white squares:
plt.plot(sta_lon, sta_lat, marker='o', color='red', markersize=8,
alpha=0.7, transform=ccrs.Geodetic())
plt.text(sta_lon, sta_lat+0.25, sta_name, ha='center', fontsize=18,
color='red', transform=ccrs.Geodetic())

Related

How do I get the transformed data of a cartopy geodetic plot?

how do I get all the data of the transformed line of the "handle" - Line2D object in the following code:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
ax = plt.axes(projection=ccrs.PlateCarree())
ax.stock_img()
ny_lon, ny_lat = -75, 43
delhi_lon, delhi_lat = 77.23, 28.61
handle = plt.plot([ny_lon, delhi_lon], [ny_lat, delhi_lat],
color='blue', linewidth=2, marker='o',
transform=ccrs.Geodetic(),
)
plt.show()
To be more clear:
I'm not looking for the output of "handle[0].get_data()", since this just prints my original longitude and latitude, but im looking for the the data of the geodetic line drawn on the map.
I found the answer!
According to this question, you can access the data of the transformation via the following code snippet:
[handle] = plt.plot([ny_lon, delhi_lon], [ny_lat, delhi_lat], color='blue', linewidth=2, marker='o', transform=ccrs.Geodetic())
t_path = handle._get_transformed_path()
path_in_data_coords, _ = t_path.get_transformed_path_and_affine()
print(path_in_data_coords.vertices)
In the answer to this question there is also a second approach.
Let me do some computation and plot checks on the code provided by the OP.
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
ax = plt.axes(projection=ccrs.PlateCarree())
ax.stock_img()
ny_lon, ny_lat = -75, 43
delhi_lon, delhi_lat = 77.23, 28.61
# Plot geodetic path in thick 'blue' line
handle = plt.plot([ny_lon, delhi_lon], [ny_lat, delhi_lat],
color='blue', linewidth=10, marker='o',
transform=ccrs.Geodetic(),
)
# Get the geodetic path's coordinates to plot on top in 'red'
t_path = handle[0]._get_transformed_path()
path_in_data_coords, _ = t_path.get_transformed_path_and_affine()
ax.plot(path_in_data_coords.vertices[:,0],
path_in_data_coords.vertices[:,1],
color='red', lw=2)
plt.show()
And, the output plot is:
Congratulations to the OP.
(Extension part 1)
Now, let us compute the length of the geodesic path using the coordinates obtained above. My proposed code is:
# (*** Continued from the code above ***)
import cartopy.geodesic as geodesic
import numpy as np
# defining the earth shape on which to make calculations
myGeod = geodesic.Geodesic(6378137.0, 1/298.257223563)
# get (lat,long) lists from (long,lat) of the geodesic path
latlonlists = []
[latlonlists.append([lat,lon]) for lon,lat in zip(path_in_data_coords.vertices[:,0], path_in_data_coords.vertices[:,1])]
#print(latlonlists)
# compute length of the geodesic
geodesic_in_meters = myGeod.geometry_length(np.array(latlonlists))
print(geodesic_in_meters) # output: 17554975.077432975

Plotting negative values using matplotlib scatter

I want to plot scatter points corresponding to 6 different datasets over global maps of the Earth. The problem is that some of these quantities have negative values and they don't appear in the maps. I have tried to overcome this problem by taking absolute values of the data and multiplying (or taking the power of) them by some factors, but nothing seems to work the way I want. The problem is that the datasets have very different ranges. Ideally, I want them all to have the same scale so everything will be more organized, but I don't know how to do this.
I created some synthetic data to illustrate this issue
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
from mpl_toolkits.basemap import Basemap, addcyclic, shiftgrid
from matplotlib.pyplot import cm
np.random.seed(100)
VarReTx = np.random.uniform(low=-0.087, high=0.0798, size=(52,))
VarReTy = np.random.uniform(low=-0.076, high=0.1919, size=(52,))
VarImTx = np.random.uniform(low=-0.0331, high=0.0527, size=(52,))
VarImTy = np.random.uniform(low=-0.0311, high=0.2007, size=(52,))
eTx = np.random.uniform(low=0.0019, high=0.0612, size=(52,))
eTx = np.random.uniform(low=0.0031, high=0.0258, size=(52,))
obslat = np.array([18.62, -65.25, -13.8, -7.95, -23.77, 51.84, 40.14, 58.07,
-12.1875, -35.32, 36.37, -46.43, 40.957, -43.474, 38.2 , 37.09,
48.17, 0.6946, 13.59, 28.32, 51., -25.88, -34.43, 21.32,
-12.05, 52.27, 36.23, -12.69, 31.42, 5.21, -22.22, 36.1,
14.38, -54.5, 43.91, 61.16, 48.27, 52.07, 54.85, 45.403,
52.971, -17.57, -51.7, 18.11, 39.55, 47.595, 22.79, -37.067,
-1.2, 32.18, 51.933, 48.52])
obslong = np.array([-287.13, -64.25, -171.78, -14.38, -226.12, -339.21, -105.24,
-321.77, -263.1664, -210.64, -233.146, -308.13, -359.667, -187.607,
-77.37, -119.72, -348.72, -287.8463, -215.13, -16.43, -4.48,
-332.29, -340.77, -158., -75.33, -255.55, -219.82, -227.53,
-229.12, -52.73, -245.9, -256.16, -16.97, -201.05, -215.81,
-45.442, -117.12, -347.32, -276.77, -75.552, -201.752, -149.58,
-57.89, -66.15, -4.35, -52.677, -354.47, -12.315, -48.5,
-110.73, -10.25, -123.42, ])
fig, ([ax1, ax2], [ax3, ax4], [eax1, eax2]) = plt.subplots(3,2, figsize=(24,23))
matplotlib.rc('xtick', labelsize=12)
matplotlib.rc('ytick', labelsize=12)
plots = [ax1, ax2, ax3, ax4, eax1, eax2]
Vars = [VarReTx, VarReTy, VarImTx, VarImTy, eTx, eTy]
titles = [r'$\Delta$ ReTx', r'$\Delta$ ReTy', r'$\Delta$ ImTx', r'$\Delta$ ImTy', 'Error (X)', 'Error (Y)']
colors = iter(cm.jet(np.reshape(np.linspace(0.0, 1.0, len(plots)), ((len(plots), 1)))))
for j in range(len(plots)):
c3 = next(colors)
lat = np.arange(-91, 91, 0.5)
long = np.arange(-0.1, 360.1, 0.5)
longrid, latgrid = np.meshgrid(long, lat)
plots[j].set_title(titles[j], fontsize=48, y=1.05)
condmap = Basemap(projection='robin', llcrnrlat=-90, urcrnrlat=90,\
llcrnrlon=-180, urcrnrlon=180, resolution='c', lon_0=0, ax=plots[j])
maplong, maplat = condmap(longrid, latgrid)
condmap.drawcoastlines()
condmap.drawmapboundary(fill_color='white')
parallels = np.arange(-90, 90, 15)
condmap.drawparallels(parallels,labels=[False,True,True,False], fontsize=15)
x,y = condmap(obslong, obslat)
w = []
for m in range(obslong.size):
w.append(Vars[j][m])
w = np.array(w)
condmap.scatter(x, y, s = w*1e+4, c=c3)
r = np.linspace(np.min(Vars[j]), np.max(Vars[j]), 4)
for n in r:
condmap.scatter([], [], c=c3, s=n*1e+4, label=str(np.round(n, 4)))
plots[j].legend(bbox_to_anchor=(0., -0.2, 1., .102), loc='lower left',
ncol=4, mode="expand", borderaxespad=0., fontsize=16, frameon = False)
plt.show()
plt.close('all')
As you can see in the map, negative data does not are not being exhibited. I want they all to appear in the maps and that all the scatter plots have the same scale in their respective ranges. Thanks!
It looks like you are trying to map your dataset to dot size. Obviously you cannot have negative size dots, so that won't work.
Instead, you need to normalize your dataset to a strictly positive range and use those normalized values for the size parameter. A simple way to do this would be to use matplotlib.colors.Normalize(vmin, vmax), which allows you to map any values in the interval [vmin, vmax] to the interval [0,1].
If you want to have a shared scale for all your datasets, first find the global min and max, and use that to instantiate your normalization, then normalize each dataset when plotting:
datasets = [VarReTx,VarReTy,VarImTx,VarImTy,eTx,eTx]
min_val = min([d.min() for d in datasets])
max_val = max([d.max() for d in datasets])
norm = matplotlib.colors.Normalize(vmin=min_val, vmax=max_val)
plt.scatter(x,y,s=norm(VarReTx)*100) # choose appropiate scaling factor instead of 100 to get nicely sized dots

How to create a basic legend to a multicolored line?

I am currently finishing a bigger project and the last part is to add a simple legend to a plot of a multicolored line. The line only contains two different colors.
The following image shows the plot when created.
The next image shows the same plot with higher resolution.
The plot displays the distance between Earth and Mars over time. For the months March to August the line is orange, for the other months it's blue. The legend should come in a simple box in the upper right corner of the plot showing a label each for the used colors. Something like this would be nice.
The data for the plot comes from a huge matrix I named master_array. It contains a lot more information that is necessary for some tasks prior to show the plot this question is regarding to.
Important for the plot I am struggling with are the columns 0, 1 and 6 which are containing the date, distance between the planets at related date and in column 6 I set a flag to determine whether the given point belongs to the 'March to August' set or not (0 is for Sep-Feb / "winter", 1 is for Mar-Aug / "summer"). The master_array is a numpy array, dtype is float64. It contains approximately 45k data points.
It looks like:
In [3]: master_array
Out[3]:
array([[ 1.89301010e+07, 1.23451036e+00, -8.10000000e+00, ...,
1.00000000e+00, 1.00000000e+00, 1.89300000e+03],
[ 1.89301020e+07, 1.24314818e+00, -8.50000000e+00, ...,
2.00000000e+00, 1.00000000e+00, 1.89300000e+03],
[ 1.89301030e+07, 1.25179997e+00, -9.70000000e+00, ...,
3.00000000e+00, 1.00000000e+00, 1.89300000e+03],
...,
[ 2.01903100e+07, 1.84236878e+00, 7.90000000e+00, ...,
1.00000000e+01, 3.00000000e+00, 2.01900000e+03],
[ 2.01903110e+07, 1.85066892e+00, 5.50000000e+00, ...,
1.10000000e+01, 3.00000000e+00, 2.01900000e+03],
[ 2.01903120e+07, 1.85894904e+00, 9.40000000e+00, ...,
1.20000000e+01, 3.00000000e+00, 2.01900000e+03]])
This is the function to get the plot I described in the beginning:
def md_plot3(dt64=np.array, md=np.array, swFilter=np.array):
""" noch nicht fertig """
y, m, d = dt64.astype(int) // np.c_[[10000, 100, 1]] % np.c_[[10000, 100, 100]]
dt64 = y.astype('U4').astype('M8') + (m-1).astype('m8[M]') + (d-1).astype('m8[D]')
cmap = ListedColormap(['b','darkorange'])
plt.figure('zeitlich-global betrachtet')
plt.title("Marsdistanz unter Berücksichtigung der Halbjahre der steigenden und sinkenden Temperaturen",
loc='left', wrap=True)
plt.xlabel("Zeit in Jahren\n")
plt.xticks(rotation = 45)
plt.ylabel("Marsdistanz in AE\n(1 AE = 149.597.870,7 km)")
# plt.legend(loc='upper right', frameon=True) # worked formerly
ax=plt.gca()
plt.style.use('seaborn-whitegrid')
#convert dates to numbers first
inxval = mdates.date2num(dt64)
points = np.array([inxval, md]).T.reshape(-1,1,2)
segments = np.concatenate([points[:-1],points[1:]], axis=1)
lc = LineCollection(segments, cmap=cmap, linewidth=3)
# set color to s/w values
lc.set_array(swFilter)
ax.add_collection(lc)
loc = mdates.AutoDateLocator()
ax.xaxis.set_major_locator(loc)
ax.xaxis.set_major_formatter(mdates.AutoDateFormatter(loc))
ax.autoscale_view()
In the bigger script there is also another function (scatter plot) to mark the minima and maxima of the curve, but I guess this is not so important here.
I already tried this resulting in a legend, that shows a vertical colorbar and only one label and also both options described in the answers to this question because it looks more like what I am aiming for but couldn't make it work for my case.
Maybe I should add that I am only a beginner in python, this is my first project so I am not familiar with the deeper functionality of matplotlib what is probably the reason why I am not able to customize the mentioned answers to get it to work in my case.
UPDATE
Thanks to the help of the user ImportanceOfBeingErnest I made some improvements:
import matplotlib.dates as mdates
from matplotlib.collections import LineCollection
from matplotlib.colors import ListedColormap
from matplotlib.lines import Line2D
def md_plot4(dt64=np.array, md=np.array, swFilter=np.array):
y, m, d = dt64.astype(int) // np.c_[[10000, 100, 1]] % np.c_[[10000, 100, 100]]
dt64 = y.astype('U4').astype('M8') + (m-1).astype('m8[M]') + (d-1).astype('m8[D]')
z = np.unique(swFilter)
cmap = ListedColormap(['b','darkorange'])
fig = plt.figure('Test')
plt.title("Test", loc='left', wrap=True)
plt.xlabel("Zeit in Jahren\n")
plt.xticks(rotation = 45)
plt.ylabel("Marsdistanz in AE\n(1 AE = 149.597.870,7 km)")
# plt.legend(loc='upper right', frameon=True) # worked formerly
ax=plt.gca()
plt.style.use('seaborn-whitegrid')
#plt.style.use('classic')
#convert dates to numbers first
inxval = mdates.date2num(dt64)
points = np.array([inxval, md]).T.reshape(-1,1,2)
segments = np.concatenate([points[:-1],points[1:]], axis=1)
lc = LineCollection(segments, array=z, cmap=plt.cm.get_cmap(cmap),
linewidth=3)
# set color to s/w values
lc.set_array(swFilter)
ax.add_collection(lc)
fig.colorbar(lc)
loc = mdates.AutoDateLocator()
ax.xaxis.set_major_locator(loc)
ax.xaxis.set_major_formatter(mdates.AutoDateFormatter(loc))
ax.autoscale_view()
def make_proxy(zvalue, scalar_mappable, **kwargs):
color = scalar_mappable.cmap(scalar_mappable.norm(zvalue))
return Line2D([0, 1], [0, 1], color=color, **kwargs)
proxies = [make_proxy(item, lc, linewidth=2) for item in z]
ax.legend(proxies, ['Winter', 'Summer'])
plt.show()
md_plot4(dt64, md, swFilter)
+What is good about it:
Well it shows a legend and it shows the right colors according to the labels.
-What is still to optimize:
1) The legend is not in a box and the 'lines' of the legend are interfering with the bottom layers of the plot. As the user ImportanceOfBeingErnest stated out this is caused by using plt.style.use('seaborn-whitegrid'). So if there's a way to use plt.style.use('seaborn-whitegrid') together with the legend style of plt.style.use('classic') that might would help.
2) The bigger issue is the colorbar. I added the fig.colorbar(lc) line to the original code to achieve what I was looking for according to this answer.
So I tried some other changes:
I used the plt.style.use('classic') to get a legend in the way I need it but this costs me the nice style of plt.style.use('seaborn-whitegrid') as mentioned before. Moreover I disabled the colorbar line I added prior according to the mentioned answer.
This is what I got:
import matplotlib.dates as mdates
from matplotlib.collections import LineCollection
from matplotlib.colors import ListedColormap
from matplotlib.lines import Line2D
def md_plot4(dt64=np.array, md=np.array, swFilter=np.array):
y, m, d = dt64.astype(int) // np.c_[[10000, 100, 1]] % np.c_[[10000, 100, 100]]
dt64 = y.astype('U4').astype('M8') + (m-1).astype('m8[M]') + (d-1).astype('m8[D]')
z = np.unique(swFilter)
cmap = ListedColormap(['b','darkorange'])
#fig =
plt.figure('Test')
plt.title("Test", loc='left', wrap=True)
plt.xlabel("Zeit in Jahren\n")
plt.xticks(rotation = 45)
plt.ylabel("Marsdistanz in AE\n(1 AE = 149.597.870,7 km)")
# plt.legend(loc='upper right', frameon=True) # worked formerly
ax=plt.gca()
#plt.style.use('seaborn-whitegrid')
plt.style.use('classic')
#convert dates to numbers first
inxval = mdates.date2num(dt64)
points = np.array([inxval, md]).T.reshape(-1,1,2)
segments = np.concatenate([points[:-1],points[1:]], axis=1)
lc = LineCollection(segments, array=z, cmap=plt.cm.get_cmap(cmap),
linewidth=3)
# set color to s/w values
lc.set_array(swFilter)
ax.add_collection(lc)
#fig.colorbar(lc)
loc = mdates.AutoDateLocator()
ax.xaxis.set_major_locator(loc)
ax.xaxis.set_major_formatter(mdates.AutoDateFormatter(loc))
ax.autoscale_view()
def make_proxy(zvalue, scalar_mappable, **kwargs):
color = scalar_mappable.cmap(scalar_mappable.norm(zvalue))
return Line2D([0, 1], [0, 1], color=color, **kwargs)
proxies = [make_proxy(item, lc, linewidth=2) for item in z]
ax.legend(proxies, ['Winter', 'Summer'])
plt.show()
md_plot4(dt64, md, swFilter)
+What is good about it:
It shows the legend in the way I need it.
It doesn't show a colorbar anymore.
-What is to optimize:
The plot isn't multicolored anymore.
Neither is the legend.
The classic style is not what I was looking for as I explained before...
So if anyone has a good advice please let me know!
I am using numpy version 1.16.2 and matplotlib version 3.0.3
To get a multicoloured plot in matplotlib, label your plots and then call the legend() function. The following sample code is taken from a link, but as links break, here's the post..
The chart used here is a line, but the same principle applies to other chart types, as you can see from this other SO answer
import matplotlib.pyplot as plt
import numpy as np
y = [2,4,6,8,10,12,14,16,18,20]
y2 = [10,11,12,13,14,15,16,17,18,19]
x = np.arange(10)
fig = plt.figure()
ax = plt.subplot(111)
ax.plot(x, y, label='$y = numbers')
ax.plot(x, y2, label='$y2 = other numbers')
plt.title('Legend inside')
ax.legend()
plt.show()
This code will show the following image (with the legend inside the chart)
Hope this helps
So here is the answer how to create a basic legend to a multicolored line, containing multiple labels for each used color and without showing a colorbar next to the plot (standard colorbar, nothing inside the legend; see update of original question for more information about the issues):
Thanks to a lot of helpful comments I figured out to add a norm to the LineCollection() to avoid ending up with a monocolored line when removing the colorbar by disabling fig.colorbar() (also see this)
The additional argument (in this case "norm") to add was norm=plt.Normalize(z.min(), z.max()), where z is the array that contains the information responsible for the different colors of the segments. Note that z only needs to hold one single element for each different color. This is why I wrapped my swFilter array, consisting of one flag per data point, into np.unique().
To get a proper legend inside a box not touching the plt.style.use(), I simply had to add the right arguments to ax.legend(). In my case a simple frameon=True did the job.
The result is the following:
Here is the code:
import matplotlib.dates as mdates
from matplotlib.collections import LineCollection
from matplotlib.colors import ListedColormap
from matplotlib.lines import Line2D
def md_plot4(dt64=np.array, md=np.array, swFilter=np.array):
y, m, d = dt64.astype(int) // np.c_[[10000, 100, 1]] % np.c_[[10000, 100, 100]]
dt64 = y.astype('U4').astype('M8') + (m-1).astype('m8[M]') + (d-1).astype('m8[D]')
z = np.unique(swFilter)
cmap = ListedColormap(['b','darkorange'])
#fig =
plt.figure('Test')
plt.title("Marsdistanz unter Berücksichtigung der Halbjahre der steigenden und sinkenden Temperaturen\n",
loc='left', wrap=True)
plt.xlabel("Zeit in Jahren\n")
plt.xticks(rotation = 45)
plt.ylabel("Marsdistanz in AE\n(1 AE = 149.597.870,7 km)")
plt.tight_layout()
ax=plt.gca()
plt.style.use('seaborn-whitegrid')
#convert dates to numbers first
inxval = mdates.date2num(dt64)
points = np.array([inxval, md]).T.reshape(-1,1,2)
segments = np.concatenate([points[:-1],points[1:]], axis=1)
lc = LineCollection(segments, array=z, cmap=plt.cm.get_cmap(cmap),
linewidth=3, norm=plt.Normalize(z.min(), z.max()))
# set color to s/w values
lc.set_array(swFilter)
ax.add_collection(lc)
loc = mdates.AutoDateLocator()
ax.xaxis.set_major_locator(loc)
ax.xaxis.set_major_formatter(mdates.AutoDateFormatter(loc))
ax.autoscale_view()
def make_proxy(zvalue, scalar_mappable, **kwargs):
color = scalar_mappable.cmap(scalar_mappable.norm(zvalue))
return Line2D([0, 1], [0, 1], color=color, **kwargs)
proxies = [make_proxy(item, lc, linewidth=2) for item in z]
ax.legend(proxies, ['Halbjahr der sinkenden \nTemperaturen',
'Halbjahr der steigenden \nTemperaturen'], frameon=True)
plt.show()
md_plot4(dt64, md, swFilter)
Note that I added plt.tight_layout() to ensure the title of the plot and the description of the axes are shown without any cut-offs in the window mode.
New issue now (resulting from adding tight_layout()) is that the plot gets horizontal compressed, even though there is much space available on the right side of the plot (the place where a colorbar would appear when called).
This requires another fix but currently I don't know how. So if anyone knows how to prevent the plots title and description of the axes from getting cut-off in window mode, I would be very grateful if you leave a comment.

how to plot geotiff data in specific area (lat/lon) with python

I have a geotiff raster data sets with elevation data init and i want to plot it in specific area, such as 60°E - 70° E ,70°S - 80°E.
I have a bit of code from here,but the pcolormesh seem couldn't plot my geotif.it's all red. picture. The picture is shown by imshow as really picture
When I try to make a plot with this code below:
path = "F:\\Mosaic_h1112v28_ps.tif"
dataset = gdal.Open(path)
data = dataset.ReadAsArray()
x0, dx, dxdy, y0, dydx, dy = dataset.GetGeoTransform()
nrows, ncols = data.shape
londata = np.linspace(x0, x0+dx*ncols)
latdata = np.linspace(y0, y0+dy*nrows)
lons, lats = np.meshgrid(lonarray, latarray)
fig = plt.figure(figsize=(8, 8))
m = Basemap(projection='lcc', lon_0=67.5, lat_0=-68.5, height=950000,
width=580000, resolution='h')
m.drawcoastlines()
x, y = m(lons, lats)
Then i dont know how to continue it . I just want to use imshow, but the imshow dont specify area(lat/lon).
I will really appreciate your help.
It's a good question, here is my solution.
Required packages: georaster with its dependencies (gdal, etc).
Data for demo purposes downloadable from http://dwtkns.com/srtm/
import georaster
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
fig = plt.figure(figsize=(8,8))
# full path to the geotiff file
fpath = r"C:\\path_to_your\geotiff_file\srtm_57_10.tif" # Thailand east
# read extent of image without loading
# good for values in degrees lat/long
# geotiff may use other coordinates and projection
my_image = georaster.SingleBandRaster(fpath, load_data=False)
# grab limits of image's extent
minx, maxx, miny, maxy = my_image.extent
# set Basemap with slightly larger extents
# set resolution at intermediate level "i"
m = Basemap( projection='cyl', \
llcrnrlon=minx-2, \
llcrnrlat=miny-2, \
urcrnrlon=maxx+2, \
urcrnrlat=maxy+2, \
resolution='i')
m.drawcoastlines(color="gray")
m.fillcontinents(color='beige')
# load the geotiff image, assign it a variable
image = georaster.SingleBandRaster( fpath, \
load_data=(minx, maxx, miny, maxy), \
latlon=True)
# plot the image on matplotlib active axes
# set zorder to put the image on top of coastlines and continent areas
# set alpha to let the hidden graphics show through
plt.imshow(image.r, extent=(minx, maxx, miny, maxy), zorder=10, alpha=0.6)
plt.show()
The resulting plot:
Edit1
My original answer places focus on how to plot simple geotiff image on the most basic projection with Basemap. A better answer was not possible without access to all required resources (i.e. geotiff file).
Here I try to improve my answer.
I have clipped a small portion from whole world geotiff file. Then reproject (warp) it to LCC projection specifications defined by Basemap() to be used. All the process were done with GDAL softwares. The resulting file is named "lcc_2.tiff". With this geotiff file, the plotting of the image is done with the code below.
The most important part is that geotiff file must have the same coordinate system (same projection) as the projection used by Basemap.
import georaster
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
fig = plt.figure(figsize=(8,8))
m = Basemap(projection='lcc', lon_0=67.5, lat_0=-68.5, \
height=950000, width=580000, resolution='h')
m.drawcoastlines()
m.fillcontinents(color='beige')
image = georaster.SingleBandRaster( "lcc_2.tiff", latlon=False)
plt.imshow(image.r, extent=image.extent, zorder=10, alpha=0.6)
plt.show()
The output map:
Here is my solution.
1. Import GEOTIF file and transform it into 2-D array data
from osgeo import gdal
pathToRaster = r'./xxxx.tif'
raster = gdal.Open(pathToRaster, gdal.GA_ReadOnly)
data = raster.GetRasterBand(1).ReadAsArray()
data = data[::-1]
2. Plot it using Pcolormesh
kk = plt.pcolormesh(data,cmap = plt.cm.Reds,alpha = 0.45, zorder =2)
You can use rioxarray
import rioxarray as rio
ds = rio.open_rasterio(path)
# Example lat lon range for subset
geometries = [
{
'type': 'Polygon',
'coordinates': [[
[33.97301017665958, -118.45830810580743],
[33.96660083660732, -118.37455741054782],
[33.92304171545437, -118.37151348516299],
[33.915042933806724, -118.42909440702563]
]]
}
]
clipped = ds.rio.clip(geometries)
clipped.plot()

How do I make a heatmap in Cartopy

I am mapping latitude, longitude, and then a separate value on cartopy.
How do I make the points colored like a heatmap based the list called klist? I cant find any snippets of code where it will work with cartopy
That list has range of values that I want colored based on the how big the value is.
# Define a Cartopy 'ordinary' lat-lon coordinate reference system.
crs_latlon = ccrs.PlateCarree()
def make_plot(projection_name, projection_crs):
ax = plt.axes(projection=projection_crs)
# Set display limits to include a set region of latitude * longitude.
# (Note: Cartopy-specific).
ax.set_extent((-65.0, -62, 44, 45.5), crs=crs_latlon)
# Add coastlines and meridians/parallels (Cartopy-specific).
ax.coastlines(linewidth=0.2, color='black')
ax.gridlines(crs=crs_latlon, linestyle='-')
# Mark some particular places with a small circle and a name label...
# Define some test points with latitude and longitude coordinates.
#city_data = [('Halifax, NS', 44.67, -63.61)]
plt.plot(lon,lat,marker='x', markersize=1.0, markeredgewidth=2.5,
markerfacecolor='black',
transform=crs_latlon)
# Add a title, and display.
iplt.show("Mission #1: Attenuation Coeffiecient")
def main():
# Demonstrate with two different display projections.
make_plot('Equidistant Cylindrical', ccrs.PlateCarree())
if __name__ == '__main__':
main()
From what I can see, you would produce a heat map the same way you would produce a heat map in plain matplotlib. Just use pcolormesh (or pcolor or whatever) and with a properly defined meshgrid. Here, I modified #berna1111's answer to produce a color map instead of drawing circles on the map.
To avoid drawing outside the coastlines, you could use a masked array or use transparency, although the former would probably be best.
In the following example, I supply a heat_data that is a numpy array that contains the data that will be colour coded. I assume that this data is defined over the whole map range for convenience. Your data may differ.
Because I don't have the actual data, I create lat and lon arrays from the extent and the size of heat_data. In the main(), I generate some noise to fill heat_data and create the plot.
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import numpy as np
def make_plot(projection_name, projection_crs, extent, heat_data):
"""
?
"""
fig = plt.figure()
rect = 0.1, 0.1, 0.8, 0.8
ax = fig.add_axes(rect, projection=projection_crs)
# Set display limits to include a set region of latitude * longitude.
# (Note: Cartopy-specific).
ax.set_extent(extent, crs=projection_crs)
# Add coastlines and meridians/parallels (Cartopy-specific).
ax.coastlines(linewidth=0.2, color='black')
ax.gridlines(crs=projection_crs, linestyle='-')
lat = np.linspace(extent[0],extent[1],heat_data.shape[0])
lon = np.linspace(extent[2],extent[3],heat_data.shape[1])
Lat,Lon = np.meshgrid(lat,lon)
ax.pcolormesh(Lat,Lon,np.transpose(heat_data))
plt.savefig("Test_fig.pdf", bbox_inches='tight')
def main():
#extent = (-65.0, -62, 44, 45.5)
extent = (-90, -40, 30, 60)
# Define some test points with latitude and longitude coordinates.
#city_data = [('Halifax, NS', 44.67, -63.61, 'black'),
# ('Neighbour', 45, -63, 'blue'),
# ('Other_Place', 44.1, -64, 'red')]
heat_data = np.random.normal(0.0,0.2,size=(100,150))
# Demonstrate with two different display projections.
# Define a Cartopy 'ordinary' lat-lon coordinate reference system.
crs_latlon = ccrs.PlateCarree()
make_plot('Equidistant Cylindrical', crs_latlon, extent, heat_data)
#crs_ae = ccrs.LambertCylindrical()
#make_plot('Lambert Cylindrical', crs_ae, extent, heat_data)
if __name__ == '__main__':
main()
If you want different coloured points this might help you (based on your code):
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
def make_plot(projection_name, projection_crs, extent, city_data):
"""
?
"""
fig = plt.figure()
rect = 0.1, 0.1, 0.8, 0.8
ax = fig.add_axes(rect, projection=projection_crs)
# Set display limits to include a set region of latitude * longitude.
# (Note: Cartopy-specific).
ax.set_extent(extent, crs=projection_crs)
# Add coastlines and meridians/parallels (Cartopy-specific).
ax.coastlines(linewidth=0.2, color='black')
ax.gridlines(crs=projection_crs, linestyle='-')
# Mark some particular places with a small circle and a name label...
for city in city_data:
ax.plot(city[2], city[1], marker='o',
markersize=2.0, markeredgewidth=1.0,
markeredgecolor=city[3], markerfacecolor=city[3],
linestyle='None', label=city[0], transform=projection_crs)
# Add a title, legend, and display.
ax.set_title(''.join(("Mission #1: Attenuation Coeffiecient - ",
projection_name)))
ax.legend()
fig.show()
def main():
#extent = (-65.0, -62, 44, 45.5)
extent = (-90, -40, 30, 60)
# Define some test points with latitude and longitude coordinates.
city_data = [('Halifax, NS', 44.67, -63.61, 'black'),
('Neighbour', 45, -63, 'blue'),
('Other_Place', 44.1, -64, 'red')]
# Demonstrate with two different display projections.
# Define a Cartopy 'ordinary' lat-lon coordinate reference system.
crs_latlon = ccrs.PlateCarree()
make_plot('Equidistant Cylindrical', crs_latlon, extent, city_data)
crs_ae = ccrs.LambertCylindrical()
make_plot('Lambert Cylindrical', crs_ae, extent, city_data)
if __name__ == '__main__':
main()
I don't know enough about cartography to understand why the points are in different places in the two projections, but maybe you know what that means and how to correct it.

Categories