How do I make a heatmap in Cartopy - python

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.

Related

Resampling and re-projecting weather satellite image

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())

live drawing on image in Matplotlib

I'm trying to plot the satellite ground track on a map currently I'm using cartopy, matplotlib
the pass of the satellite is calculated separately and saved in.CSV file when I try to plot the path the result is great and accurate but it's done once (the full path is plotted as a whole)
but I need to plot it point by point and update the figure each time a new point is added.
this is the code I used in this problem:
# imports
import cartopy.crs as ccrs
import pandas as pd
import matplotlib.pyplot as plt
from cartopy.feature.nightshade import Nightshade
import datetime
# reading CSV file
data = pd.read_csv("longlat.csv")
# converting column data to list of latitudes and longitudes
latitudes = data['Latitude'].tolist()
longitudes = data['Longitude'].tolist()
# Create a new figure, or activate an existing figure.
fig = plt.figure(figsize=(15.2, 8.2))
# Add an Axes to the current figure
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
# time to set the Nightshade to
# date = datetime.datetime(2022, 3, 8, 20, 9, 00)
date = datetime.datetime.now()
ax.stock_img()
# add Nightshade tp the map according to the time
ax.add_feature(Nightshade(date, alpha=0.3))
# set the x and y limits of the current axes.
plt.xlim([-180, 180])
plt.ylim([-90, 90])
# Configure the grid lines displayed on the map with the labels om x amd y.
plt.grid(True, color='w', linestyle="dotted", alpha=0.3, linewidth=1)
plt.xticks(range(-180, 181, 30), ['-180°W', '-150°W', '-120°W', '-90°W', '-60°W', '-30°W', '0°', '30°E', '60°E',
'90°E', '120°E', '150°E', '180°E'])
plt.yticks(range(-90, 91, 30), ['-90°S', '-60°S', '-30°S', '0°', '30°N', '60°N', '90°N'])
# Adjust the padding between and around subplots.
plt.tight_layout()
def plot_city(cities_list):
"""
function plot cities location points on the map
:param cities_list: list like object
list of cities to plo on the map ['city name', longitude, latitude]
:return: None
"""
for city in cities_list:
# plot city on the map
plt.plot(city[1], city[2], marker="o", markersize=5, markeredgecolor="blue", markerfacecolor="green")
# plot city name on the map according to it's location
plt.text(city[1] - 1, city[2] - 1, city[0], horizontalalignment='right', fontsize=8, transform=ccrs.Geodetic())
cities = [['Cairo', 29.35, 30.03333], ['Moscow', 37.618423, 55.751244], ['Paris', 2.349014, 48.864716]]
# sample city location plotting (cairo)
plot_city(cities)
def satellite_plot(longitudes, latitudes):
# plot the satellite moving line
plt.plot(longitudes, latitudes, 'red', linestyle="dotted", linewidth=2, transform=ccrs.Geodetic())
satellite = plt.plot(longitudes, latitudes, marker="o", markersize=5,
markeredgecolor="blue", markerfacecolor="green")
line = satellite.pop(0)
line.remove()
satellite_plot(longitudes, latitudes)
plt.show()

How can I annotate text in Basemap using the exact coordinates of the location in Python?

I have created a Basemap of Asia using following lines of code. The projection used is "marcator". The region I want to show in this basemap is Chitwan, Nepal. The coordinates is also provided.
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
chitwan = (27.618556599999998, 84.45385600798173)
fig = plt.figure()
ax = fig.add_axes([0.5, 0.5, 2, 2]) #left, bottom, width, height
m = Basemap(
llcrnrlon = 30,
llcrnrlat = 0,
urcrnrlon = 120,
urcrnrlat = 60,
projection = "merc",
resolution = "l"
)
m.plot(chitwan[1], chitwan[0],
latlon = True,
marker = "s",
color = "blue",
markersize = 20)
m.drawcoastlines()
m.fillcontinents()
m.drawcountries()
m.drawstates()
plt.annotate(text = "Chitwan, Nepal", xy = chitwan)
plt.show()
I am able to add a blue square marker in Chitwan, Nepal using
m.plot(chitwan[1], chitwan[0],
latlon = True,
marker = "s",
color = "blue",
markersize = 40)
Passing latlon = True allows me to draw the marker using the coordinates of the place directly.
However, I also want to annotate "Chitwan, Nepal" as a text in the same location.
I tried plt.annotate(text = "Chitwan, Nepal", xy = chitwan). However, this plots the text on the lower left corner of the Basemap as shown below.
I think this should be because the latitude and longitude coordinates of the text is not projected to that of the Basemap. So how can I project the coordinates of my location such that the text is also inserted in the same location as the marker? Is it possible to do this by passing the exact coordinates of the place directly and passing an argument similar to latlon = True for the marker?
Ok. I figured out the way for this myself.
I need to use:
x, y = m(chitwan[1], chitwan[0])
The x and y are projected to the map location with longitude and latitude of chitwan respectively.
And then I annotated the text as follows:
ax.annotate(text = "Chitwan, Nepal",
xy = (x+5,y+5),
)
I get the resulting plot as shown:

Python: Iteration over Polygon in Dataframe from Shapefile to color cartopy map

I'm coloring countries on a cartopy map according to certain values. I'm using geopandas and a shapefile from: https://www.naturalearthdata.com/
While iterating over the dataframe df to get the geometry of certain countries I encountered a problem. I can get the geometry of Countries with a Multipolygon geometry, but I can't get do it with countries with a polygon geometry e.g. Belgium or Austria.
Here is my code:
#imports
import matplotlib.pyplot as plt
import matplotlib
import cartopy
from cartopy.io import shapereader
import cartopy.crs as ccrs
import geopandas
import numpy as np
# get natural earth data (http://www.naturalearthdata.com/)
# get country borders
resolution = '10m'
category = 'cultural'
name = 'admin_0_countries'
shpfilename = shapereader.natural_earth(resolution, category, name)
# read the shapefile using geopandas
df = geopandas.read_file(shpfilename)
# Set up the canvas
fig = plt.figure(figsize=(20, 20))
central_lon, central_lat = 0, 45
extent = [-10, 28, 35, 65]
ax = plt.axes(projection=cartopy.crs.Orthographic(central_lon, central_lat))
ax.set_extent(extent)
ax.gridlines()
# Add natural earth features and borders
ax.add_feature(cartopy.feature.BORDERS, linestyle='-', alpha=0.8)
ax.add_feature(cartopy.feature.OCEAN, facecolor=("lightblue"))
ax.add_feature(cartopy.feature.LAND, facecolor=("lightgreen"), alpha=0.35)
ax.coastlines(resolution='10m')
# Countries and value
countries = ['Sweden', 'Netherlands', 'Ireland', 'Denmark', 'Germany', 'Greece', 'France', 'Spain', 'Portugal', 'Italy', 'United Kingdom', 'Austria']
value = [47.44, 32.75, 27.53, 23.21, 20.08, 18.08, 17.23, 13.59, 12.13, 5.66, 22.43, 7]
# Normalise values
value_norm = (value-np.nanmin(value))/(np.nanmax(value) - np.nanmin(value))
# Colourmap
cmap = matplotlib.cm.get_cmap('YlOrBr')
for country, value_norm in zip(countries, value_norm):
# read the borders of the country in this loop
poly = df.loc[df['ADMIN'] == country]['geometry'].values[0]
# get the color for this country
rgba = cmap(value_norm)
# plot the country on a map
ax.add_geometries(poly, crs=ccrs.PlateCarree(), facecolor=rgba, edgecolor='none', zorder=1)
# Add a scatter plot of the original data so the colorbar has the correct numbers
dummy_scat = ax.scatter(value, value, c=value, cmap=cmap, zorder=0)
fig.colorbar(mappable=dummy_scat, label='Percentage of Do and Dont`s [%]', orientation='horizontal', shrink=0.8)
plt.show()
fig.savefig("Länderübersicht.jpg")
How can I iterate over, or rather color, these countries or do I have to get another shapefile?
Thanks!
The command ax.add_geometries() asks for a list of geometries, so that, a single geometry will cause an error. To fix your code, you can replace the line:
ax.add_geometries(poly, crs=ccrs.PlateCarree(), facecolor=rgba, edgecolor='none', zorder=1)
with these lines of code:
# plot the country on a map
if poly.geom_type=='MultiPolygon':
# `poly` is a list of geometries
ax.add_geometries(poly, crs=ccrs.PlateCarree(), facecolor=rgba, edgecolor='none', zorder=1)
elif poly.geom_type=='Polygon':
# `poly` is a geometry
# Austria, Belgium
# Plot it `green` for checking purposes
ax.add_geometries([poly], crs=ccrs.PlateCarree(), facecolor="green", edgecolor='none', zorder=1)
else:
pass #do not plot the geometry
Note that if poly.geom_type is 'Polygon', I just use [poly] in place of poly.
Taking inspiration from the error code TypeError: 'Polygon' object is not iterable I started from the assumption that we need some kind of iterable, such as a list of polygons. Drawing from this answer I found the function shapely.geometry.MultiPolygon does the job. You simply pass it a list of polygons. Add a little logic to take this action only when a Polygon rather than a MultiPolygon is detected and we have:
poly = df.loc[df['ADMIN'] == country]['geometry'].values[0]
if type(poly) == shapely.geometry.polygon.Polygon:
simple_poly = df.loc[df['ADMIN'] == country]['geometry'].values[0]
list_polys = [poly, poly]
poly = shapely.geometry.MultiPolygon(list_polygons)
This is a rather hacky solution that will print the polygon twice, so be aware if you later decide to make it transparent or something. Alterantively, in place of [poly, poly] you could use [poly, some_other_poly_outside_map_area].

Adding quantitative values to differentiate data through colours in a scatterplot's legend in Python?

Currently, I'm working on an introductory paper on data manipulation and such; however... the CSV I'm working on has some things I wish to do a scatter graph on!
I want a scatter graph to show me the volume sold on certain items as well as their average price, differentiating all data according to their region (Through colours I assume).
So what I want is to know if I can add the region column as a quantitative value
or if there's a way to make this possible...
It's my first time using Python and I'm confused way too often
I'm not sure if this is what you mean, but here is some working code, assuming you have data in the format of [(country, volume, price), ...]. If not, you can change the inputs to the scatter method as needed.
import random
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
n_countries = 50
# get the data into "countries", for example
countries = ...
# in this example: countries is [('BS', 21, 25), ('WZ', 98, 25), ...]
df = pd.DataFrame(countries)
# arbitrary method to get a color
def get_color(i, max_i):
cmap = matplotlib.cm.get_cmap('Spectral')
return cmap(i/max_i)
# get the figure and axis - make a larger figure to fit more points
# add labels for metric names
def get_fig_ax():
fig = plt.figure(figsize=(14,14))
ax = fig.add_subplot(1, 1, 1)
ax.set_xlabel('volume')
ax.set_ylabel('price')
return fig, ax
# switch around the assignments depending on your data
def get_x_y_labels():
x = df[1]
y = df[2]
labels = df[0]
return x, y, labels
offset = 1 # offset just so annotations aren't on top of points
x, y, labels = get_x_y_labels()
fig, ax = get_fig_ax()
# add a point and annotation for each of the labels/regions
for i, region in enumerate(labels):
ax.annotate(region, (x[i] + offset, y[i] + offset))
# note that you must use "label" for "legend" to work
ax.scatter(x[i], y[i], color=get_color(i, len(x)), label=region)
# Add the legend just outside of the plot.
# The .1, 0 at the end will put it outside
ax.legend(loc='upper right', bbox_to_anchor=(1, 1, .1, 0))
plt.show()

Categories