Skip to content

Instantly share code, notes, and snippets.

@bennyistanto
Last active May 28, 2024 02:33
Show Gist options
  • Save bennyistanto/dc6804ccdea540a940d8f5e987468bfb to your computer and use it in GitHub Desktop.
Save bennyistanto/dc6804ccdea540a940d8f5e987468bfb to your computer and use it in GitHub Desktop.
Custom xkcd-style map for Indonesia

Custom xkcd-style map for Indonesia

Label setting for capital, country name and the ocean, and the red-dot is manually set, the value is calculate from top-left main map area.

You can paste the below code into an online Python compiler like https://python-fiddle.com/ and grab the result instantly.

map_idn_xkcd_style

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
# Set the figure DPI to 300
plt.rcParams['figure.dpi'] = 300
# Define the country name
country_name = "Indonesia"
# Load the world shapefile dataset
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
# Get the bounding box for the country
country_geom = world[world.name == country_name].geometry.unary_union
bbox = country_geom.bounds
# Add a buffer of 5 degrees
buffer = 5
bbox = (bbox[0] - buffer, bbox[1] - buffer, bbox[2] + buffer, bbox[3] + buffer)
# Clip the world data to the bounding box
bbox_shape = box(*bbox)
world_clip = world[world.intersects(bbox_shape)]
# Calculate the aspect ratio of the bounding box
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
min_width = 5
fig_width = max(min_width, aspect_ratio * 5)
fig_height = fig_width / aspect_ratio + 0.5 # Add extra space for legend
# Set XKCD style
plt.xkcd()
# Create the plot with tight layout to ensure bbox fit
fig, ax = plt.subplots(1, 1, figsize=(10, 5), tight_layout=True)
# Set the plot limits to 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
ax.add_patch(Rectangle((bbox[0], bbox[1]),
bbox[2] - bbox[0], bbox[3] - bbox[1],
color='#ADD8E6', zorder=0))
# Plot all countries
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
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
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
plt.title(f'Map of {country_name} and Surrounding Areas', fontsize=18)
# Remove axes
ax.set_axis_off()
# Add custom legend
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 left')
# Adjust the position of the scale bar and north arrow
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 legend box
scalebar_length = 500 # in kilometers
scale_x = bbox[0] + 0.3 * (bbox[2] - bbox[0])
scale_y = bbox[1] + 0.08 * (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)
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")])
# Add north arrow
x, y, arrow_length = 0.34, 0.22, 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)
# Add data source label
plt.text(bbox[2], bbox[1], "Data: Natural Earth", fontsize=8, ha='right', va='bottom', zorder=6)
# Add world map inset
inset_width = 0.3
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 + 0.05, inset_bottom - 0.06, 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)
# Add country labels
ax.text(bbox[0] + 0.4 * bbox_width, bbox[3] - 0.5 * bbox_height, "I n d o n e s i a", fontsize=15, ha='left', zorder=6, path_effects=[PathEffects.withStroke(linewidth=3, foreground="white")])
ax.text(bbox[0] + 0.1 * bbox_width, bbox[3] - 0.1 * bbox_height, "Thailand", fontsize=11, ha='left', zorder=6, path_effects=[PathEffects.withStroke(linewidth=3, foreground="white")])
ax.text(bbox[0] + 0.25 * bbox_width, bbox[3] - 0.06 * bbox_height, "Viet Nam", fontsize=11, ha='left', zorder=6, path_effects=[PathEffects.withStroke(linewidth=3, foreground="white")])
ax.text(bbox[0] + 0.18 * bbox_width, bbox[3] - 0.27 * bbox_height, "Malaysia", fontsize=11, ha='left', zorder=6, path_effects=[PathEffects.withStroke(linewidth=3, foreground="white")])
ax.text(bbox[0] + 0.37 * bbox_width, bbox[3] - 0.3 * bbox_height, "Malaysia", fontsize=11, ha='left', zorder=6, path_effects=[PathEffects.withStroke(linewidth=3, foreground="white")])
ax.text(bbox[0] + 0.24 * bbox_width, bbox[3] - 0.37 * bbox_height, "Singapore", fontsize=11, ha='left', zorder=6, path_effects=[PathEffects.withStroke(linewidth=3, foreground="white")])
ax.text(bbox[0] + 0.45 * bbox_width, bbox[3] - 0.2 * bbox_height, "Brunei\nDarussalam", fontsize=11, ha='right', zorder=6, path_effects=[PathEffects.withStroke(linewidth=3, foreground="white")])
ax.text(bbox[0] + 0.6 * bbox_width, bbox[3] - 0.1 * bbox_height, "Philippines", fontsize=11, ha='center', zorder=6, path_effects=[PathEffects.withStroke(linewidth=3, foreground="white")])
ax.text(bbox[0] + 0.95 * bbox_width, bbox[3] - 0.68 * bbox_height, "Papua\nNew\nGuinea", fontsize=11, ha='center', zorder=6, path_effects=[PathEffects.withStroke(linewidth=3, foreground="white")])
ax.text(bbox[0] + 0.68 * bbox_width, bbox[3] - 0.78 * bbox_height, "Timor-Leste", fontsize=11, ha='center', zorder=6, path_effects=[PathEffects.withStroke(linewidth=3, foreground="white")])
ax.text(bbox[0] + 0.76 * bbox_width, bbox[3] - 0.93 * bbox_height, "Australia", fontsize=11, ha='center', zorder=6, path_effects=[PathEffects.withStroke(linewidth=3, foreground="white")])
# Add ocean labels
ax.text(bbox[0] + 0.12 * bbox_width, bbox[3] - 0.6 * bbox_height, "Indian Ocean", fontsize=13, ha='center', color='tab:blue', zorder=6, path_effects=[PathEffects.withStroke(linewidth=3, foreground="white")])
ax.text(bbox[0] + 0.85 * bbox_width, bbox[3] - 0.3 * bbox_height, "Pacific Ocean", fontsize=13, ha='center', color='tab:blue', zorder=6, path_effects=[PathEffects.withStroke(linewidth=3, foreground="white")])
# Add red dot with white outline, and the label
ax.text(bbox[0] + 0.33 * bbox_width, bbox[3] - 0.61 * bbox_height, "Jakarta", fontsize=9, ha='center', zorder=6, path_effects=[PathEffects.withStroke(linewidth=3, foreground="white")])
dot_x = bbox[0] + 0.295 * bbox_width
dot_y = bbox[3] - 0.63 * bbox_height
ax.plot(dot_x, dot_y, 'o', markersize=8, markeredgewidth=0.5, color='red', markeredgecolor='white', zorder=7)
# Show the plot
plt.show()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment