Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 33 additions & 10 deletions ogc/podpac.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import ogc
import podpac
from podpac.core.coordinates import Coordinates
from podpac.core.coordinates import Coordinates, union, merge_dims
import traitlets as tl
from typing import List
from matplotlib import pyplot as plt
Expand Down Expand Up @@ -80,27 +80,50 @@ def time_instances(self) -> List[str]:
def get_coordinates(self) -> Coordinates | None:
"""Retrieve the coordinates from the node.

This enforces that all coordinates implement unstacked latitude and longitude dimensions.

Returns
-------
Coordinates | None
Coordinates from the node or None if not found.

Raises
------
ValueError
If any coordinates do not have unstacked latitude and longitude dimensions.
"""
if self.node is None:
return None

output_coordinates_list = []
shared_dims = ["lat", "lon"]
coordinates_list = self.node.find_coordinates()
dimension_set = set()
coordinates = None

for coords in coordinates_list:
dimension_set.update(coords.udims)
if coordinates is None or len(coords.udims) > len(coordinates.udims):
coordinates = coords
if len(coordinates_list) == 0:
return None

# Verify all coordinates define unstacked latitude and longitude
if not all(dim in coordinates.dims for coordinates in coordinates_list for dim in shared_dims):
raise ValueError("Invalid dimensions for coordinate retrieval.")

# Only use one source for shared coordinates
shared_coordinates_source = coordinates_list[0]
for dim in shared_dims:
output_coordinates_list.append(
Coordinates(
[shared_coordinates_source[dim].coordinates],
dims=[dim],
crs=shared_coordinates_source.crs,
)
)

if coordinates is not None and not all(dim in coordinates.udims for dim in dimension_set):
raise ValueError("Not all node coordinate dimensions contained in the layer coordinates.")
# Use all sources for remaining dimensions, removing duplicates and enforce matching CRS
remaining_coords = union(
[coords.drop(shared_dims).transform(shared_coordinates_source.crs) for coords in coordinates_list]
)
output_coordinates_list.append(remaining_coords)

return coordinates
return merge_dims(output_coordinates_list)

def get_units(self) -> str | None:
"""Retrieve the units from the node.
Expand Down
103 changes: 103 additions & 0 deletions ogc/test/test_podpac.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import pytest
import podpac
from ogc import podpac as pogc

LAT = [0.0, 1.0, 2.0]
LON = [10.0, 20.0, 30.0]
TIME = ["2020-01-01", "2020-01-02"]
TIME2 = ["2020-01-02", "2020-01-03"]
ALT = [1.0, 2.0]
CRS_LATLON = "EPSG:4326"
CRS_OTHER = "EPSG:2193"


class MockNode(podpac.Node):
"""A mock node for layer coordinate testing."""

def __init__(self, coordinates_list):
self._coordinates_list = coordinates_list

def find_coordinates(self):
return self._coordinates_list


class TestLayerGetCoordinates:
def test_node_is_none(self):
"""Return value should be None when underlying node is None."""
layer = pogc.Layer()
assert layer.get_coordinates() is None

def test_no_coordinates_found(self):
"""Return value should be None when no underlying coordinates exist."""
layer = pogc.Layer(node=MockNode([]))
assert layer.get_coordinates() is None

def test_unstacked_lat_lon(self):
"""Test that unstacked latitude and longitude is valid."""
source = pogc.Coordinates([LAT, LON, TIME], dims=["lat", "lon", "time"], crs=CRS_LATLON)
layer = pogc.Layer(node=MockNode([source]))

coordinates = layer.get_coordinates()

assert coordinates is not None
assert set(coordinates.dims) == {"lat", "lon", "time"}

def test_stacked_lat_lon(self):
"""Test that stacked latitude and longitude is invalid."""
source = pogc.Coordinates([[LAT, LON], TIME], dims=["lat_lon", "time"], crs=CRS_LATLON)
layer = pogc.Layer(node=MockNode([source]))

with pytest.raises(ValueError, match="Invalid dimensions for coordinate retrieval."):
layer.get_coordinates()

def test_no_spatial_dims(self):
"""Test that base spatial dimensions are required."""
source1 = pogc.Coordinates([TIME], dims=["time"], crs=CRS_LATLON)
source2 = pogc.Coordinates([TIME2], dims=["time"], crs=CRS_LATLON)
layer = pogc.Layer(node=MockNode([source1, source2]))

with pytest.raises(ValueError, match="Invalid dimensions for coordinate retrieval."):
layer.get_coordinates()

def test_mixed_stacked_and_unstacked_sources(self):
"""Test that stacked and unstacked coordinates cannot be mixed together."""
stacked_source = pogc.Coordinates([[LAT, LON], TIME], dims=["lat_lon", "time"], crs=CRS_LATLON)
unstacked_source = pogc.Coordinates([LAT, LON, TIME2], dims=["lat", "lon", "time"], crs=CRS_LATLON)
layer = pogc.Layer(node=MockNode([stacked_source, unstacked_source]))

with pytest.raises(ValueError, match="Invalid dimensions for coordinate retrieval."):
layer.get_coordinates()

def test_crs_mismatch_is_reprojected(self):
"""Test that CRS mismatch is transformed to the coordinates which spatial dimensions were pulled from."""
source1 = pogc.Coordinates([LAT, LON, TIME], dims=["lat", "lon", "time"], crs=CRS_LATLON)
source2 = pogc.Coordinates([LAT, LON, TIME2], dims=["lat", "lon", "time"], crs=CRS_OTHER)
layer = pogc.Layer(node=MockNode([source1, source2]))

coordinates = layer.get_coordinates()

assert coordinates is not None
assert coordinates.crs == CRS_LATLON

def test_duplicate_dims_are_deduped(self):
"""Test that duplicate values are removed."""
source1 = pogc.Coordinates([LAT, LON, TIME], dims=["lat", "lon", "time"], crs=CRS_LATLON)
source2 = pogc.Coordinates([LAT, LON, TIME], dims=["lat", "lon", "time"], crs=CRS_LATLON)
layer = pogc.Layer(node=MockNode([source1, source2]))

coordinates = layer.get_coordinates()

assert coordinates is not None
assert coordinates["time"].size == len(TIME)

def test_dims_are_combined(self):
"""Test that dimension values are combined."""
source1 = pogc.Coordinates([LAT, LON, TIME], dims=["lat", "lon", "time"], crs=CRS_LATLON)
source2 = pogc.Coordinates([LAT, LON, TIME2], dims=["lat", "lon", "time"], crs=CRS_LATLON)
source3 = pogc.Coordinates([LAT, LON], dims=["lat", "lon"], crs=CRS_LATLON)
layer = pogc.Layer(node=MockNode([source1, source2, source3]))

coordinates = layer.get_coordinates()

assert coordinates is not None
assert coordinates["time"].size == 3
Loading