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

1#!/usr/bin/env python3 

2 

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. 

26 

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) 

71 

72 Notes 

73 ----- 

74 Copyright 2017 Thomas Guymer [1]_ 

75 

76 References 

77 ---------- 

78 .. [1] PyGuymer3, https://github.com/Guymer/PyGuymer3 

79 """ 

80 

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 

99 

100 # Import sub-functions ... 

101 from .check import check 

102 

103 # ************************************************************************** 

104 

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) 

111 

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) 

118 

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