This is example of Python script that could generate a xkcd-style for Country Map.
Example:
""" | |
NAME | |
xkcd_countrymap.py | |
DESCRIPTION | |
Generate a country map using XKCD style. | |
USAGE | |
You can paste the below code into an online Python compiler like https://python-fiddle.com/ | |
and grab the result instantly. Don't forget to change the country name in line 41. | |
The script used column NAME as country name identification from the Natural Earth data: | |
https://naciscdn.org/naturalearth/110m/cultural/ne_110m_admin_0_countries.zip | |
NOTES | |
The output is similar to what ReliefWeb's location maps do but in a more informal way. | |
The map includes a title, inset, north arrow, scale bar, and legend. | |
The map doesn't show country labels on it due to the complicated script and XKCD font issues. | |
This map is just for fun. | |
It uses low-resolution Natural Earth boundaries, which may not be comparable to the official | |
boundaries from each country, the United Nations, or the World Bank. | |
To use the XKCD font, ensure it is installed correctly on your system. | |
You can download it from: https://github.com/ipython/xkcd-font/tree/master/xkcd-script/font | |
CONTACT | |
Benny Istanto | |
Climate Geographer | |
GOST/DECSC/DECDG, The World Bank | |
LICENSE | |
This script is in the public domain, free from copyrights or restrictions. | |
VERSION | |
$Id$ | |
TODO | |
Add country labels to the map. | |
Add handling on country who located in central meridian, like Fiji. | |
""" | |
import matplotlib.pyplot as plt | |
import geopandas as gpd | |
from shapely.geometry import box | |
from matplotlib.patches import Polygon, Rectangle | |
import matplotlib.patheffects as PathEffects | |
import numpy as np | |
# Define the country name | |
# This is the main country that will be highlighted in the map. | |
country_name = "Indonesia" | |
# Load the world shapefile dataset from Natural Earth | |
# This dataset contains the geometries of all countries. | |
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres')) | |
# Get the bounding box for the country | |
# This retrieves the geometry of the specified country and calculates its bounding box. | |
country_geom = world[world.name == country_name].geometry.unary_union | |
bbox = country_geom.bounds | |
# Add a buffer of 5 degrees | |
# The buffer adds extra space around the country's bounding box to ensure the map isn't too tight around the edges. | |
buffer = 5 | |
bbox = (bbox[0] - buffer, bbox[1] - buffer, bbox[2] + buffer, bbox[3] + buffer) | |
# Clip the world data to the bounding box | |
# This clips the world dataset to the bounding box of the specified country. | |
bbox_shape = box(*bbox) | |
world_clip = world[world.intersects(bbox_shape)] | |
# Calculate the aspect ratio of the bounding box | |
# This ensures the plot maintains the correct aspect ratio based on the bounding box dimensions. | |
bbox_width = bbox[2] - bbox[0] | |
bbox_height = bbox[3] - bbox[1] | |
aspect_ratio = bbox_width / bbox_height | |
# Set minimum width for the figure to avoid extremely thin plots | |
# This sets a minimum width for the plot and adjusts the height accordingly. | |
min_width = 5 | |
fig_width = max(min_width, aspect_ratio * 5) | |
fig_height = fig_width / aspect_ratio + 0.5 # Add extra space for the legend | |
# Set XKCD style | |
# This applies the XKCD style to the plot for a hand-drawn, comic-like appearance. | |
plt.xkcd() | |
# Create the plot with dynamic figsize | |
# This creates the figure and axes objects with the specified dimensions. | |
fig, ax = plt.subplots(1, 1, figsize=(fig_width, fig_height), tight_layout=True) | |
plt.subplots_adjust(bottom=1.5 / fig_height) | |
# Set the plot limits to the bounding box | |
# This sets the x and y limits of the plot to match the bounding box. | |
ax.set_xlim(bbox[0], bbox[2]) | |
ax.set_ylim(bbox[1], bbox[3]) | |
# Plot the sea with light blue color within the bbox | |
# This adds a light blue rectangle to represent the sea within the bounding box. | |
ax.add_patch(Rectangle((bbox[0], bbox[1]), | |
bbox_width, bbox_height, | |
color='#ADD8E6', zorder=0)) | |
# Plot all countries | |
# This plots the boundaries and fills for all countries within the clipped area. | |
world_clip.boundary.plot(ax=ax, edgecolor='black', zorder=2) | |
world_clip.plot(ax=ax, color='white', edgecolor='black', zorder=3) | |
# Highlight the specified country in grey | |
# This highlights the specified country in grey. | |
highlight_country = world_clip[world_clip.name == country_name] | |
highlight_country.plot(ax=ax, color='grey', edgecolor='black', zorder=4) | |
# Plot the rectangle border for bbox | |
# This adds a border around the bounding box. | |
bbox_patch = Polygon([(bbox[0], bbox[1]), (bbox[0], bbox[3]), | |
(bbox[2], bbox[3]), (bbox[2], bbox[1]), | |
(bbox[0], bbox[1])], closed=True, | |
edgecolor='black', facecolor='none', linewidth=0.5, zorder=5) | |
ax.add_patch(bbox_patch) | |
# Add title | |
# This adds a title to the plot. | |
plt.title(f'{country_name}', fontsize=20) | |
# Remove axes | |
# This removes the axes for a cleaner look. | |
ax.set_axis_off() | |
# Calculate dynamic scale bar length | |
# This calculates the scale bar length dynamically based on the bounding box width. | |
bbox_width_km = bbox_width * 111 # approximate conversion from degrees to kilometers | |
scalebar_length = round(bbox_width_km / 10, -int(np.floor(np.log10(bbox_width_km / 10)))) # scale bar length to a rounded number | |
# Adjust the position of the scale bar | |
# This sets the position for the scale bar. | |
scale_bar_x_offset = 0.15 * (bbox[2] - bbox[0]) | |
scale_bar_y_offset = 0.05 * (bbox[3] - bbox[1]) | |
# Add scale bar next to the north arrow | |
# This plots the scale bar on the map. | |
scale_x = bbox[0] + 0.15 * (bbox[2] - bbox[0]) | |
scale_y = bbox[1] + 0.1 * (bbox[3] - bbox[1]) | |
scale_length_deg = scalebar_length / 111 # approximate conversion from km to degrees | |
# Plot the scale bar | |
ax.plot([scale_x, scale_x + scale_length_deg], [scale_y, scale_y], color='k', linewidth=3, zorder=6) | |
ax.text(scale_x + scale_length_deg / 2, scale_y - 0.5, f'{scalebar_length} km', ha='center', va='top', fontsize=12, color='k', | |
path_effects=[PathEffects.withStroke(linewidth=3, foreground="white")], zorder=6) | |
# Add north arrow | |
# This adds a north arrow to the map. | |
x, y, arrow_length = 0.05, 0.14, 0.1 | |
ax.annotate('N', xy=(x, y), xytext=(x, y - arrow_length), | |
arrowprops=dict(facecolor='black', width=5, headwidth=15), | |
ha='center', va='center', fontsize=15, | |
xycoords=ax.transAxes, zorder=6) | |
# Calculate dynamic font size for legend | |
# This calculates the font size for the legend dynamically based on the bounding box width. | |
legend_font_size = min(8, bbox_width * 10) # Adjust the multiplier as needed | |
# Add custom legend with adjusted font size | |
# This adds a legend to the map with the specified font size. | |
legend_patches = [ | |
Polygon([(0,0)], closed=True, edgecolor='black', facecolor='grey', label=country_name), | |
Polygon([(0,0)], closed=True, edgecolor='black', facecolor='white', label='Other Countries'), | |
Rectangle((0,0),1,1, color='#ADD8E6', label='Sea') | |
] | |
legend = plt.legend(handles=legend_patches, loc='lower center', bbox_to_anchor=(0.5, -0.07), ncol=3, fontsize=legend_font_size) | |
# Add data source label | |
# This adds a label indicating the data source at the bottom right of the map. | |
plt.text(bbox[2], bbox[1], "Data: Natural Earth", fontsize=6, ha='right', va='bottom', zorder=6) | |
# Add world map inset | |
# This adds an inset world map to the top right corner of the plot. | |
inset_width = 0.2 | |
inset_height = inset_width * (fig_height / fig_width) | |
# Calculate the top right corner of the main map in figure coordinates | |
bbox_main = ax.get_position() | |
inset_left = bbox_main.x1 - inset_width | |
inset_bottom = bbox_main.y1 - inset_height | |
# Adjust inset position to align with top right corner of the main map area | |
inset_ax = fig.add_axes([inset_left, inset_bottom, inset_width, inset_height]) | |
world.plot(ax=inset_ax, color='lightgrey') | |
highlight_country_inset = world[world.name == country_name] | |
highlight_country_inset.plot(ax=inset_ax, color='red', edgecolor='red') | |
inset_ax.set_xticks([]) | |
inset_ax.set_yticks([]) | |
inset_ax.set_xlim(-180, 180) | |
inset_ax.set_ylim(-90, 90) | |
# Show the plot | |
# This displays the final map. | |
plt.show() |