Polygons
Unstructured Grids are often used in the Dynamical Cores of climate models because of their ability to represent complex geometries effectively. However, the way unstructured grids are represented makes it difficult to produce quick visualizations.
Previously, we have discussed that data variables are typically mapped to the elements that compose unstructured grids (nodes, edges, and faces). When data is mapped to the faces, each face is shaded with the value of the data variable. We can treat these faces as Polygons on a plane for visualization.
This notebook showcases UXarray’s Polygon Visualization methods.
Setup
import uxarray as ux
fig_size = 400
plot_kwargs = {"backend": "matplotlib", "aspect": 2, "fig_size": fig_size}
file_dir = "../../meshfiles/"
grid_filename_mpas = file_dir + "oQU480.grid.nc"
data_filename_mpas = file_dir + "oQU480.data.nc"
uxds = ux.open_dataset(grid_filename_mpas, data_filename_mpas)
Vector Polygon Plots
The UxDataArray.plot.polygons()
method produces a Vector Polygon plot, where each face is represent as a polygon.
uxds["bottomDepth"].plot.polygons(**plot_kwargs)
WARNING:param.main: fig_size option not found for polygons plot with matplotlib; similar options include: []
Interactive Example
In the plot below, take some time to zoom and pan around the plot to observe some of the defining characteristics of a Vector Polygon Plot:
Hovering over a polygon (face) displays the value mapped to it, which is the same as the original value stored in our
UxDataArray
There is no drop in quality when zooming in
Each polygon is bounded by edges that are drawn as lines
uxds["bottomDepth"].plot.polygons(
backend="bokeh", width=1000, height=500, tools=["hover"]
)
Data Fidelity
The defining characteristic of vector polygon plots is that there is no loss of data fidelity (i.e. the accuracy and/or quality) when zooming in.
Zooming into a region of our grid highlights this, which can be seen in the plot below.
uxds["bottomDepth"].plot.polygons(xlim=(-20, 0), ylim=(-5, 5), **plot_kwargs)
WARNING:param.main: fig_size option not found for polygons plot with matplotlib; similar options include: []
Rasterized Polygon Plots
The UxDataArray.plot.rasterize(method='polygon')
function produces a Rasterized Polygon plot. Instead of rendering each polygon exactly, they are rasterized into cells using Datashader internally.
uxds["bottomDepth"].plot.rasterize(method="polygon", **plot_kwargs)
/tmp/ipykernel_2689/2640074272.py:1: DeprecationWarning: ``UxDataArray.plot.rasterize()`` will be deprecated in a future release. Please use ``UxDataArray.plot.polygons(rasterize=True)`` or ``UxDataArray.plot.points(rasterize=True)``
uxds["bottomDepth"].plot.rasterize(method="polygon", **plot_kwargs)
Interactive Example
In the plot below, take some time to zoom and pan around to observe some of the defining characters of a Raster Polygon Plot and compare it to the Vector Polygon Plot
Zooming in exposes the fact that each polygon is approximately rendered, with jagged edges around the boundaries of each cell
Hovering over a polygon (face) still displays the original data values, even though the boundaries are not explicit like the vector example
uxds["bottomDepth"].plot.rasterize(
method="polygon", backend="bokeh", width=1000, height=500, tools=["hover"]
)
/tmp/ipykernel_2689/2588369353.py:1: DeprecationWarning: ``UxDataArray.plot.rasterize()`` will be deprecated in a future release. Please use ``UxDataArray.plot.polygons(rasterize=True)`` or ``UxDataArray.plot.points(rasterize=True)``
uxds["bottomDepth"].plot.rasterize(
Data Fidelity
If we restrict our viewing area to the same constraints as the zoomed in vector plot, we begin to see the effects of rasterization. Since rasterization approximates each polygon by binning the data values onto a fixed-size grid, our polygons appear jagged and no longer have clear boundaries like the vector plot.
This means the effective resolution is much lower when zoomed in.
uxds["bottomDepth"].plot.rasterize(
method="polygon", xlim=(-20, 0), ylim=(-5, 5), **plot_kwargs
)
/tmp/ipykernel_2689/1268510653.py:1: DeprecationWarning: ``UxDataArray.plot.rasterize()`` will be deprecated in a future release. Please use ``UxDataArray.plot.polygons(rasterize=True)`` or ``UxDataArray.plot.points(rasterize=True)``
uxds["bottomDepth"].plot.rasterize(
However, a higher level of data fidelity can be achieved by specifying a pixel_ratio
, which controls the size of the binning used for rasterization.
A high pixel ratio increases the number of bins by making each bin smaller, leading to a higher effective resolution and a better approximation of the vector polygon plot
A low pixel ratio decreases the number of bins by making each bin larger, leading to a lower effective resolution with jagged and coarse approximations of each polygon
(
uxds["bottomDepth"].plot.rasterize(
xlim=(-20, 0),
ylim=(-5, 5),
pixel_ratio=0.5,
title="0.5 (low) Pixel Ratio",
**plot_kwargs
)
+ uxds["bottomDepth"].plot.rasterize(
xlim=(-20, 0),
ylim=(-5, 5),
pixel_ratio=8.0,
title="8.0 (high) Pixel Ratio",
**plot_kwargs
)
).opts(fig_size=fig_size / 1.5).cols(1)
/tmp/ipykernel_2689/4070835465.py:2: DeprecationWarning: ``UxDataArray.plot.rasterize()`` will be deprecated in a future release. Please use ``UxDataArray.plot.polygons(rasterize=True)`` or ``UxDataArray.plot.points(rasterize=True)``
uxds["bottomDepth"].plot.rasterize(
/tmp/ipykernel_2689/4070835465.py:9: DeprecationWarning: ``UxDataArray.plot.rasterize()`` will be deprecated in a future release. Please use ``UxDataArray.plot.polygons(rasterize=True)`` or ``UxDataArray.plot.points(rasterize=True)``
+ uxds["bottomDepth"].plot.rasterize(
Polygons Around Antimeridian
When attempting to visualize unstructured meshes that reside on a sphere, it’s necessary to consider the behavior of geometric elements near or on the Antimeridian. Elements that exist on or cross the antimeridian need to be corrected to properly visualize them. UXarray uses the antimeridian package to split faces along the antimeridian. More details can be found in their documentation.
Opting to Include Antimeridian Polygons
To include and correct antimeridian polygons, we can set exclude_antimeridian=False
.
The following plots are zoomed in to a section along the antimeridian. We can see that our plot is split exactly across the antimeridian.
(
uxds["bottomDepth"].plot.polygons(
xlim=(-185, -175),
ylim=(-5, 5),
exclude_antimeridian=False,
title="Left of Antimeridian",
**plot_kwargs
)
+ uxds["bottomDepth"].plot.polygons(
xlim=(175, 185),
ylim=(-5, 5),
exclude_antimeridian=False,
title="Right of Antimeridian",
**plot_kwargs
)
).opts(fig_size=fig_size / 1.5).cols(1)
WARNING:param.main: exclude_antimeridian option not found for polygons plot with matplotlib; similar options include: []
WARNING:param.main: fig_size option not found for polygons plot with matplotlib; similar options include: []
WARNING:param.main: exclude_antimeridian option not found for polygons plot with matplotlib; similar options include: []
WARNING:param.main: fig_size option not found for polygons plot with matplotlib; similar options include: []
Opting to Exclude Antimeridian Polygons
To exclude antimeridian polygons, we can set exclude_antimeridian=True
.
In the following plots, we can see that the polygons that were corrected above are now missing. Since there is a relatively high overhead involved in detecting and correcting each antimeridian polygon, this option opts to exclude them from the overall visualuzation workflow, which leads to a significant performance increase at higher resolutions.
(
uxds["bottomDepth"].plot.polygons(
xlim=(-185, -175),
ylim=(-5, 5),
exclude_antimeridian=True,
title="Left of Antimeridian",
**plot_kwargs
)
+ uxds["bottomDepth"].plot.polygons(
xlim=(175, 185),
ylim=(-5, 5),
exclude_antimeridian=True,
title="Right of Antimeridian",
**plot_kwargs
)
).opts(fig_size=fig_size / 1.5).cols(1)
WARNING:param.main: exclude_antimeridian option not found for polygons plot with matplotlib; similar options include: []
WARNING:param.main: fig_size option not found for polygons plot with matplotlib; similar options include: []
WARNING:param.main: exclude_antimeridian option not found for polygons plot with matplotlib; similar options include: []
WARNING:param.main: fig_size option not found for polygons plot with matplotlib; similar options include: []
Projection Support
As of the most recent UXarray release, geographic projections and transformations are not supported by UXarray’s polygon plotting functionality. However, we can still utilize UXarray paired with HoloViz tools to generate these types of plots with a bit of extra effort.
import cartopy.crs as ccrs
import holoviews as hv
import hvplot.pandas
hv.extension("matplotlib")
Conversion to a SpatialPandas GeoDataFrame
In order to support visualization with the popular HoloViz stack of libraries (hvPlot, HoloViews, Datashader, etc.), UXarray provides methods for converting Grid and UxDataArray objects into a SpatialPandas GeoDataFrame, which can be used for visualizing the polygons that represent each grid, in addition to data variables.
gdf = uxds["bottomDepth"].to_geodataframe()
gdf
geometry | bottomDepth | |
---|---|---|
0 | Polygon([[-173.4220428466797, 28.4104290008544... | 4973.000000 |
1 | Polygon([[3.516157388687134, -28.4104290008544... | 2639.000000 |
2 | Polygon([[79.46817016601562, -25.8366222381591... | 4001.012148 |
3 | Polygon([[-28.531827926635742, 25.836622238159... | 5403.000000 |
4 | Polygon([[-137.4220428466797, -28.410429000854... | 4345.000000 |
... | ... | ... |
1739 | Polygon([[-99.18515014648438, -53.847068786621... | 4597.000000 |
1740 | Polygon([[-102.95294189453125, -50.05697250366... | 3945.000000 |
1741 | Polygon([[-102.95294189453125, -52.62263107299... | 4431.000000 |
1742 | Polygon([[-171.18515014648438, -53.84706878662... | 5197.000000 |
1743 | Polygon([[-178.7207489013672, -53.847068786621... | 5499.990273 |
1744 rows × 2 columns
See also:
For more infromation about UXarray and the conversion to a GeoDataFrame, please refer to the UXarray Usage Examples
Using hvPlot
Once we have a spatialpandas.GeoDataFrame
, we can use the .hvplot
accessor to generate Polygon plots with projections and geographic features. Below are a few examples:
projection = ccrs.Robinson()
gdf.hvplot.polygons(
rasterize=True,
c="bottomDepth",
cmap="Blues",
projection=projection,
height=600,
title="Robinson Projection",
)
projection = ccrs.Robinson(central_longitude=-180)
gdf.hvplot.polygons(
rasterize=True,
c="bottomDepth",
cmap="Blues",
projection=projection,
height=600,
title="Robinson Projection (centered about Antimeridian)",
)
import geoviews.feature as gf
projection = ccrs.Robinson(central_longitude=-180)
gdf.hvplot.polygons(
rasterize=True,
c="bottomDepth",
cmap="Blues",
projection=projection,
height=600,
title="Robinson Projection (Continents and Borders)",
) * gf.coastline() * gf.borders()
/home/runner/miniconda3/envs/unstructured-grid-viz-cookbook-dev/lib/python3.10/site-packages/cartopy/io/__init__.py:241: DownloadWarning: Downloading: https://naturalearth.s3.amazonaws.com/110m_cultural/ne_110m_admin_0_boundary_lines_land.zip
warnings.warn(f'Downloading: {url}', DownloadWarning)
projection = ccrs.Orthographic(central_latitude=90)
gdf.hvplot.polygons(
rasterize=True,
c="bottomDepth",
cmap="Blues",
projection=projection,
height=400,
title="Orthographic Projection over North Pole",
) * gf.coastline() * gf.borders()
Takeaways
Polygon plotting is UXarray’s flagship visualization method, which provides a high level of data fidelity by utilizing the unstructured grid’s connectivity information.
When should I use vector or raster plots?
Rasterization should be used for most applications, especially when working with moderate to large grids. Also consider using an appropriate pixel ratio value to get a higher effective resolution
Vector plots should be used when working with a small grid, typically under 10,000 elements because of the high overhead needed to render each polygon directly
When should I set exclude_antimeridian=False
?
It is suggested to almost always keep
exclude_antimeridian=True
(the default option) since it leads to a very large performance increaseFor small grids or when excluding the polygons would lead to artifacts, it is recommend to set it to False