-
Notifications
You must be signed in to change notification settings - Fork 32
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
Point In Face #1056
base: main
Are you sure you want to change the base?
Point In Face #1056
Changes from 53 commits
75b88e8
998aa99
7d7be67
e38ca24
92bab4a
354f821
5fc59aa
c4a4979
3b09973
f7cb51c
22e84d5
4043c8b
95a0dea
5dbed72
c1eae89
586c8ad
b33326d
0acaa2b
807ce07
b4eba94
fa6fd8c
0092258
1f6f51b
5e0477b
acce252
d88d69f
a379a1a
fefe1e7
cc9f943
73a1be4
8392e2c
6fea25e
200f4bb
e86e81c
a6b0dcc
36ef299
dc153dc
c5ed371
d7318de
ab18797
47843d8
845cca6
88a602f
c28772a
e055327
46cecc4
5dba109
d297a9e
bff6cd5
67ea7b2
ce354fd
7eeef4e
98a676d
66fba3d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,7 @@ | |
from uxarray.grid.connectivity import _populate_face_edge_connectivity, _build_edge_face_connectivity, \ | ||
_build_edge_node_connectivity, _build_face_face_connectivity, _populate_face_face_connectivity | ||
|
||
from uxarray.grid.coordinates import _populate_node_latlon, _lonlat_rad_to_xyz | ||
from uxarray.grid.coordinates import _populate_node_latlon, _lonlat_rad_to_xyz, _xyz_to_lonlat_rad_scalar | ||
|
||
from uxarray.constants import INT_FILL_VALUE, ERROR_TOLERANCE | ||
|
||
|
@@ -44,13 +44,10 @@ | |
|
||
shp_filename = current_path / "meshfiles" / "shp" / "grid_fire.shp" | ||
|
||
|
||
|
||
grid_CSne30 = ux.open_grid(gridfile_CSne30) | ||
grid_RLL1deg = ux.open_grid(gridfile_RLL1deg) | ||
grid_RLL10deg_CSne4 = ux.open_grid(gridfile_RLL10deg_CSne4) | ||
|
||
|
||
mpas_filepath = current_path / "meshfiles" / "mpas" / "QU" / "mesh.QU.1920km.151026.nc" | ||
exodus_filepath = current_path / "meshfiles" / "exodus" / "outCSne8" / "outCSne8.g" | ||
ugrid_filepath_01 = current_path / "meshfiles" / "ugrid" / "outCSne30" / "outCSne30.ug" | ||
|
@@ -89,6 +86,7 @@ def test_grid_validate(): | |
grid_mpas = ux.open_grid(gridfile_mpas) | ||
assert grid_mpas.validate() | ||
|
||
|
||
def test_grid_with_holes(): | ||
"""Test _holes_in_mesh function.""" | ||
grid_without_holes = ux.open_grid(gridfile_mpas) | ||
|
@@ -97,6 +95,7 @@ def test_grid_with_holes(): | |
assert grid_with_holes.partial_sphere_coverage | ||
assert grid_without_holes.global_sphere_coverage | ||
|
||
|
||
def test_grid_encode_as(): | ||
"""Reads a ugrid file and encodes it as `xarray.Dataset` in various types.""" | ||
grid_CSne30.encode_as("UGRID") | ||
|
@@ -107,6 +106,7 @@ def test_grid_encode_as(): | |
grid_RLL1deg.encode_as("Exodus") | ||
grid_RLL10deg_CSne4.encode_as("Exodus") | ||
|
||
|
||
def test_grid_init_verts(): | ||
"""Create a uxarray grid from multiple face vertices with duplicate nodes and saves a ugrid file.""" | ||
cart_x = [ | ||
|
@@ -131,7 +131,7 @@ def test_grid_init_verts(): | |
[5, 4, 7, 6], # back face | ||
[4, 0, 3, 7], # left face | ||
[3, 2, 6, 7], # top face | ||
[4, 5, 1, 0] # bottom face | ||
[4, 5, 1, 0] # bottom face | ||
] | ||
|
||
faces_coords = [] | ||
|
@@ -163,6 +163,7 @@ def test_grid_init_verts(): | |
assert vgrid.n_node == 6 | ||
vgrid.encode_as("UGRID") | ||
|
||
|
||
def test_grid_init_verts_different_input_datatype(): | ||
"""Create a uxarray grid from multiple face vertices with different datatypes (ndarray, list, tuple) and saves a ugrid file.""" | ||
faces_verts_ndarray = np.array([ | ||
|
@@ -176,8 +177,8 @@ def test_grid_init_verts_different_input_datatype(): | |
vgrid.encode_as("UGRID") | ||
|
||
faces_verts_list = [[[150, 10], [160, 20], [150, 30], [135, 30], [125, 20], [135, 10]], | ||
[[125, 20], [135, 30], [125, 60], [110, 60], [100, 30], [105, 20]], | ||
[[95, 10], [105, 20], [100, 30], [85, 30], [75, 20], [85, 10]]] | ||
[[125, 20], [135, 30], [125, 60], [110, 60], [100, 30], [105, 20]], | ||
[[95, 10], [105, 20], [100, 30], [85, 30], [75, 20], [85, 10]]] | ||
vgrid = ux.open_grid(faces_verts_list, latlon=True) | ||
assert vgrid.n_face == 3 | ||
assert vgrid.n_node == 14 | ||
|
@@ -195,6 +196,7 @@ def test_grid_init_verts_different_input_datatype(): | |
assert vgrid.validate() | ||
vgrid.encode_as("UGRID") | ||
|
||
|
||
def test_grid_init_verts_fill_values(): | ||
faces_verts_filled_values = [[[150, 10], [160, 20], [150, 30], | ||
[135, 30], [125, 20], [135, 10]], | ||
|
@@ -208,6 +210,7 @@ def test_grid_init_verts_fill_values(): | |
assert vgrid.n_face == 3 | ||
assert vgrid.n_node == 12 | ||
|
||
|
||
def test_grid_properties(): | ||
"""Tests to see if accessing variables through set properties is equal to using the dict.""" | ||
xr.testing.assert_equal(grid_CSne30.node_lon, grid_CSne30._ds["node_lon"]) | ||
|
@@ -234,27 +237,32 @@ def test_grid_properties(): | |
assert n_faces == grid_geoflow.n_face | ||
assert n_face_nodes == grid_geoflow.n_max_face_nodes | ||
|
||
|
||
def test_read_shpfile(): | ||
"""Reads a shape file and write ugrid file.""" | ||
with pytest.raises(ValueError): | ||
grid_shp = ux.open_grid(shp_filename) | ||
|
||
|
||
def test_read_scrip(): | ||
"""Reads a scrip file.""" | ||
grid_CSne8 = ux.open_grid(gridfile_CSne8) # tests from scrip | ||
|
||
|
||
def test_operators_eq(): | ||
"""Test Equals ('==') operator.""" | ||
grid_CSne30_01 = ux.open_grid(gridfile_CSne30) | ||
grid_CSne30_02 = ux.open_grid(gridfile_CSne30) | ||
assert grid_CSne30_01 == grid_CSne30_02 | ||
|
||
|
||
def test_operators_ne(): | ||
"""Test Not Equals ('!=') operator.""" | ||
grid_CSne30_01 = ux.open_grid(gridfile_CSne30) | ||
grid_RLL1deg = ux.open_grid(gridfile_RLL1deg) | ||
assert grid_CSne30_01 != grid_RLL1deg | ||
|
||
|
||
def test_face_areas_calculate_total_face_area_triangle(): | ||
"""Create a uxarray grid from vertices and saves an exodus file.""" | ||
verts = [[[0.57735027, -5.77350269e-01, -0.57735027], | ||
|
@@ -275,11 +283,13 @@ def test_face_areas_calculate_total_face_area_triangle(): | |
quadrature_rule="triangular", order=4) | ||
nt.assert_almost_equal(area_triangular, constants.TRI_AREA, decimal=1) | ||
|
||
|
||
def test_face_areas_calculate_total_face_area_file(): | ||
"""Create a uxarray grid from vertices and saves an exodus file.""" | ||
area = ux.open_grid(gridfile_CSne30).calculate_total_face_area() | ||
nt.assert_almost_equal(area, constants.MESH30_AREA, decimal=3) | ||
|
||
|
||
def test_face_areas_calculate_total_face_area_sphere(): | ||
"""Computes the total face area of an MPAS mesh that lies on a unit sphere, with an expected total face area of 4pi.""" | ||
mpas_grid_path = current_path / 'meshfiles' / "mpas" / "QU" / 'mesh.QU.1920km.151026.nc' | ||
|
@@ -293,11 +303,13 @@ def test_face_areas_calculate_total_face_area_sphere(): | |
nt.assert_almost_equal(primal_face_area, constants.UNIT_SPHERE_AREA, decimal=3) | ||
nt.assert_almost_equal(dual_face_area, constants.UNIT_SPHERE_AREA, decimal=3) | ||
|
||
|
||
def test_face_areas_compute_face_areas_geoflow_small(): | ||
"""Checks if the GeoFlow Small can generate a face areas output.""" | ||
grid_geoflow = ux.open_grid(gridfile_geoflow) | ||
grid_geoflow.compute_face_areas() | ||
|
||
|
||
def test_face_areas_verts_calc_area(): | ||
faces_verts_ndarray = np.array([ | ||
np.array([[150, 10, 0], [160, 20, 0], [150, 30, 0], [135, 30, 0], | ||
|
@@ -311,11 +323,12 @@ def test_face_areas_verts_calc_area(): | |
face_verts_areas = verts_grid.face_areas | ||
nt.assert_almost_equal(face_verts_areas.sum(), constants.FACE_VERTS_AREA, decimal=3) | ||
|
||
|
||
def test_populate_coordinates_populate_cartesian_xyz_coord(): | ||
# The following testcases are generated through the matlab cart2sph/sph2cart functions | ||
lon_deg = [ | ||
45.0001052295749, 45.0001052295749, 360 - 45.0001052295749, | ||
360 - 45.0001052295749 | ||
360 - 45.0001052295749 | ||
] | ||
lat_deg = [ | ||
35.2655522903022, -35.2655522903022, 35.2655522903022, | ||
|
@@ -342,10 +355,11 @@ def test_populate_coordinates_populate_cartesian_xyz_coord(): | |
nt.assert_almost_equal(vgrid.node_y.values[i], cart_y[i], decimal=12) | ||
nt.assert_almost_equal(vgrid.node_z.values[i], cart_z[i], decimal=12) | ||
|
||
|
||
def test_populate_coordinates_populate_lonlat_coord(): | ||
lon_deg = [ | ||
45.0001052295749, 45.0001052295749, 360 - 45.0001052295749, | ||
360 - 45.0001052295749 | ||
360 - 45.0001052295749 | ||
] | ||
lat_deg = [ | ||
35.2655522903022, -35.2655522903022, 35.2655522903022, | ||
|
@@ -374,8 +388,8 @@ def test_populate_coordinates_populate_lonlat_coord(): | |
|
||
|
||
def _revert_edges_conn_to_face_nodes_conn(edge_nodes_connectivity: np.ndarray, | ||
face_edges_connectivity: np.ndarray, | ||
original_face_nodes_connectivity: np.ndarray): | ||
face_edges_connectivity: np.ndarray, | ||
original_face_nodes_connectivity: np.ndarray): | ||
"""Utilize the edge_nodes_connectivity and face_edges_connectivity to | ||
generate the res_face_nodes_connectivity in the counter-clockwise | ||
order. The counter-clockwise order will be enforced by the passed in | ||
|
@@ -440,6 +454,7 @@ def _revert_edges_conn_to_face_nodes_conn(edge_nodes_connectivity: np.ndarray, | |
|
||
return np.array(res_face_nodes_connectivity) | ||
|
||
|
||
def test_connectivity_build_n_nodes_per_face(): | ||
"""Tests the construction of the ``n_nodes_per_face`` variable.""" | ||
grids = [grid_mpas, grid_exodus, grid_ugrid] | ||
|
@@ -457,6 +472,7 @@ def test_connectivity_build_n_nodes_per_face(): | |
expected_nodes_per_face = np.array([6, 3, 4, 6, 6, 4, 4], dtype=int) | ||
nt.assert_equal(grid_from_verts.n_nodes_per_face.values, expected_nodes_per_face) | ||
|
||
|
||
def test_connectivity_edge_nodes_euler(): | ||
"""Verifies that (``n_edge``) follows euler's formula.""" | ||
grid_paths = [exodus_filepath, ugrid_filepath_01, ugrid_filepath_02, ugrid_filepath_03] | ||
|
@@ -470,6 +486,7 @@ def test_connectivity_edge_nodes_euler(): | |
|
||
assert (n_face == n_edge - n_node + 2) | ||
|
||
|
||
def test_connectivity_build_face_edges_connectivity_mpas(): | ||
"""Tests the construction of (``Mesh2_edge_nodes``) on an MPAS grid with known edge nodes.""" | ||
from uxarray.grid.connectivity import _build_edge_node_connectivity | ||
|
@@ -492,6 +509,7 @@ def test_connectivity_build_face_edges_connectivity_mpas(): | |
|
||
assert (n_face == n_edge - n_node + 2) | ||
|
||
|
||
def test_connectivity_build_face_edges_connectivity(): | ||
"""Generates Grid.Mesh2_edge_nodes from Grid.face_node_connectivity.""" | ||
ug_filename_list = [ugrid_filepath_01, ugrid_filepath_02, ugrid_filepath_03] | ||
|
@@ -522,6 +540,7 @@ def test_connectivity_build_face_edges_connectivity(): | |
for i in range(len(reverted_mesh2_edge_nodes)): | ||
assert np.array_equal(reverted_mesh2_edge_nodes[i], original_face_nodes_connectivity[i]) | ||
|
||
|
||
def test_connectivity_build_face_edges_connectivity_fillvalues(): | ||
verts = [f0_deg, f1_deg, f2_deg, f3_deg, f4_deg, f5_deg, f6_deg] | ||
uds = ux.open_grid(verts) | ||
|
@@ -543,6 +562,7 @@ def test_connectivity_build_face_edges_connectivity_fillvalues(): | |
|
||
assert np.array_equal(res_face_nodes_connectivity, uds._ds["face_node_connectivity"].values) | ||
|
||
|
||
def test_connectivity_node_face_connectivity_from_verts(): | ||
"""Test generating Grid.Mesh2_node_faces from array input.""" | ||
face_nodes_conn_lonlat_degree = [[162., 30], [216., 30], [70., 30], | ||
|
@@ -573,6 +593,7 @@ def test_connectivity_node_face_connectivity_from_verts(): | |
|
||
assert np.array_equal(vgrid.node_face_connectivity.values, expected) | ||
|
||
|
||
def test_connectivity_node_face_connectivity_from_files(): | ||
"""Test generating Grid.Mesh2_node_faces from file input.""" | ||
grid_paths = [exodus_filepath, ugrid_filepath_01, ugrid_filepath_02, ugrid_filepath_03] | ||
|
@@ -593,12 +614,14 @@ def test_connectivity_node_face_connectivity_from_files(): | |
|
||
for i in range(grid_ux.n_node): | ||
face_index_from_sparse_matrix = grid_ux.node_face_connectivity.values[i] | ||
valid_face_index_from_sparse_matrix = face_index_from_sparse_matrix[face_index_from_sparse_matrix != grid_ux.node_face_connectivity.attrs["_FillValue"]] | ||
valid_face_index_from_sparse_matrix = face_index_from_sparse_matrix[ | ||
face_index_from_sparse_matrix != grid_ux.node_face_connectivity.attrs["_FillValue"]] | ||
valid_face_index_from_sparse_matrix.sort() | ||
face_index_from_dict = node_face_connectivity[i] | ||
face_index_from_dict.sort() | ||
assert np.array_equal(valid_face_index_from_sparse_matrix, face_index_from_dict) | ||
|
||
|
||
def test_connectivity_edge_face_connectivity_mpas(): | ||
"""Tests the construction of ``Mesh2_face_edges`` to the expected results of an MPAS grid.""" | ||
uxgrid = ux.open_grid(mpas_filepath) | ||
|
@@ -611,6 +634,7 @@ def test_connectivity_edge_face_connectivity_mpas(): | |
|
||
nt.assert_array_equal(edge_faces_output, edge_faces_gold) | ||
|
||
|
||
def test_connectivity_edge_face_connectivity_sample(): | ||
"""Tests the construction of ``Mesh2_face_edges`` on an example with one shared edge, and the remaining edges only being part of one face.""" | ||
verts = [[(0.0, -90.0), (180, 0.0), (0.0, 90)], | ||
|
@@ -633,6 +657,7 @@ def test_connectivity_edge_face_connectivity_sample(): | |
assert n_solo == uxgrid.n_edge - n_shared | ||
assert n_invalid == 0 | ||
|
||
|
||
def test_connectivity_face_face_connectivity_construction(): | ||
"""Tests the construction of face-face connectivity.""" | ||
grid = ux.open_grid(mpas_filepath) | ||
|
@@ -645,6 +670,7 @@ def test_connectivity_face_face_connectivity_construction(): | |
|
||
nt.assert_array_equal(face_face_conn_new_sorted, face_face_conn_old_sorted) | ||
|
||
|
||
def test_class_methods_from_dataset(): | ||
# UGRID | ||
xrds = xr.open_dataset(gridfile_ugrid) | ||
|
@@ -663,6 +689,7 @@ def test_class_methods_from_dataset(): | |
xrds = xr.open_dataset(gridfile_scrip) | ||
uxgrid = ux.Grid.from_dataset(xrds) | ||
|
||
|
||
def test_class_methods_from_face_vertices(): | ||
single_face_latlon = [(0.0, 90.0), (-180, 0.0), (0.0, -90)] | ||
uxgrid = ux.Grid.from_face_vertices(single_face_latlon, latlon=True) | ||
|
@@ -673,6 +700,7 @@ def test_class_methods_from_face_vertices(): | |
|
||
single_face_cart = [(0.0,)] | ||
|
||
|
||
def test_latlon_bounds_populate_bounds_GCA_mix(): | ||
gridfile_mpas = current_path / "meshfiles" / "mpas" / "QU" / "oQU480.231010.nc" | ||
face_1 = [[10.0, 60.0], [10.0, 10.0], [50.0, 10.0], [50.0, 60.0]] | ||
|
@@ -691,11 +719,13 @@ def test_latlon_bounds_populate_bounds_GCA_mix(): | |
bounds_xarray = grid.bounds | ||
nt.assert_allclose(bounds_xarray.values, expected_bounds, atol=ERROR_TOLERANCE) | ||
|
||
|
||
def test_latlon_bounds_populate_bounds_MPAS(): | ||
gridfile_mpas = current_path / "meshfiles" / "mpas" / "QU" / "oQU480.231010.nc" | ||
uxgrid = ux.open_grid(gridfile_mpas) | ||
bounds_xarray = uxgrid.bounds | ||
|
||
|
||
def test_dual_mesh_mpas(): | ||
grid = ux.open_grid(gridfile_mpas, use_dual=False) | ||
mpas_dual = ux.open_grid(gridfile_mpas, use_dual=True) | ||
|
@@ -708,11 +738,13 @@ def test_dual_mesh_mpas(): | |
|
||
nt.assert_equal(dual.face_node_connectivity.values, mpas_dual.face_node_connectivity.values) | ||
|
||
|
||
def test_dual_duplicate(): | ||
dataset = ux.open_dataset(gridfile_geoflow, gridfile_geoflow) | ||
with pytest.raises(RuntimeError): | ||
dataset.get_dual() | ||
|
||
|
||
def test_normalize_existing_coordinates_non_norm_initial(): | ||
gridfile_mpas = current_path / "meshfiles" / "mpas" / "QU" / "mesh.QU.1920km.151026.nc" | ||
from uxarray.grid.validation import _check_normalization | ||
|
@@ -726,9 +758,39 @@ def test_normalize_existing_coordinates_non_norm_initial(): | |
uxgrid.normalize_cartesian_coordinates() | ||
assert _check_normalization(uxgrid) | ||
|
||
|
||
def test_normalize_existing_coordinates_norm_initial(): | ||
gridfile_CSne30 = current_path / "meshfiles" / "ugrid" / "outCSne30" / "outCSne30.ug" | ||
from uxarray.grid.validation import _check_normalization | ||
uxgrid = ux.open_grid(gridfile_CSne30) | ||
|
||
assert _check_normalization(uxgrid) | ||
|
||
|
||
def test_number_of_faces_found(): | ||
grid = ux.open_grid(gridfile_mpas) | ||
|
||
# For a face center only one face should be found | ||
point_xyz = np.array([grid.face_x[100].values, grid.face_y[100].values, grid.face_z[100].values]) | ||
|
||
assert len(grid.get_faces_containing_point(point_xyz=point_xyz)) == 1 | ||
|
||
# For an edge two faces should be found | ||
point_xyz = np.array([grid.edge_x[100].values, grid.edge_y[100].values, grid.edge_z[100].values]) | ||
|
||
assert len(grid.get_faces_containing_point(point_xyz=point_xyz)) == 2 | ||
|
||
# For a node three faces should be found | ||
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. This is not always true, a node can have 1, 2, 3 or higher number of faces. For 2, consider a node on the boundary that is the actual node on the face, on both faces. For 4/higher, consider a node that is the intersection of 4 faces or higher. 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. That is true, in general. However this is a MPAS grid. Which is always going to have each node connected to 3 faces. So I think it’s an OK assumption here that it should result in 3 faces being found. 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.
Ok, you can keep it, but if the grid has holes and the node is along the boundary. I think, it'd be good to test these edge cases. |
||
point_xyz = np.array([grid.node_x[100].values, grid.node_y[100].values, grid.node_z[100].values]) | ||
|
||
assert len(grid.get_faces_containing_point(point_xyz=point_xyz)) == 3 | ||
|
||
|
||
def test_whole_grid(): | ||
grid = ux.open_grid(gridfile_mpas) | ||
|
||
# Ensure a face is found on the grid for every face center | ||
for i in range(len(grid.face_x.values)): | ||
point_xyz = np.array([grid.face_x[i].values, grid.face_y[i].values, grid.face_z[i].values]) | ||
|
||
assert len(grid.get_faces_containing_point(point_xyz=point_xyz)) == 1 |
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.
From the GH Actions report, it appears that a single Point in Face query is taking approximately
27.0±0.1ms
Ideally, we want to get this number significantly down.
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.
Did some digging:
Please include the following in a custom setup function:
We don't want this to be included in the timing. I believe much of the 27ms is attributed to that.
This is for a 30km Grid
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.
Also, consider including a single sample point query in the setup, since there is also some overhead with the KD tree construction.
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.
This is the performance I get for a 30km grid after doing the things mentioned above. pretty good for 30km grids
Only issue, is that it doesn't find any faces.
Here is my sample code I used.
Can do
snakeviz pface.prof
to view the profiler.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.
Thanks for looking into this for me! I am trying to implement these changes now. In terms of it not finding any faces, does the mesh have holes in it? There may not be a face at the pole possibly?
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.
The grid is a global MPAS atmosphere grid with no holes.
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.
I may have been on an older version of the code. Just ran it again after pulling and it works. A single face is detected. Going to run a few more tests.
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.
Is it working locally for you? It does for me, but the CI is failing. Not sure why.
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.
It seems like it is working for the poles at least on my end. Haven't tested other points