Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 198 additions & 0 deletions benchmarks/bench_cartopy_coastlines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
"""
Benchmark PyGMT and cartopy when plotting simple global coastlines.
"""

import statistics
import time
from pathlib import Path

import cartopy.crs as ccrs
import cartopy.feature as cfeature
import pygmt

import matplotlib.pyplot as plt # noqa: E402


OUTPUT_DIR = Path("plots/coastlines")
REPEATS = 10
CARTOPY_RESOLUTIONS = ("110m", "50m", "10m")
PYGMT_RESOLUTIONS = ("crude", "low", "intermediate", "high", "full")
LAND_COLOR = "#cccccc"
WATER_COLOR = "#b9d9ea"


def plot_cartopy_global(resolution: str):
"""Create a simple global coastline plot with cartopy."""
fig = plt.figure(figsize=(6, 4), dpi=300)
ax = fig.add_subplot(1, 1, 1, projection=ccrs.Robinson())
ax.set_global()
ax.add_feature(cfeature.LAND, facecolor=LAND_COLOR)
ax.add_feature(cfeature.OCEAN, facecolor=WATER_COLOR)
ax.coastlines(resolution=resolution, linewidth=0.5)
ax.gridlines(color="0.8", linewidth=0.4)
return fig


def plot_cartopy_regional(resolution: str):
"""Create a regional coastline plot with cartopy."""
fig = plt.figure(figsize=(6, 4), dpi=300)
ax = fig.add_subplot(1, 1, 1, projection=ccrs.Mercator())
ax.set_extent([110, 160, -45, -10], crs=ccrs.PlateCarree())
ax.add_feature(cfeature.LAND, facecolor=LAND_COLOR)
ax.add_feature(cfeature.OCEAN, facecolor=WATER_COLOR)
ax.coastlines(resolution=resolution, linewidth=0.5)
ax.gridlines(color="0.8", linewidth=0.4)
return fig


def save_cartopy(fig, output: Path) -> None:
"""Save a cartopy/matplotlib figure and release it."""
fig.savefig(output)
plt.close(fig)


def plot_pygmt_global(resolution: str) -> pygmt.Figure:
"""Create a simple global coastline plot with PyGMT."""
fig = pygmt.Figure()
fig.coast(
region="d",
projection="R6i",
resolution=resolution,
land=LAND_COLOR,
water=WATER_COLOR,
shorelines="1/0.5p,black",
frame="afg",
)
return fig

def plot_pygmt_regional(resolution: str) -> pygmt.Figure:
"""Create a regional coastline plot with PyGMT."""
fig = pygmt.Figure()
fig.coast(
region=[110, 160, -45, -10],
projection="M6i",
resolution=resolution,
land=LAND_COLOR,
water=WATER_COLOR,
shorelines="1/0.5p,black",
frame="afg",
)
return fig


def save_pygmt(fig: pygmt.Figure, output: Path) -> None:
"""Save a PyGMT figure."""
fig.savefig(output)


def benchmark(
name: str,
plot_func,
save_func,
output_dir: Path,
repeats: int,
resolution: str,
) -> tuple[list[float], list[float]]:
"""Time repeated coastline plot creation and figure export runs."""
output_dir.mkdir(parents=True, exist_ok=True)

# Warm up each backend once before recording timings.
fig = plot_func(resolution)
save_func(fig, output_dir / f"{name}_warmup.png")

plot_timings = []
save_timings = []
for run_id in range(repeats):
output = output_dir / f"{name}_{run_id + 1}.png"

start = time.perf_counter()
fig = plot_func(resolution)
plot_timings.append(time.perf_counter() - start)

save_func(fig, output)
save_timings.append(time.perf_counter() - start)

return plot_timings, save_timings


def format_summary(name: str, timings: list[float]) -> str:
"""Format benchmark timing statistics."""
mean = statistics.fmean(timings)
median = statistics.median(timings)
minimum = min(timings)
maximum = max(timings)
return (
f"{name:10s} "
f"mean={mean:.4f}s "
f"median={median:.4f}s "
f"min={minimum:.4f}s "
f"max={maximum:.4f}s"
)


def main() -> None:
"""Run the coastline plotting benchmark."""
print(f"Running {REPEATS} timed run(s) per backend")
print(f"Writing PNG files to {OUTPUT_DIR}")

print("Benchmarking cartopy global...", flush=True)
for resolution in CARTOPY_RESOLUTIONS:
print(f"Resolution: {resolution}")
plot_timings, save_timings = benchmark(
name=f"cartopy_global_{resolution}",
plot_func=plot_cartopy_global,
save_func=save_cartopy,
output_dir=OUTPUT_DIR,
repeats=REPEATS,
resolution=resolution,
)
print(format_summary("cartopy plot", plot_timings))
print(format_summary("cartopy savefig", save_timings))

print("Benchmarking cartopy regional...", flush=True)
for resolution in CARTOPY_RESOLUTIONS[2:]:
print(f"Resolution: {resolution}")
plot_timings, save_timings = benchmark(
name=f"cartopy_regional_{resolution}",
plot_func=plot_cartopy_regional,
save_func=save_cartopy,
output_dir=OUTPUT_DIR,
repeats=REPEATS,
resolution=resolution,
)
print(format_summary("cartopy plot", plot_timings))
print(format_summary("cartopy savefig", save_timings))


print("Benchmarking pygmt global...", flush=True)
for resolution in PYGMT_RESOLUTIONS[0:3]:
print(f"Resolution: {resolution}")
plot_timings, save_timings = benchmark(
name=f"pygmt_global_{resolution}",
plot_func=plot_pygmt_global,
save_func=save_pygmt,
output_dir=OUTPUT_DIR,
repeats=REPEATS,
resolution=resolution,
)
print(format_summary("pygmt plot", plot_timings))
print(format_summary("pygmt savefig", save_timings))

print("Benchmarking pygmt regional...", flush=True)
for resolution in PYGMT_RESOLUTIONS[2:]:
print(f"Resolution: {resolution}")
plot_timings, save_timings = benchmark(
name=f"pygmt_regional_{resolution}",
plot_func=plot_pygmt_regional,
save_func=save_pygmt,
output_dir=OUTPUT_DIR,
repeats=REPEATS,
resolution=resolution,
)
print(format_summary("pygmt plot", plot_timings))
print(format_summary("pygmt savefig", save_timings))



if __name__ == "__main__":
main()