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
« 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_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, 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 urllib
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
123 # Import sub-functions ...
124 from .extract_polys import extract_polys
126 # **************************************************************************
128 # Check inputs ...
129 if levels is None:
130 levels = [1, 6]
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
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
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))
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 )