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

40 statements  

« prev     ^ index     » next       coverage.py v7.10.3, created at 2025-08-16 08:31 +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, 5, 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 pathlib 

94 import urllib 

95 

96 # Import special modules ... 

97 try: 

98 import cartopy 

99 cartopy.config.update( 

100 { 

101 "cache_dir" : pathlib.PosixPath("~/.local/share/cartopy_cache").expanduser(), 

102 } 

103 ) 

104 except: 

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

106 try: 

107 import matplotlib 

108 matplotlib.rcParams.update( 

109 { 

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

111 "figure.dpi" : 300, 

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

113 "font.size" : 8, 

114 } 

115 ) 

116 except: 

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

118 

119 # Import sub-functions ... 

120 from .extract_polys import extract_polys 

121 

122 # ************************************************************************** 

123 

124 # Check inputs ... 

125 if levels is None: 

126 levels = (1, 5, 6,) 

127 

128 # Loop over levels ... 

129 for level in levels: 

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

131 try: 

132 sfile = cartopy.io.shapereader.gshhs( 

133 level = level, 

134 scale = resolution, 

135 ) 

136 except urllib.error.HTTPError: 

137 if debug: 

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

139 continue 

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

141 if debug: 

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

143 continue 

144 

145 # Loop over records ... 

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

147 # Skip bad records ... 

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

149 continue 

150 

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

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

153 polys = [] 

154 for poly in extract_polys( 

155 record.geometry, 

156 onlyValid = onlyValid, 

157 repair = repair, 

158 ): 

159 if fov is None: 

160 polys.append(poly) 

161 continue 

162 if poly.disjoint(fov): 

163 continue 

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

165 

166 # Plot geometry ... 

167 ax.add_geometries( 

168 polys, 

169 cartopy.crs.PlateCarree(), 

170 edgecolor = edgecolor, 

171 facecolor = facecolor, 

172 linestyle = linestyle, 

173 linewidth = linewidth, 

174 zorder = zorder, 

175 )