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
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-16 08:31 +0000
1#!/usr/bin/env python3
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.
22 This function adds coastline boundaries to a Cartopy axis. The resolution of
23 the boundaries and *what* the boundaries delineate, are both configurable.
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)
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/>`_ :
64 * *levels*; and
65 * *resolution*.
67 There are six levels to choose from:
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).
76 There are five resolutions to choose from:
78 * crude ("c");
79 * low ("l");
80 * intermediate ("i");
81 * high ("h"); and
82 * full ("f").
84 Copyright 2017 Thomas Guymer [1]_
86 References
87 ----------
88 .. [1] PyGuymer3, https://github.com/Guymer/PyGuymer3
89 """
91 # Import standard modules ...
92 import os
93 import pathlib
94 import urllib
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
119 # Import sub-functions ...
120 from .extract_polys import extract_polys
122 # **************************************************************************
124 # Check inputs ...
125 if levels is None:
126 levels = (1, 5, 6,)
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
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
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))
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 )