live drawing on image in Matplotlib - python

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

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

How to make a bubble graph using seaborn

import matplotlib.pyplot as plt
import numpy as np
# data
x=["IEEE", "Elsevier", "Others"]
y=[7, 6, 2]
import seaborn as sns
plt.legend()
plt.scatter(x, y, s=300, c="blue", alpha=0.4, linewidth=3)
plt.ylabel("No. of Papers")
plt.figure(figsize=(10, 4))
I want to make a graph as shown in the image. I am not sure how to provide data for both journal and conference categories. (Currently, I just include one). Also, I am not sure how to add different colors for each category.
You can try this code snippet for you problem.
- I modified your Data format, I suggest you to use pandas for
data visualization.
- I added one more field to visualize the data more efficiently.
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
import pandas as pd
# data
x=["IEEE", "Elsevier", "Others", "IEEE", "Elsevier", "Others"]
y=[7, 6, 2, 5, 4, 3]
z=["conference", "journal", "conference", "journal", "conference", "journal"]
# create pandas dataframe
data_list = pd.DataFrame(
{'x_axis': x,
'y_axis': y,
'category': z
})
# change size of data points
minsize = min(data_list['y_axis'])
maxsize = max(data_list['y_axis'])
# scatter plot
sns.catplot(x="x_axis", y="y_axis", kind="swarm", hue="category",sizes=(minsize*100, maxsize*100), data=data_list)
plt.grid()
How to create the graph with correct bubble sizes and with no overlap
Seaborn stripplot and swarmplot (or sns.catplot(kind=strip or kind=swarm)) provide the handy dodge argument which prevents the bubbles from overlapping. The only downside is that the size argument applies a single size to all bubbles and the sizes argument (as used in the other answer) is of no use here. They do not work like the s and size arguments of scatterplot. Therefore, the size of each bubble must be edited after generating the plot:
import numpy as np # v 1.19.2
import pandas as pd # v 1.1.3
import seaborn as sns # v 0.11.0
# Create sample data
x = ['IEEE', 'Elsevier', 'Others', 'IEEE', 'Elsevier', 'Others']
y = np.array([7, 6, 3, 7, 1, 3])
z = ['conference', 'conference', 'conference', 'journal', 'journal', 'journal']
df = pd.DataFrame(dict(organisation=x, count=y, category=z))
# Create seaborn stripplot (swarmplot can be used the same way)
ax = sns.stripplot(data=df, x='organisation', y='count', hue='category', dodge=True)
# Adjust the size of the bubbles
for coll in ax.collections[:-2]:
y = coll.get_offsets()[0][1]
coll.set_sizes([100*y])
# Format figure size, spines and grid
ax.figure.set_size_inches(7, 5)
ax.grid(axis='y', color='black', alpha=0.2)
ax.grid(axis='x', which='minor', color='black', alpha=0.2)
ax.spines['bottom'].set(position='zero', color='black', alpha=0.2)
sns.despine(left=True)
# Format ticks
ax.tick_params(axis='both', length=0, pad=10, labelsize=12)
ax.tick_params(axis='x', which='minor', length=25, width=0.8, color=[0, 0, 0, 0.2])
minor_xticks = [tick+0.5 for tick in ax.get_xticks() if tick != ax.get_xticks()[-1]]
ax.set_xticks(minor_xticks, minor=True)
ax.set_yticks(range(0, df['count'].max()+2))
# Edit labels and legend
ax.set_xlabel('Organisation', labelpad=15, size=12)
ax.set_ylabel('No. of Papers', labelpad=15, size=12)
ax.legend(bbox_to_anchor=(1.0, 0.5), loc='center left', frameon=False);
Alternatively, you can use scatterplot with the convenient s argument (or size) and then edit the space between the bubbles to reproduce the effect of the missing dodge argument (note that the x_jitter argument seems to have no effect). Here is an example using the same data as before and without all the extra formatting:
# Create seaborn scatterplot with size argument
ax = sns.scatterplot(data=df, x='organisation', y='count',
hue='category', s=100*df['count'])
ax.figure.set_size_inches(7, 5)
ax.margins(0.2)
# Dodge bubbles
bubbles = ax.collections[0].get_offsets()
signs = np.repeat([-1, 1], df['organisation'].nunique())
for bubble, sign in zip(bubbles, signs):
bubble[0] += sign*0.15
As a side note, I recommend that you consider other types of plots for this data. A grouped bar chart:
df.pivot(index='organisation', columns='category').plot.bar()
Or a balloon plot (aka categorical bubble plot):
sns.scatterplot(data=df, x='organisation', y='category', s=100*count).margins(0.4)
Why? In the bubble graph, the counts are displayed using 2 visual attributes, i) the y-coordinate location and ii) the bubble size. Only one of them is really necessary.

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].

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

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