|
| 1 | +from typing import Callable, Optional, Union |
| 2 | + |
1 | 3 | import matplotlib.pyplot as plt |
2 | 4 | import numpy as np |
| 5 | +from matplotlib import patheffects |
3 | 6 | from matplotlib.patches import Polygon |
4 | 7 | from matplotlib.ticker import FuncFormatter, MultipleLocator |
| 8 | +from shapely import LineString |
5 | 9 |
|
6 | 10 | from ..dims.grid import get_affine_mod_to_world |
7 | 11 | from ..epsg28992 import EPSG_28992 |
@@ -269,3 +273,135 @@ def title_inside( |
269 | 273 | bbox=bbox, |
270 | 274 | **kwargs, |
271 | 275 | ) |
| 276 | + |
| 277 | + |
| 278 | +def inset_map( |
| 279 | + ax: plt.Axes, |
| 280 | + extent: Union[tuple[float], list[float]], |
| 281 | + axes_bounds: Union[tuple[float], list[float]] = (0.63, 0.025, 0.35, 0.35), |
| 282 | + anchor: str = "SE", |
| 283 | + provider: Optional[str] = "nlmaps.water", |
| 284 | + add_to_plot: Optional[list[Callable]] = None, |
| 285 | +): |
| 286 | + """Add an inset map to an axes. |
| 287 | +
|
| 288 | + Parameters |
| 289 | + ---------- |
| 290 | + ax : matplotlib.Axes |
| 291 | + The axes to add the inset map to. |
| 292 | + extent : list of 4 floats |
| 293 | + The extent of the inset map. |
| 294 | + axes_bounds : list of 4 floats, optional |
| 295 | + The bounds (left, right, width height) of the inset axes, default |
| 296 | + is [0.63, 0.025, 0.35, 0.35]. This is rescaled according to the extent of |
| 297 | + the inset map. |
| 298 | + anchor : str, optional |
| 299 | + The anchor point of the inset map, default is 'SE'. |
| 300 | + provider : str, optional |
| 301 | + Add a backgroundmap if map provider is passed, default is 'nlmaps.water'. To |
| 302 | + turn off the backgroundmap set provider to None. |
| 303 | + add_to_plot : list of functions, optional |
| 304 | + List of functions to plot on the inset map, default is None. The functions |
| 305 | + must accept an ax argument. Hint: use `functools.partial` to set plot style, |
| 306 | + and pass the partial function to add_to_plot. |
| 307 | +
|
| 308 | + Returns |
| 309 | + ------- |
| 310 | + mapax : matplotlib.Axes |
| 311 | + The inset map axes. |
| 312 | + """ |
| 313 | + mapax = ax.inset_axes(axes_bounds) |
| 314 | + mapax.axis(extent) |
| 315 | + mapax.set_aspect("equal", adjustable="box", anchor=anchor) |
| 316 | + mapax.set_xticks([]) |
| 317 | + mapax.set_yticks([]) |
| 318 | + mapax.set_xlabel("") |
| 319 | + mapax.set_ylabel("") |
| 320 | + |
| 321 | + if provider: |
| 322 | + add_background_map(mapax, map_provider=provider, attribution=False) |
| 323 | + |
| 324 | + if add_to_plot: |
| 325 | + for fplot in add_to_plot: |
| 326 | + fplot(ax=mapax) |
| 327 | + |
| 328 | + return mapax |
| 329 | + |
| 330 | + |
| 331 | +def add_xsec_line_and_labels( |
| 332 | + line: Union[list, LineString], |
| 333 | + ax: plt.Axes, |
| 334 | + mapax: plt.Axes, |
| 335 | + x_offset: float = 0.0, |
| 336 | + y_offset: float = 0.0, |
| 337 | + label: str = "A", |
| 338 | + **kwargs, |
| 339 | +): |
| 340 | + """Add a cross-section line to an overview map and label the start and end points. |
| 341 | +
|
| 342 | + Parameters |
| 343 | + ---------- |
| 344 | + line : list or shapely LineString |
| 345 | + The line to plot. |
| 346 | + ax : matplotlib.Axes |
| 347 | + The axes to plot the labels on. |
| 348 | + mapax : matplotlib.Axes |
| 349 | + The axes of the overview map to plot the line on. |
| 350 | + x_offset : float, optional |
| 351 | + The x offset of the labels, default is 0.0. |
| 352 | + y_offset : float, optional |
| 353 | + The y offset of the labels, default is 0.0. |
| 354 | + kwargs : dict |
| 355 | + Keyword arguments to pass to the line plot function. |
| 356 | +
|
| 357 | + Raises |
| 358 | + ------ |
| 359 | + ValueError |
| 360 | + If the line is not a list or a shapely LineString. |
| 361 | + """ |
| 362 | + if isinstance(line, list): |
| 363 | + x, y = np.array(line).T |
| 364 | + elif isinstance(line, LineString): |
| 365 | + x, y = line.xy |
| 366 | + else: |
| 367 | + raise ValueError("line should be a list or a shapely LineString") |
| 368 | + mapax.plot(x, y, **kwargs) |
| 369 | + stroke = [patheffects.withStroke(linewidth=2, foreground="w")] |
| 370 | + mapax.text( |
| 371 | + x[0] + x_offset, |
| 372 | + y[0] + y_offset, |
| 373 | + f"{label}", |
| 374 | + fontweight="bold", |
| 375 | + path_effects=stroke, |
| 376 | + fontsize=7, |
| 377 | + ) |
| 378 | + mapax.text( |
| 379 | + x[-1] + x_offset, |
| 380 | + y[-1] + y_offset, |
| 381 | + f"{label}'", |
| 382 | + fontweight="bold", |
| 383 | + path_effects=stroke, |
| 384 | + fontsize=7, |
| 385 | + ) |
| 386 | + ax.text( |
| 387 | + 0.01, |
| 388 | + 0.99, |
| 389 | + f"{label}", |
| 390 | + transform=ax.transAxes, |
| 391 | + path_effects=stroke, |
| 392 | + fontsize=14, |
| 393 | + ha="left", |
| 394 | + va="top", |
| 395 | + fontweight="bold", |
| 396 | + ) |
| 397 | + ax.text( |
| 398 | + 0.99, |
| 399 | + 0.99, |
| 400 | + f"{label}'", |
| 401 | + transform=ax.transAxes, |
| 402 | + path_effects=stroke, |
| 403 | + fontsize=14, |
| 404 | + ha="right", |
| 405 | + va="top", |
| 406 | + fontweight="bold", |
| 407 | + ) |
0 commit comments