diff --git a/podpac/core/data/datasource.py b/podpac/core/data/datasource.py index b03d77ae..35fc32ea 100644 --- a/podpac/core/data/datasource.py +++ b/podpac/core/data/datasource.py @@ -449,6 +449,30 @@ def find_coordinates(self): return [self.coordinates] + def get_bounds(self, crs="default"): + """Get the full available coordinate bounds for the Node. + + Arguments + --------- + crs : str + Desired CRS for the bounds. Use 'source' to use the native source crs. + If not specified, podpac.settings["DEFAULT_CRS"] is used. Optional. + + Returns + ------- + bounds : dict + Bounds for each dimension. Keys are dimension names and values are tuples (min, max). + crs : str + The crs for the bounds. + """ + + if crs == "default": + crs = settings["DEFAULT_CRS"] + elif crs == "source": + crs = self.coordinates.crs + + return self.coordinates.transform(crs).bounds, crs + @common_doc(COMMON_DATA_DOC) def get_data(self, coordinates, coordinates_index): """{get_data} diff --git a/podpac/core/data/ogr.py b/podpac/core/data/ogr.py index 33e33591..963c5949 100644 --- a/podpac/core/data/ogr.py +++ b/podpac/core/data/ogr.py @@ -53,7 +53,7 @@ def extents(self): def get_source_data(self, bounds={}): """ - Raise a user-friendly exception when calling get_source_data for this node. + Not available for OGR nodes. Arguments --------- @@ -71,6 +71,21 @@ def get_source_data(self, bounds={}): "The source data is a vector-based shapefile without a native resolution." ) + def find_coordinates(self): + """ + Not available for OGR nodes. + + raises + ------ + coord_list : list + list of available coordinates (Coordinates objects) + """ + + raise AttributeError( + "Cannot get available coordinates for OGR datasources. " + "The source data is a vector-based shapefile without native coordinates." + ) + @common_doc(COMMON_NODE_DOC) def _eval(self, coordinates, output=None, _selector=None): if "lat" not in coordinates.udims or "lon" not in coordinates.udims: diff --git a/podpac/core/data/test/test_datasource.py b/podpac/core/data/test/test_datasource.py index 34cce015..01d90bba 100644 --- a/podpac/core/data/test/test_datasource.py +++ b/podpac/core/data/test/test_datasource.py @@ -582,6 +582,36 @@ def test_get_source_data_with_bounds(self): data = node.get_source_data({"lon": (1.5, 4.5)}) np.testing.assert_array_equal(data, node.source[:, 2:]) + def test_get_bounds(self): + node = podpac.data.Array( + source=np.ones((3, 4)), + coordinates=podpac.Coordinates([range(3), range(4)], ["lat", "lon"], crs="EPSG:2193"), + ) + + with podpac.settings: + podpac.settings["DEFAULT_CRS"] = "EPSG:4326" + + # specify crs + bounds, crs = node.get_bounds(crs="EPSG:3857") + assert bounds == { + "lat": (-13291827.558247399, -13291815.707967814), + "lon": (9231489.26794932, 9231497.142754894), + } + assert crs == "EPSG:3857" + + # native/source crs + bounds, crs = node.get_bounds(crs="source") + assert bounds == {"lat": (0, 2), "lon": (0, 3)} + assert crs == "EPSG:2193" + + # default crs + bounds, crs = node.get_bounds() + assert bounds == { + "lat": (-75.81365382984804, -75.81362774074242), + "lon": (82.92787904584206, 82.92794978642414), + } + assert crs == "EPSG:4326" + class TestDataSourceWithMultipleOutputs(object): def test_evaluate_no_overlap_with_output_extract_output(self): diff --git a/podpac/core/interpolation/interpolation.py b/podpac/core/interpolation/interpolation.py index 313813b9..239317e3 100644 --- a/podpac/core/interpolation/interpolation.py +++ b/podpac/core/interpolation/interpolation.py @@ -12,7 +12,7 @@ from podpac.core.settings import settings from podpac.core.node import Node -from podpac.core.utils import NodeTrait, common_doc +from podpac.core.utils import NodeTrait, common_doc, cached_property from podpac.core.units import UnitsDataArray from podpac.core.coordinates import merge_dims, Coordinates from podpac.core.interpolation.interpolation_manager import InterpolationManager, InterpolationTrait @@ -273,3 +273,22 @@ def find_coordinates(self): """ return self.source.find_coordinates() + + def get_bounds(self, crs="default"): + """Get the full available coordinate bounds for the Node. + + Arguments + --------- + crs : str + Desired CRS for the bounds. Use 'source' to use the native source crs. + If not specified, the default CRS in the podpac settings is used. Optional. + + Returns + ------- + bounds : dict + Bounds for each dimension. Keys are dimension names and values are tuples (hi, lo). + crs : str + The crs for the bounds. + """ + + return self.source.get_bounds(crs=crs) diff --git a/podpac/core/interpolation/test/test_interpolation.py b/podpac/core/interpolation/test/test_interpolation.py index e49ee340..ff533411 100644 --- a/podpac/core/interpolation/test/test_interpolation.py +++ b/podpac/core/interpolation/test/test_interpolation.py @@ -81,6 +81,9 @@ def test_compositor_chain(self): np.testing.assert_array_equal(o.data, np.concatenate([self.s1.source, self.s2.source], axis=0)) + def test_get_bounds(self): + assert self.interp.get_bounds() == self.s1.get_bounds() + class TestInterpolationBehavior(object): def test_linear_1D_issue411and413(self): diff --git a/podpac/core/node.py b/podpac/core/node.py index 48f78016..1d58576d 100644 --- a/podpac/core/node.py +++ b/podpac/core/node.py @@ -353,6 +353,37 @@ def find_coordinates(self): raise NotImplementedError + def get_bounds(self, crs="default"): + """Get the full available coordinate bounds for the Node. + + Arguments + --------- + crs : str + Desired CRS for the bounds. + If not specified, the default CRS in the podpac settings is used. Optional. + + Returns + ------- + bounds : dict + Bounds for each dimension. Keys are dimension names and values are tuples (min, max). + crs : str + The CRS for the bounds. + """ + + if crs == "default": + crs = podpac.settings["DEFAULT_CRS"] + + bounds = {} + for coords in self.find_coordinates(): + ct = coords.transform(crs) + for dim, (lo, hi) in ct.bounds.items(): + if dim not in bounds: + bounds[dim] = (lo, hi) + else: + bounds[dim] = (min(lo, bounds[dim][0]), max(hi, bounds[dim][1])) + + return bounds, crs + @common_doc(COMMON_DOC) def create_output_array(self, coords, data=np.nan, attrs=None, **kwargs): """ diff --git a/podpac/core/test/test_node.py b/podpac/core/test/test_node.py index 53ba9159..03853927 100644 --- a/podpac/core/test/test_node.py +++ b/podpac/core/test/test_node.py @@ -212,6 +212,32 @@ def test_find_coordinates_not_implemented(self): with pytest.raises(NotImplementedError): node.find_coordinates() + def test_get_bounds(self): + class MyNode(Node): + def find_coordinates(self): + return [ + podpac.Coordinates([[0, 1, 2], [0, 10, 20]], dims=["lat", "lon"], crs="EPSG:2193"), + podpac.Coordinates([[3, 4], [30, 40]], dims=["lat", "lon"], crs="EPSG:2193"), + ] + + node = MyNode() + + with podpac.settings: + podpac.settings["DEFAULT_CRS"] = "EPSG:4326" + + # specify crs + bounds, crs = node.get_bounds(crs="EPSG:2193") + assert bounds == {"lat": (0, 4), "lon": (0, 40)} + assert crs == "EPSG:2193" + + # default crs + bounds, crs = node.get_bounds() + assert bounds == { + "lat": (-75.81397534013118, -75.81362774074242), + "lon": (82.92787904584206, 82.9280189659297), + } + assert crs == "EPSG:4326" + class TestCreateOutputArray(object): def test_create_output_array_default(self):