-
Notifications
You must be signed in to change notification settings - Fork 26
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
Vector support in bridge.Transform.from_points #676
base: main
Are you sure you want to change the base?
Changes from 14 commits
bd8c868
7fc4309
9b6ca8e
f6823ac
a05ff4b
82155a0
0f1ab54
58dc72a
5167fdb
6037a1e
48a57f3
1f1e994
baaaee1
2ad49bb
6e836e7
cf85be7
225cfd2
bc1ba77
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -750,6 +750,51 @@ def to_cartesian( | |
return np.vstack(xyz).T if stacked else np.array(xyz) | ||
|
||
|
||
def vectors_to_cartesian( | ||
lons_lats: (ArrayLike, ArrayLike), | ||
vectors_uvw: (ArrayLike, ArrayLike, ArrayLike), | ||
) -> (np.ndarray, np.ndarray, np.ndarray): | ||
"""Convert geographic-oriented vectors to cartesian ``xyz`` points. | ||
|
||
Parameters | ||
---------- | ||
lons_lats : pair of ArrayLike | ||
The longitude + latitude locations of the vectors (in degrees). | ||
Both shapes must be the same. | ||
vectors_uvw : triple of ArrayLike | ||
The eastward, northward and upward vector components. | ||
All shapes must be the same as in ``lons_lats``. | ||
|
||
Returns | ||
------- | ||
(ndarray, ndarray, ndarray) | ||
The corresponding ``xyz`` cartesian vector components. | ||
|
||
Notes | ||
----- | ||
.. versionadded:: 0.?.? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume this gets gets fixed once the PR is completed/approved. |
||
|
||
""" | ||
# TODO @pp-mo: Argument checking ??? | ||
lons, lats = (np.deg2rad(arr) for arr in lons_lats) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It may be outside the scope of this PR, but it occurs to me that the documentation of these functions could be a bit clearer on the fact that, unless a crs is specified, units are expected to be in degrees. Though I suppose this may be implicit in the default crs being There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I felt that all the existing docs of Transform methods are missing any statement of this, so it just didn't seem an appropriate place to mention it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, not true I see : in the dosctring here for from_points, it does say that |
||
u, v, w = vectors_uvw | ||
|
||
coslons = np.cos(lons) | ||
sinlons = np.sin(lons) | ||
coslats = np.cos(lats) | ||
sinlats = np.sin(lats) | ||
# N.B. the term signs are slightly unexpected here, because the viewing coord system | ||
# is not quite what you may expect : The "Y" axis goes to the right, and the "X" | ||
# axis points out of the screen, towards the viewer. | ||
z_factor = w * coslats - v * sinlats | ||
wy = coslons * u + sinlons * z_factor | ||
wx = -sinlons * u + coslons * z_factor | ||
wz = v * coslats + w * sinlats | ||
# NOTE: for better efficiency, we *COULD* handle the w=0 special case separately. | ||
|
||
return wx, wy, wz | ||
|
||
|
||
def to_lonlat( | ||
xyz: ArrayLike, | ||
radians: bool | None = False, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
#!/usr/bin/env python3 | ||
# Copyright (c) 2021, GeoVista Contributors. | ||
# | ||
# This file is part of GeoVista and is distributed under the 3-Clause BSD license. | ||
# See the LICENSE file in the package root directory for licensing details. | ||
|
||
""" | ||
3D Wind Arrows | ||
-------------- | ||
|
||
This example demonstrates how to display wind vectors. | ||
|
||
📋 Summary | ||
^^^^^^^^^^ | ||
|
||
The data source provides X and Y arrays containing plain longitude and | ||
latitude values, which is the most common case. | ||
|
||
The wind information is provided in three separate field arrays, 'U, V and W' | ||
-- i.e. eastward, northward and vertical components. | ||
These values are coded for each location (X, Y), measured relative to the longitude, | ||
latitude and vertical directions at each point. | ||
|
||
There is no connectivity provided, so each location has a vector and is independent of | ||
the others. Hence we use the `geovista.Transform.from_points` function, passing the | ||
winds to the `vectors` keyword. | ||
|
||
Initially, we can just show the horizontal winds, as this easier to interprt | ||
We scale up the 'W' values, since vertical winds are typically much smaller than | ||
horizontal. Coastlines and a base layer are also added for ease of viewing. | ||
|
||
""" # noqa: D205,D212,D400 | ||
import geovista as gv | ||
from geovista.pantry.data import lfric_winds | ||
|
||
# get sample data | ||
sample = lfric_winds() | ||
|
||
# Create a mesh of individual points, adding vectors at each point | ||
mesh = gv.Transform.from_points( | ||
sample.lons, | ||
sample.lats, | ||
vectors = (sample.u, sample.v), | ||
) | ||
|
||
# Create a new mesh containing arrow glyphs, from the mesh vectors | ||
# NOTE: the 'mesh vectors' are a specific concept in PyVista | ||
# NOTE ALSO: the 'arrows' property is effectively a convenience for calling | ||
# :meth:'~pyvista.Dataset.glyph' | ||
arrows = mesh.glyph(factor=0.02) # Note the overall scaling factor | ||
|
||
# Add the arrows to a plotter with other aspects, and display | ||
plotter = gv.GeoPlotter() | ||
plotter.add_base_layer(radius=0.99) | ||
plotter.add_mesh(arrows, cmap="inferno") | ||
plotter.add_coastlines() | ||
plotter.add_graticule() | ||
plotter.add_axes() | ||
|
||
# Set up a nice default view | ||
plotter.camera.zoom(1.3) # adjusts the camera view angle | ||
selected_view = [ | ||
(-4.0688208659033505, -2.5462610064466777, -2.859304866708606), | ||
(-0.0037798285484313965, 0.005168497562408447, -0.0031679868698120117), | ||
(-0.523382090763761, -0.11174892277533728, 0.8447386372874786) | ||
] | ||
plotter.camera_position = selected_view | ||
plotter.show() | ||
print(plotter.camera_position) | ||
|
||
|
||
# %% | ||
# Repeat, but now add in the 'W' vertical components. | ||
# These need scaling up, since vertical winds are typically much smaller than | ||
# horizontal. | ||
# We also use one colour, and apply a vertical offset to prevent downward-going arrows | ||
# from disappearing into the surface. | ||
|
||
# Create a mesh of individual points, adding vectors at each point | ||
mesh = gv.Transform.from_points( | ||
sample.lons, | ||
sample.lats, | ||
# supply all three components | ||
vectors = (sample.u, sample.v, sample.w), | ||
# apply additional scaling and a vertical offset | ||
vectors_z_scaling=1500., | ||
radius=1.1 | ||
) | ||
arrows = mesh.glyph(factor=0.02) | ||
|
||
plotter = gv.GeoPlotter() | ||
plotter.add_base_layer() | ||
plotter.add_mesh(arrows, color="darkred") | ||
plotter.add_coastlines() | ||
plotter.add_graticule() | ||
plotter.add_axes() | ||
|
||
plotter.camera.zoom(1.3) | ||
selected_view = [ | ||
(0.9892890077409511, -2.9925812011503097, 1.008438916341214), | ||
(0.456372154072792, 0.10044567821980169, 0.7120015972700701), | ||
(-0.39009517660643345, 0.021012607195809205, 0.9205347486799345) | ||
] | ||
plotter.camera_position = selected_view | ||
plotter.show() | ||
print(plotter.camera_position) | ||
|
||
|
||
# %% | ||
# Finally, it sometimes makes more sense to display all arrows the same size so that | ||
# direction is always readable. | ||
# Here's an example of constant size, but still colored by windspeed. | ||
|
||
# Create a mesh of individual points, adding vectors at each point | ||
mesh = gv.Transform.from_points( | ||
sample.lons, | ||
sample.lats, | ||
# supply all three components | ||
vectors = (sample.u, sample.v), | ||
) | ||
# Note: the overall size scale is now different, too | ||
arrows = mesh.glyph(factor=0.1, scale=False) | ||
|
||
plotter = gv.GeoPlotter() | ||
plotter.add_base_layer() | ||
plotter.add_mesh(arrows, cmap="magma") | ||
plotter.add_coastlines() | ||
plotter.add_graticule() | ||
plotter.add_axes() | ||
|
||
plotter.camera.zoom(1.3) # adjusts the camera view angle | ||
selected_view = [ | ||
(-4.0688208659033505, -2.5462610064466777, -2.859304866708606), | ||
(-0.0037798285484313965, 0.005168497562408447, -0.0031679868698120117), | ||
(-0.523382090763761, -0.11174892277533728, 0.8447386372874786) | ||
] | ||
plotter.camera_position = selected_view | ||
plotter.show() | ||
print(plotter.camera_position) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that there is a case for
vectors_to_cartesian
to be used in other transforms likefrom_unstructured
, would it be worth considering if this code (from line 659 to here) would be suitable to exist insidevectors_to_cartesian
? It doesn't seem to me like this code is specific tofrom_1d
and I don't believe there's anything here which wouldn't work with the other transforms from what I've seen of them.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting...
I'm actually doubting right now whether we may not remove this keyword altogether, since it's so obvious how the user would do it.
I already removed an overall scaling operation for the same reason, and also because it can be done in the 'glyph' call.