Coverage for pyguymer3/geo/_add_coastlines.py: 2%

44 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_coastlines( 

5 ax, 

6 /, 

7 *, 

8 debug = __debug__, 

9 edgecolor = "black", 

10 facecolor = "none", 

11 fov = None, 

12 levels = None, 

13 linestyle = "solid", 

14 linewidth = 0.5, 

15 onlyValid = False, 

16 repair = False, 

17 resolution = "i", 

18 zorder = 1.5, 

19): 

20 """Add coastlines to a Cartopy axis. 

21 

22 This function adds coastline boundaries to a Cartopy axis. The resolution of 

23 the boundaries and *what* the boundaries delineate, are both configurable. 

24 

25 Parameters 

26 ---------- 

27 axis : cartopy.mpl.geoaxes.GeoAxesSubplot 

28 the axis 

29 debug : bool, optional 

30 print debug messages 

31 edgecolor : str, optional 

32 the colour of the edges of the coastline Polygons 

33 facecolor : str, optional 

34 the colour of the faces of the coastline Polygons 

35 fov : None or shapely.geometry.polygon.Polygon, optional 

36 clip the plotted shapes to the provided field-of-view to work around 

37 occaisional MatPlotLib or Cartopy plotting errors when shapes much 

38 larger than the field-of-view are plotted 

39 levels : list of int, optional 

40 the levels of the coastline boundaries (if None then default to 

41 ``[1, 6]``) 

42 linestyle : str, optional 

43 the linestyle to draw the coastline boundaries with 

44 linewidth : float, optional 

45 the linewidth to draw the coastline boundaries with 

46 onlyValid : bool, optional 

47 only return valid Polygons (checks for validity can take a while, if 

48 being called often) 

49 repair : bool, optional 

50 attempt to repair invalid Polygons 

51 resolution : str, optional 

52 the resolution of the coastline boundaries 

53 zorder : float, optional 

54 the zorder to draw the coastline boundaries with (the default value has 

55 been chosen to match the value that it ends up being if the coastline 

56 boundaries are not drawn with the zorder keyword specified -- obtained 

57 by manual inspection on 5/Dec/2023) 

58 

59 Notes 

60 ----- 

61 There are two arguments relating to the `Global Self-Consistent Hierarchical 

62 High-Resolution Geography dataset <https://www.ngdc.noaa.gov/mgg/shorelines/>`_ : 

63 

64 * *levels*; and 

65 * *resolution*. 

66 

67 There are six levels to choose from: 

68 

69 * boundary between land and ocean (1); 

70 * boundary between lake and land (2); 

71 * boundary between island-in-lake and lake (3); 

72 * boundary between pond-in-island and island-in-lake (4); 

73 * boundary between Antarctica ice and ocean (5); and 

74 * boundary between Antarctica grounding-line and ocean (6). 

75 

76 There are five resolutions to choose from: 

77 

78 * crude ("c"); 

79 * low ("l"); 

80 * intermediate ("i"); 

81 * high ("h"); and 

82 * full ("f"). 

83 

84 Copyright 2017 Thomas Guymer [1]_ 

85 

86 References 

87 ---------- 

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

89 """ 

90 

91 # Import standard modules ... 

92 import os 

93 import urllib 

94 

95 # Import special modules ... 

96 try: 

97 import cartopy 

98 cartopy.config.update( 

99 { 

100 "cache_dir" : os.path.expanduser("~/.local/share/cartopy_cache"), 

101 } 

102 ) 

103 except: 

104 raise Exception("\"cartopy\" is not installed; run \"pip install --user Cartopy\"") from None 

105 try: 

106 import matplotlib 

107 matplotlib.rcParams.update( 

108 { 

109 "backend" : "Agg", # NOTE: See https://matplotlib.org/stable/gallery/user_interfaces/canvasagg.html 

110 "figure.dpi" : 300, 

111 "figure.figsize" : (9.6, 7.2), # NOTE: See https://github.com/Guymer/misc/blob/main/README.md#matplotlib-figure-sizes 

112 "font.size" : 8, 

113 } 

114 ) 

115 except: 

116 raise Exception("\"matplotlib\" is not installed; run \"pip install --user matplotlib\"") from None 

117 try: 

118 import shapely 

119 import shapely.geometry 

120 except: 

121 raise Exception("\"shapely\" is not installed; run \"pip install --user Shapely\"") from None 

122 

123 # Import sub-functions ... 

124 from .extract_polys import extract_polys 

125 

126 # ************************************************************************** 

127 

128 # Check inputs ... 

129 if levels is None: 

130 levels = [1, 6] 

131 

132 # Loop over levels ... 

133 for level in levels: 

134 # Deduce Shapefile name (catching missing datasets) ... 

135 try: 

136 sfile = cartopy.io.shapereader.gshhs( 

137 level = level, 

138 scale = resolution, 

139 ) 

140 except urllib.error.HTTPError: 

141 if debug: 

142 print("INFO: Skipping (HTTP error).") 

143 continue 

144 if os.path.basename(sfile) != f"GSHHS_{resolution}_L{level:d}.shp": 

145 if debug: 

146 print(f"INFO: Skipping \"{sfile}\" (filename does not match request).") 

147 continue 

148 

149 # Loop over records ... 

150 for record in cartopy.io.shapereader.Reader(sfile).records(): 

151 # Skip bad records ... 

152 if not hasattr(record, "geometry"): 

153 continue 

154 

155 # Create a list of Polygons to plot (taking in to account if the 

156 # user provided a field-of-view to clip them by) ... 

157 polys = [] 

158 for poly in extract_polys( 

159 record.geometry, 

160 onlyValid = onlyValid, 

161 repair = repair, 

162 ): 

163 if fov is None: 

164 polys.append(poly) 

165 continue 

166 if poly.disjoint(fov): 

167 continue 

168 polys.append(poly.intersection(fov)) 

169 

170 # Plot geometry ... 

171 ax.add_geometries( 

172 polys, 

173 cartopy.crs.PlateCarree(), 

174 edgecolor = edgecolor, 

175 facecolor = facecolor, 

176 linestyle = linestyle, 

177 linewidth = linewidth, 

178 zorder = zorder, 

179 )