Many data analysis tasks nowadays involve working with geo spatial data and plot it on maps to get useful insights. For this purpose, we need a trustworthy and reliable plotting library that can be used for plotting maps from a different perspective. Python has a list of libraries (geopandas, bokeh, holoviews, plotly, bqplot, etc) which provides functionalities to plot different types of maps like scatter maps, choropleth map, bubble map, etc. We have already covered tutorials on creating maps using geopandas, holoviews and bqplot.
In this tutorial, we are going to introduce one more python map plotting library named cartopy. Canopy is a map plotting library in python which is based on matplotlib
for plotting. It uses PROJ.4
, numpy
and shapely
for handling data conversions between cartographic projection and handling shape files. Cartopy can be very useful to generate a high-quality static map chart that has high publication quality. If you want a high publication chart for your research paper and looking for plotting library with various projections available then cartopy is for you.
The main plus point of cartopy is its object-oriented projection system which lets us plot maps with points, lines polygons, etc using one projection and then can easily let us transfer it to another projection system.
As a part of this tutorial, we'll explore the API of cartopy in order to generate various maps. We'll be using various datasets available from kaggle for our map examples.
We'll start by loading necessary libraries for our plotting purpose.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import cartopy
import sys
from itertools import combinations
from datetime import datetime
import random
import warnings
warnings.filterwarnings("ignore")
print("Python Version : ", sys.version)
print("Cartopy Version : ", cartopy.__version__)
%matplotlib inline
The generation of maps using cartopy can be done by following below mentioned common steps:
plt.figure()
.projection
attribute set as one of the projections available from cartopy.cars
module.cartopy.feature
module.plt.show()
.Above mentioned steps are common steps when you want to generate world maps but it also provides other functionalities which can let you restrict map area to a particular region. We'll use the above-mentioned steps to plot a simple map.
Below we are plotting our first simple plot with projection PlateCarree. We have also added the COASTLINE
feature so that we can see all continental borders.
import cartopy.crs as crs
import cartopy.feature as cfeature
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(1,1,1, projection=crs.PlateCarree())
ax.add_feature(cfeature.COASTLINE)
plt.show()
If you are facing issue when running cartopy code where your kernel crashes or you are getting error related to GEOS then we suggest that you follow solution mentioned on this link.
We'll now try a few graph projections available in cartopy. The list of projections as available in crs
module of the cartopy
library.
Full Projections List: https://scitools.org.uk/cartopy/docs/v0.11/crs/projections.html
Below we are again trying the same PlateCarree projection which we had tried in the previous figure but we have also introduced natural earth look to the plot. We can add it by simply calling the stock_img()
method on the axes object. We have also introduced a new way to add new coastline features to our plot.
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(1,1,1, projection=crs.PlateCarree())
ax.stock_img()
ax.coastlines()
plt.show()
Below we have introduced another projection named AlbersEqualArea. We also have introduced grid lines on the plot as a light grey color. The graph generally keeps center at 0 longitudes and 0 latitudes which we can change by setting central_latitude
and central_longitude
parameter of AlbersEqualArea object.
fig = plt.figure(figsize=(6,6))
ax = fig.add_subplot(1,1,1, projection=crs.AlbersEqualArea())
ax.stock_img()
ax.coastlines()
ax.gridlines()
plt.show()
Below we have introduced another projection named Orthographic which has a shape of the globe. We also have passed its parameter central_latitude
and central_longitude
which centers glob location on that point which is the center of India.
fig = plt.figure(figsize=(5,5))
ax = fig.add_subplot(1,1,1, projection=crs.Orthographic(central_latitude=20, central_longitude=80))
ax.stock_img()
ax.coastlines()
plt.show()
We'll now explore further adding more futures to our map plots. Till we only had added coastline features to our maps.
Below we are first creating a map with projection as Mercator
. We are then adding features by calling method add_feature()
on-axis object passing it feature object available from the cartopy.feature
module. The list of features available through cartopy is available from the cartopy.feature
module.
We are adding below feature to our map:
COASTLINE
feature - Adds coastline around all continents. We can change a coastline color, style, etc.LAND
feature - Adds land on top of world map. We can change it's color and opacity as well.LAKES
feature - Adds big lakes of the world. We can change its configuration as well.BORDERS
feature - Adds country borders for the whole world. We can change border style and color, etc.OCEAN
feature - Adds ocean with a color that we can change by setting our own configuration as well.RIVERS
feature - Adds big rivers of the world.STATES
feature - Adds state boundary for the US.There are few more classes available from cartopy which lets us add our own designed features but we have not discussed them here as they are not part of the scope of this tutorial.
fig = plt.figure(figsize=(8,8))
ax = fig.add_subplot(1,1,1, projection=crs.Mercator())
ax.add_feature(cfeature.COASTLINE)
ax.add_feature(cfeature.LAND, color="lightgrey", alpha=0.5)
ax.add_feature(cfeature.LAKES, color="lime")
ax.add_feature(cfeature.BORDERS, linestyle="--")
ax.add_feature(cfeature.OCEAN, color="skyblue", alpha=0.4)
ax.add_feature(cfeature.RIVERS, edgecolor="red")
ax.add_feature(cfeature.STATES)
plt.show()
Below we are adding one more feature to cartopy map called nightshade feature. We need to create a datetime object with datetime data and need to pass it to Nightshade
object so that it can create nightshade on a map at this time. It'll create a shade effect on a map covering countries where it'll be night during that time.
from cartopy.feature.nightshade import Nightshade
fig = plt.figure(figsize=(8, 5))
ax = fig.add_subplot(1, 1, 1, projection=crs.PlateCarree())
ax.stock_img()
current_time = datetime.now()
ax.add_feature(Nightshade(current_time, alpha=0.3))
ax.set_title('Night time shading for {}'.format(current_time))
plt.show()
Below we are explaining how to show only a particular part of the map instead of showing the whole world map. It happens many times that we only need to show a particular country, continent, and region of interest than showing the whole world map. We can do this in two ways:
set_extent()
on axes object passing it latitude and longitudes of the area to be covered.set_xlim()
and set_ylim()
methods passing them longitude and latitude limits respectively.We have used the below set_extent()
method passing it projection as PlateCarree so that it projects box from that projection. We have tried to cover Asia and Europe continents on a map.
fig = plt.figure(figsize=(6,6))
ax = fig.add_subplot(1,1,1, projection=crs.Mercator())
ax.stock_img()
ax.coastlines()
ax.add_feature(cfeature.BORDERS)
lat1, lon1, lat2, lon2 = -10, 180, 0, 80
ax.set_extent([lat1, lon1, lat2, lon2], crs=crs.PlateCarree())
#ax.set_xlim(65, 95)
#ax.set_ylim(5, 40)
plt.show()
We have now explained how to create various maps using cartopy. We'll now explain how to add further more data on maps like plots, lines, etc below. For that, we'll be using the Starbucks store locations dataset and brazil flight dataset.
The first dataset that we'll load is Starbucks store locations dataset available from kaggle. We suggest that you download a dataset from kaggle to follow along.
This dataset has information about Starbucks store locations worldwide. It has also geospatial information (longitude & latitude) about each store location. Apart from geospatial information, it has data about store address, name, number, ownership type, country, state, city, postcode, etc.
starbucks_locations = pd.read_csv("datasets/starbucks_store_locations.csv")
starbucks_locations.head()
We'll be filtering dataset removing entries from all other countries present other than the United States.
starbucks_us = starbucks_locations[starbucks_locations["Country"] == "US"]
starbucks_us.head()
Below we are plotting scatter map of showing Starbucks locations on the whole Earth.
We have first created a map using a new projection named Robinson
. We have then added scatter points on a map using commonly using plt.scatter()
the method passing its longitude and latitude of locations. We have also passed it PlateCarree
projection as a transform
parameter value. We need to pass it so that it plots points on that projection first and then projects it on a map with Robinson
projection. This is the convention that we'll be following in future maps as well.
fig = plt.figure(figsize=(10,8))
ax = fig.add_subplot(1,1,1, projection=crs.Robinson())
ax.set_global()
ax.add_feature(cfeature.COASTLINE, edgecolor="tomato")
ax.add_feature(cfeature.BORDERS, edgecolor="tomato")
ax.gridlines()
plt.scatter(x=starbucks_locations.Longitude, y=starbucks_locations.Latitude,
color="dodgerblue",
s=1,
alpha=0.5,
transform=crs.PlateCarree()) ## Important
plt.show()
We can notice a high number of Starbucks locations in the whole US and Europe. There is also a sizable number of stores in Chine and Canada as well. All other locations of the world have very less or no Starbucks presence.
For creating a bubble map of Starbucks store count per US states, we need to prepare data by following the below-mentioned steps.
Our final dataset will have an entry for each state along with its latitude, longitude, and Starbucks store count which we'll use for plotting bubble map.
## Below step will get us geospatial data for each state.
us_statewise_lat_lon = starbucks_us.groupby(by="State/Province").mean()[["Longitude", "Latitude"]]
## Below step will get us store count per state.
statewise_store_count = starbucks_us.groupby(by="State/Province").count()[["Store Number"]].rename(columns={"Store Number":"Count"})
## Below step will merge store counts dataframe with geospatial dataframe.
statewise_store_count = us_statewise_lat_lon.join(statewise_store_count).reset_index()
statewise_store_count.head()
Once we are done with creating a dataset, we'll plot the bubble map exactly the same way as the previous map. We'll call plt.scatter()
the method passing it latitude and longitude
of each state of the US. We also have passed the size of the marker to be used for each step which is count of stores
available in that state. This time we are also restricting graph by setting a limit on it using set_extent()
so that it only displays US map. We also have added for states of the US.
fig = plt.figure(figsize=(12,10))
ax = fig.add_subplot(1,1,1, projection=crs.PlateCarree())
ax.stock_img()
ax.coastlines()
ax.add_feature(cfeature.STATES)
ax.set_extent([-135, -66.5, 20, 55],
crs=crs.PlateCarree()) ## Important
plt.scatter(x=statewise_store_count.Longitude, y=statewise_store_count.Latitude,
color="orangered",
s=statewise_store_count.Count,
alpha=0.8,
transform=crs.PlateCarree()) ## Important
plt.show()
We can notice from the above chart that there is a very high presence of Starbucks in California, Washington, Texas, Florida, Illinois, and New York.
The second dataset that we'll be using is flight data for a number of flights from and to Brazil. The dataset is available at kaggle.
The dataset has information about flight time, type, airline name, source city, destination city, source country, destination country, source state, destination state, source latitude & longitude, and destination latitude & longitude.
We suggest that you download the dataset to follow along with us. Please make a note that the dataset is quite big and has nearly 2.5 Mn rows. If your computer has less memory and wants to follow along with tutorial then we suggest that you load the first few rows only using nrows
parameter of read_csv()
.
brazil_flights = pd.read_csv("brazil_flights_data.csv", nrows=100000, encoding="latin")
brazil_flights.head()
To plot a connection map depicting flights from Brazil to other countries, we need to first prepare the dataset by following a list of the below steps.
Our final dataset will have information about flight count from source to destination countries as well as geospatial data for each country.
## Removing entry where source and destination are not same and destination country is not Brazil.
## This will remove domestic entries as well as flights from other countries to brazil.
## It'll keep entries of flights from brazil to other countries.
brazil_flights = brazil_flights[(brazil_flights["Pais.Origem"]!=brazil_flights["Pais.Destino"]) & (brazil_flights["Pais.Destino"]!="Brasil")]
## This will get us count of flight from Brazil to each country.
brazil_to_other_country_count = brazil_flights.groupby(by=["Pais.Origem", "Pais.Destino"]).count()[["Voos"]].rename(columns={"Voos":"Count"})
## This will get us mean of all columns grouped according source and destination countries.
brazil_to_other_country_lat_lon = brazil_flights.groupby(by=["Pais.Origem", "Pais.Destino"]).mean()
## This will filter and remove columns other that geospatial data columns
brazil_to_other_country_lat_lon = brazil_to_other_country_lat_lon[["LongDest","LatDest","LongOrig","LatOrig"]]
## This will merge flight count dataframe with geospatial data dataframe.
brazil_to_other_country_flights = brazil_to_other_country_count.join(brazil_to_other_country_lat_lon).reset_index()
brazil_to_other_country_flights.head()
Our last map is a connection map where we'll be displaying flight movement from Brazil to various countries of the earth using a line. We also show a thick line where there are more flights going on and a thin line where fewer flights are going.
We have used Mollweide
projection this time for the creation of a world map. We are then looping through each flight entry taking the source and destination latitude, longitude to draw line between them using conventional plt.plot()
method. We have used Geodetic
projection for plotting lines so that it shows a curvy line instead of a straight line from source to destination. We are also plotting names of destination
countries where flights are landing from Brazil to highlight them.
fig = plt.figure(figsize=(15,8))
ax = fig.add_subplot(1,1,1, projection=crs.Mollweide())
ax.stock_img()
ax.set_global()
ax.coastlines()
ax.add_feature(cfeature.STATES)
## Looping thorugh each entry of flights
for idx in brazil_to_other_country_flights.index:
## Getting source and destination latitude as well as longitude
lat1, lon1 = brazil_to_other_country_flights.loc[idx]["LatOrig"], brazil_to_other_country_flights.loc[idx]["LongOrig"]
lat2, lon2 = brazil_to_other_country_flights.loc[idx]["LatDest"], brazil_to_other_country_flights.loc[idx]["LongDest"]
## Getting flights count
num_flights = brazil_to_other_country_flights.loc[idx]["Count"]
## Plotting line from source country to destination country.
plt.plot([lon1, lon2], [lat1, lat2],
linewidth=2 if num_flights > 50 else 0.5,
color="lime",
transform=crs.Geodetic()) ## Important
## Plotting destination country name on map.
plt.text(lon2, lat2,
brazil_to_other_country_flights.loc[idx]["Pais.Destino"],
color="red", fontsize=10,
horizontalalignment="center", verticalalignment="center",
transform=crs.Geodetic()) ## Important
plt.show()
We can sizable number of flight traveling from Brazil to Southern American countries, US, Canada, and European countries. There are some travels to African countries as well but none traveling to Asia.
This ends our small tutorial on covering the basics of cartopy. Please feel free to let us know your views in the comments section.
If you are more comfortable learning through video tutorials then we would recommend that you subscribe to our YouTube channel.
When going through coding examples, it's quite common to have doubts and errors.
If you have doubts about some code examples or are stuck somewhere when trying our code, send us an email at coderzcolumn07@gmail.com. We'll help you or point you in the direction where you can find a solution to your problem.
You can even send us a mail if you are trying something new and need guidance regarding coding. We'll try to respond as soon as possible.
If you want to