-
Notifications
You must be signed in to change notification settings - Fork 2
Coastlines (outlines) based on MPAS mesh connectivity info #57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
5f26169
Mark cells culled by mosaic with -2
andrewdnolan 83bf9a6
Add public API for coastline drawing
andrewdnolan 604d929
Fix typing
andrewdnolan e88470e
Do not snap coastlines comprised of a single vetex
andrewdnolan ae1c3cf
Make supported proj iteration a shared fixture
andrewdnolan bce7a6e
Improve handling of kwarg arugments
andrewdnolan cfb57ee
Update docstrings and documentation
andrewdnolan b2bba15
Apply suggestions from CoPilot's review
andrewdnolan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ Plotting | |
| :toctree: generated/ | ||
|
|
||
| polypcolor | ||
| coastlines | ||
| contour | ||
| contourf | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,16 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from mosaic import datasets | ||
| from mosaic.coastlines import coastlines | ||
| from mosaic.contour import contour, contourf | ||
| from mosaic.descriptor import Descriptor | ||
| from mosaic.polypcolor import polypcolor | ||
|
|
||
| __all__ = ["Descriptor", "contour", "contourf", "datasets", "polypcolor"] | ||
| __all__ = [ | ||
| "Descriptor", | ||
| "coastlines", | ||
| "contour", | ||
| "contourf", | ||
| "datasets", | ||
| "polypcolor", | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import warnings | ||
|
|
||
| import cartopy.feature as cfeature | ||
| import numpy as np | ||
| import shapely | ||
| from cartopy.mpl.geoaxes import GeoAxes | ||
|
|
||
| from mosaic.contour import ContourGraph, MPASContourGenerator | ||
| from mosaic.descriptor import Descriptor | ||
|
|
||
|
|
||
| def coastlines( | ||
| ax: GeoAxes, descriptor: Descriptor, color: str = "black", **kwargs | ||
| ) -> None: | ||
| """ | ||
| Plot coastal **outlines** using the connectivity info from the MPAS mesh | ||
|
|
||
| Parameters | ||
| ---------- | ||
| ax : cartopy.mpl.geoaxes.GeoAxes | ||
| The cartopy axes to add the coastlines to. | ||
| descriptor : Descriptor | ||
| The descriptor containing the projection and dataset information. | ||
| **kwargs | ||
| Additional keyword arguments. See | ||
| :py:class:`matplotlib.collections.Collection` for supported options. | ||
| """ | ||
|
|
||
| if not isinstance(ax, GeoAxes): | ||
| msg = ( | ||
| "Must provide a `cartopy.mpl.geoaxes` instance for " | ||
| "`mosaic.coastlines` to work. " | ||
| ) | ||
| raise TypeError(msg) | ||
|
|
||
| if "edgecolor" in kwargs and "ec" in kwargs: | ||
| msg = "Cannot specify both 'edgecolor' and 'ec'." | ||
| raise TypeError(msg) | ||
| if "edgecolor" in kwargs: | ||
| color = kwargs.pop("edgecolor") | ||
| elif "ec" in kwargs: | ||
| color = kwargs.pop("ec") | ||
|
|
||
| if "facecolor" in kwargs and "fc" in kwargs: | ||
| msg = "Cannot specify both 'facecolor' and 'fc'." | ||
| raise TypeError(msg) | ||
| if "facecolor" in kwargs or "fc" in kwargs: | ||
| warnings.warn( | ||
| "'facecolor (fc)' is not supported for `mosaic.coastlines` " | ||
| "and will be ignored.", | ||
| stacklevel=2, | ||
| ) | ||
| kwargs.pop("facecolor", None) | ||
| kwargs.pop("fc", None) | ||
|
|
||
| kwargs["edgecolor"] = color | ||
| kwargs["facecolor"] = "none" | ||
|
|
||
| generator = MPASCoastlineGenerator(descriptor) | ||
| coastlines = generator.create_coastlines() | ||
|
|
||
| geometries = shapely.GeometryCollection( | ||
| [shapely.LineString(cl) for cl in coastlines] | ||
| ) | ||
|
|
||
| feature = cfeature.ShapelyFeature(geometries, descriptor.projection) | ||
| ax.add_feature(feature, **kwargs) | ||
|
|
||
|
|
||
| class MPASCoastlineGenerator(MPASContourGenerator): | ||
| def __init__(self, descriptor: Descriptor): | ||
| # pass a dummy field to the parent class | ||
| super().__init__(descriptor, descriptor.ds.nCells) | ||
|
|
||
| self.domain = descriptor.projection.domain | ||
| self.boundary = descriptor.projection.boundary | ||
|
|
||
| shapely.prepare(self.domain) | ||
|
|
||
| def create_coastlines(self) -> list[np.ndarray]: | ||
| graph = self._create_coastline_graph() | ||
| lines = self._split_and_order_graph(graph) | ||
|
|
||
| return self._snap_lines_to_boundary(lines) | ||
|
|
||
| def _create_coastline_graph(self) -> ContourGraph: | ||
| edge_mask = (self.ds.cellsOnEdge == -1).any("TWO").values | ||
|
|
||
| vertex_1 = self.ds.verticesOnEdge[edge_mask].isel(TWO=0).values | ||
| vertex_2 = self.ds.verticesOnEdge[edge_mask].isel(TWO=1).values | ||
|
|
||
| return ContourGraph(vertex_1, vertex_2) | ||
|
|
||
| def _snap_lines_to_boundary( | ||
| self, lines: list[np.ndarray] | ||
| ) -> list[np.ndarray]: | ||
| def snap(point: np.ndarray): | ||
| return self.boundary.interpolate( | ||
| self.boundary.project(shapely.Point(point)) | ||
| ) | ||
|
|
||
| complete_lines = [] | ||
| for line in lines: | ||
| # only need to snap lines that are not already closed loops | ||
| if np.array_equal(line[0], line[-1]): | ||
| complete_lines.append(line) | ||
| continue | ||
|
|
||
| contain_mask = shapely.contains_xy(self.domain, *line.T) | ||
| if not contain_mask.any(): | ||
| continue | ||
|
|
||
| clipped = line[contain_mask] | ||
|
|
||
| if len(clipped) == 1: | ||
| # if only one point inside domain, | ||
| # all snapped points will lie along the same line | ||
| continue | ||
|
|
||
| # TODO: For coastlines with end points outside domain it would be | ||
| # better to cut at boundary intersection point rather than snapping | ||
| p0, p1 = snap(clipped[0]), snap(clipped[-1]) | ||
|
|
||
| complete_lines.append( | ||
| np.concatenate([np.array(p0.xy).T, clipped, np.array(p1.xy).T]) | ||
| ) | ||
|
|
||
| return complete_lines |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import inspect | ||
|
|
||
| import cartopy.crs as ccrs | ||
| import pytest | ||
|
|
||
| import mosaic | ||
|
|
||
| # get the names, as strings, of unsupported projections for spherical meshes | ||
| unsupported = [ | ||
| p.__name__ for p in mosaic.descriptor.UNSUPPORTED_SPHERICAL_PROJECTIONS | ||
| ] | ||
|
|
||
| PROJECTIONS = [ | ||
| obj() | ||
| for name, obj in inspect.getmembers(ccrs) | ||
| if inspect.isclass(obj) | ||
| and issubclass(obj, ccrs.Projection) | ||
| and not name.startswith("_") # skip internal classes | ||
| and obj is not ccrs.Projection # skip base Projection class | ||
| and name not in unsupported # skip unsupported projections | ||
| ] | ||
|
|
||
|
|
||
| def id_func(projection): | ||
| return type(projection).__name__ | ||
|
|
||
|
|
||
| @pytest.fixture(scope="module", params=PROJECTIONS, ids=id_func) | ||
| def iterate_supported_projections(request): | ||
| def _factory(ds): | ||
| return mosaic.Descriptor(ds, request.param, ccrs.Geodetic()) | ||
|
|
||
| return _factory |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.