Coverage for pyguymer3/geo/add_annotation.py: 3%
29 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-08 18:47 +0000
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-08 18:47 +0000
1#!/usr/bin/env python3
3# Define function ...
4def add_annotation(
5 ax,
6 locLon,
7 locLat,
8 annotation,
9 /,
10 *,
11 arrowprops = None,
12 bbox = None,
13 color = "black",
14 debug = __debug__,
15 fontsize = 8,
16 horizontalalignment = "center",
17 prefix = ".",
18 txtLat = None,
19 txtLon = None,
20 txtOffsetX = None,
21 txtOffsetY = None,
22 verticalalignment = "center",
23 zorder = 3.0,
24):
25 """Add an annotation to a Cartopy axis.
27 Parameters
28 ----------
29 ax : cartopy.mpl.geoaxes.GeoAxesSubplot
30 the axis to add the annotation to
31 locLon : float
32 the longitude of the annotation location (in degrees)
33 locLat : float
34 the latitude of the annotation location (in degrees)
35 annotation : str
36 the annotation text
37 arrowprops : dict, optional
38 the properties for the arrow connecting the annotation text to the
39 annotation location
40 bbox : dict, optional
41 the properties for the bounding box around the annotation text
42 color : str, optional
43 the colour of the annotation text
44 debug : bool, optional
45 print debug messages
46 fontsize : int, optional
47 the font size of the annotation text
48 horizontal alignment : str, optional
49 the vertical alignment of the annotation text
50 prefix : str, optional
51 change the name of the output debugging CSVs
52 txtLon : float, optional
53 the longitude of the annotation text, which implies an arrow to connect
54 it to the annotated location (in degrees)
55 txtLat : float, optional
56 the latitude of the annotation text, which implies an arrow to connect
57 it to the annotated location (in degrees)
58 txtOffsetX : int or float, optional
59 the horizontal offset of the annotation text, which implies an arrow to
60 connect it to the annotated location (in points)
61 txtOffsetY : int or float, optional
62 the vertical offset of the annotation text, which implies an arrow to
63 connect it to the annotated location (in points)
64 vertical alignment : str, optional
65 the vertical alignment of the annotation text
66 zorder : float, optional
67 the zorder of the annotation text (the default value has been chosen to
68 to match the value that it ends up being if the annotation text is not
69 drawn with the zorder keyword specified -- obtained by manual inspection
70 on 16/Jun/2024)
72 Notes
73 -----
74 Copyright 2017 Thomas Guymer [1]_
76 References
77 ----------
78 .. [1] PyGuymer3, https://github.com/Guymer/PyGuymer3
79 """
81 # Import special modules ...
82 try:
83 import matplotlib
84 matplotlib.rcParams.update(
85 {
86 "backend" : "Agg", # NOTE: See https://matplotlib.org/stable/gallery/user_interfaces/canvasagg.html
87 "figure.dpi" : 300,
88 "figure.figsize" : (9.6, 7.2), # NOTE: See https://github.com/Guymer/misc/blob/main/README.md#matplotlib-figure-sizes
89 "font.size" : 8,
90 }
91 )
92 except:
93 raise Exception("\"matplotlib\" is not installed; run \"pip install --user matplotlib\"") from None
94 try:
95 import shapely
96 import shapely.geometry
97 except:
98 raise Exception("\"shapely\" is not installed; run \"pip install --user Shapely\"") from None
100 # Import sub-functions ...
101 from .check import check
103 # **************************************************************************
105 # Create Point(s) ...
106 point1loc = shapely.geometry.point.Point(locLon, locLat)
107 if debug:
108 check(point1loc, prefix = prefix)
109 if txtLon is not None and txtLat is not None:
110 point1txt = shapely.geometry.point.Point(txtLon, txtLat)
112 # Project the Point(s) into the axis' units ...
113 point2loc = ax.projection.project_geometry(point1loc)
114 if debug:
115 check(point2loc, prefix = prefix)
116 if txtLon is not None and txtLat is not None:
117 point2txt = ax.projection.project_geometry(point1txt)
119 # Annotate the axis ...
120 if txtLon is None and txtLat is None and txtOffsetX is None and txtOffsetY is None:
121 ax.annotate(
122 annotation,
123 (point2loc.coords[0][0], point2loc.coords[0][1]),
124 bbox = bbox,
125 color = color,
126 fontsize = fontsize,
127 horizontalalignment = horizontalalignment,
128 verticalalignment = verticalalignment,
129 zorder = zorder,
130 )
131 elif txtOffsetX is not None and txtOffsetY is not None:
132 ax.annotate(
133 annotation,
134 (point2loc.coords[0][0], point2loc.coords[0][1]),
135 arrowprops = arrowprops,
136 bbox = bbox,
137 color = color,
138 fontsize = fontsize,
139 horizontalalignment = horizontalalignment,
140 textcoords = "offset points",
141 verticalalignment = verticalalignment,
142 xytext = (txtOffsetX, txtOffsetY),
143 zorder = zorder,
144 )
145 elif txtLon is not None and txtLat is not None:
146 ax.annotate(
147 annotation,
148 (point2loc.coords[0][0], point2loc.coords[0][1]),
149 arrowprops = arrowprops,
150 bbox = bbox,
151 color = color,
152 fontsize = fontsize,
153 horizontalalignment = horizontalalignment,
154 verticalalignment = verticalalignment,
155 xytext = (point2txt.coords[0][0], point2txt.coords[0][1]),
156 zorder = zorder,
157 )
158 else:
159 raise Exception("there is a bizarre combination of \"txtLon\", \"txtLat\", \"txtOffsetX\" and \"txtOffsetY\"") from None