Antti
Antti

Reputation: 1293

How to plot maps with Python's matplotlib so that small island states are included too?

I have a basic setting for plotting data on the African map with Python matplotlib. Unfortunately the geopandas natural earth database does not include the small island states that would be essential to have included as well.

My basic setting is like this:

import geopandas as gpd
import numpy as np
import matplotlib.pyplot as plt

world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
africa = world.query('continent == "Africa"')

africa.plot(column="pop_est")
plt.show()

And the figure I get is like this: enter image description here

Instead, I would like to have a figure that is something like this, where small island states are neatly presented by visible dots: enter image description here

(The source of the figure is: https://en.wikipedia.org/wiki/African_Continental_Free_Trade_Area#/media/File:AfricanContinentalFreeTradeArea.svg)

There are two problems I have: 1) geopandas natural earth data do not include the island states, and 2) I don't know how to draw the otherwise invisible island states as visible dots.

I saw some related questions in SO for R, but it is specifically the Python solution that I am after.

Upvotes: 5

Views: 3373

Answers (3)

Antti
Antti

Reputation: 1293

I got a working solution by finding the centroid data for each country. I used R for that, based on this post: https://gis.stackexchange.com/a/232959/68457

and made a GeoDataFrame that had a country identifier and geometry column for centroid points.

Then I applied geopandas function buffer to the centroid points, i.e.:

   dfCentroids["geometry"] = dfCentroids.buffer(1)

where 1 is the radius of the resulting spherical polygon. Then concatenating that with the geopandas naturalearth dataset, I got a geocoded data for plotting the map with dots on island states.

Upvotes: 1

swatchai
swatchai

Reputation: 18812

This is an interesting challenge. The following is a runnable code with output map that should meet the requirements stated in the question. Since I put many comments within the code, I should write short introduction here.

# Module imports
import matplotlib.pyplot as plt
import matplotlib
import cartopy
from cartopy.io import shapereader
import cartopy.crs as ccrs
import geopandas as gpd
import numpy as np
import pandas as pd

# get natural earth data from http://www.naturalearthdata.com/
# for country borders
use_res = '50m'  # medium resolution of (10m, 50m, 110m)
category = 'cultural'
name = 'admin_0_countries'
shpfilename = shapereader.natural_earth(use_res, category, name)

# read the shapefile using geopandas
df = gpd.read_file(shpfilename)

# select countries in Africa
africa = df[df['CONTINENT'] == "Africa"]

# It is possible to select the small island states by other methods without using their names
# .. but using names is presented here

# Select only countries in a list (small island states)
islnd_cts = ['Cabo Verde', 'Mauritius', 'Comoros', 'São Tomé and Principe', 'Seychelles']
islnds = df[df['NAME'].isin(islnd_cts)]

# collect name and centroid of countries in `islnds` dataframe
names, points, popest, iso_a3 = [], [], [], []
# this part can be improved
# 
for i, col_dict in islnds[['NAME', 'POP_EST', 'ISO_A3', 'geometry']].iterrows():
    #df1.loc[i, 'Result1'] = col_dict['NAME'] + col_dict['POP_EST']
    #print(col_dict['NAME'], col_dict['POP_EST'])
    names.append(col_dict['NAME'])
    points.append(col_dict['geometry'].centroid)
    popest.append(col_dict['POP_EST'])
    iso_a3.append(col_dict['ISO_A3'])

# prep a dict useful to build a dataframe
# population_estimate is intentionally omitted
ilsdict = {'NAME': names, 'ISO_A3': iso_a3, 'CENTROID': points}

# make it a dataframe
df6c = pd.DataFrame(ilsdict)

# create geodataframe of the island states
gdf6c = gpd.GeoDataFrame(df6c, crs={'init': 'epsg:4326'}, geometry='CENTROID')

# can do plot check with: 
#gdf6c.plot()

# Setup canvas for plotting multi-layered data (need cartopy here)
fig = plt.figure(figsize=(10, 10))
# set extent to cover Africa
extent =[-28,60,  -32, 40]  #lonmin, lonmax, latmin, latmax
ax = plt.axes(projection=cartopy.crs.PlateCarree())
ax.set_extent(extent)

# This do in batch, not possible to filter/check individual rows of data
africa.plot(ax=ax, edgecolor="black", facecolor='lightgray', lw=0.25)  #returns axes

# This layer of plot: island states, as colored dots
gdf6c.plot(ax=ax, facecolor='salmon', markersize=90)

# Annotate: iso-a3 abbrev-name of island states
for i, geo in gdf6c.centroid.iteritems():
    #print(str(i), ak['admin'][i], geo.x, geo.y)
    ax.annotate(s=gdf6c['ISO_A3'][i], xy=[geo.x, geo.y], color="blue")

# Draw other map features
ax.coastlines(resolution = use_res, lw=0.4)

ax.gridlines(draw_labels=True)
plt.title("African Island States", pad=20)
plt.show()

africa_island_states

Upvotes: 2

William Bradley
William Bradley

Reputation: 365

Can't comment yet, but I'll put this here. Using swatchai's method and the geopandas .area method, you could even set a threshold to plot circles/polygons.

Upvotes: 0

Related Questions