diff --git a/demo_new.ipynb b/demo_new.ipynb new file mode 100644 index 000000000..7c0314323 --- /dev/null +++ b/demo_new.ipynb @@ -0,0 +1,1063 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "22d314d2-0ecd-40a9-b21b-2e499cb7a71b", + "metadata": {}, + "source": [ + "How to run:\n", + "\n", + "```bash\n", + "uv run --with jupyterlab --with jupyter-collaboration jupyter lab --IdentityProvider.token=\"\" demo_new.ipynb\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "1868e927-0e50-40b9-b9fe-d79813e1c528", + "metadata": {}, + "source": [ + "# Working with `earthaccess` results the new way\n", + "\n", + "**Purpose**: Simplify and enhance user interaction with results of `earthaccess.search_data` or `.search_datasets`. Currently, users need expertise with various tools to leverage results lists and cannot trace provenance (i.e. search parameters), motivating this change.\n", + "\n", + "**Outcome**: User can more easily visualize, reproduce, convert between formats, and understand the results.\n", + "\n", + "**Process**: Create a new `Results` class with **a single responsibility -- describing results, including how we got them**. Enable easy conversion to other formats (geodataframes, disk format e.g. `results.save(...)`/`Results.load()`, ...), accessing the provenance of the results (e.g. original query parameters, time executed, ...), easy visualization (`results.explore()`), ...?\n", + "\n", + "For this demo, we've implemented basic provenance, conversion to geodataframe, and pretty display in Jupyter Notebooks." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "f637c41d-b702-4363-b2aa-cf565b2736d0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + }, + { + "data": { + "text/plain": [ + "'0.17.1.dev28+g9c4e96cc6.d20260415'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "import earthaccess\n", + "\n", + "earthaccess.__version__" + ] + }, + { + "cell_type": "markdown", + "id": "3120af28-ee4a-4914-b7af-a16d62b03d1a", + "metadata": {}, + "source": [ + "## Search granules" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "00ddc84b-996a-4fe8-b712-510161790f9a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
KeyValue
Length10
PreviewATL06_20181014034354_02370106_007_01.h5
ATL06_20181014035224_02370107_007_01.h5
ATL06_20181014154641_02450101_007_01.h5
and 7 more...
Query parameters{'short_name': 'ATL06', 'bounding_box': '-10.0,20.0,10.0,50.0', 'temporal': ['1999-02-01T00:00:00Z,2019-03-31T23:59:59Z']}
Query parameter optionsdefaultdict(, {})
\n" + ], + "text/plain": [ + "Results" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "results = earthaccess.search_data(\n", + " short_name=\"ATL06\",\n", + " bounding_box=(-10, 20, 10, 50),\n", + " temporal=(\"1999-02\", \"2019-03\"),\n", + " count=10,\n", + ")\n", + "results" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "19993b0e-76e0-484d-a007-91953d256634", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Length: 10\n", + "Preview: [\n", + " ATL06_20181014034354_02370106_007_01.h5\n", + " ATL06_20181014035224_02370107_007_01.h5\n", + " ATL06_20181014154641_02450101_007_01.h5\n", + " and 7 more...\n", + "]\n", + "Query parameters:\n", + " {'bounding_box': '-10.0,20.0,10.0,50.0',\n", + " 'short_name': 'ATL06',\n", + " 'temporal': ['1999-02-01T00:00:00Z,2019-03-31T23:59:59Z']}\n", + "Query parameter options:\n", + " defaultdict(, {})\n" + ] + } + ], + "source": [ + "print(results)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "de6988dd-341d-4627-9de3-9de3977f4b02", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'short_name': 'ATL06',\n", + " 'bounding_box': '-10.0,20.0,10.0,50.0',\n", + " 'temporal': ['1999-02-01T00:00:00Z,2019-03-31T23:59:59Z']}" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "results.query_parameters" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "f106a8a0-3eaa-4aea-b030-6220c571eb55", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "defaultdict(dict, {})" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "results.query_options" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "9b44fa92-09d5-438a-b1d5-c78d2fb36e69", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Results'" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "repr(results)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "3738a63c-728d-45c7-ab2b-5958a686898c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
sizemeta.concept-typemeta.concept-idmeta.revision-idmeta.native-idmeta.collection-concept-idmeta.provider-idmeta.formatmeta.revision-dateumm.RelatedUrls...umm.DataGranule.Identifiersumm.DataGranule.ProductionDateTimeumm.DataGranule.ArchiveAndDistributionInformationumm.TemporalExtent.RangeDateTime.BeginningDateTimeumm.TemporalExtent.RangeDateTime.EndingDateTimeumm.GranuleURumm.MetadataSpecification.URLumm.MetadataSpecification.Nameumm.MetadataSpecification.Versiongeometry
096.0granuleG3570276137-NSIDC_CPRD1ATL06_20181014034354_02370106_007_01.h5C3564876127-NSIDC_CPRDNSIDC_CPRDapplication/iso:smap+xml2025-06-16T18:34:11.022Z[{'URL': 'https://data.nsidc.earthdatacloud.na......[{'Identifier': 'ATL06_20181014034354_02370106...2025-06-02T18:10:45.000Z[{'Name': 'Not provided', 'Size': 96.0, 'SizeU...2018-10-14T03:45:14.224Z2018-10-14T03:52:24.099ZATL06_20181014034354_02370106_007_01.h5https://cdn.earthdata.nasa.gov/umm/granule/v1.6.6UMM-G1.6.6MULTIPOLYGON (((0.37223 54.41191, 0.18146 54.4...
188.0granuleG3570289327-NSIDC_CPRD1ATL06_20181014035224_02370107_007_01.h5C3564876127-NSIDC_CPRDNSIDC_CPRDapplication/iso:smap+xml2025-06-16T18:53:14.951Z[{'URL': 'https://data.nsidc.earthdatacloud.na......[{'Identifier': 'ATL06_20181014035224_02370107...2025-06-02T18:10:45.000Z[{'Name': 'Not provided', 'Size': 88.0, 'SizeU...2018-10-14T03:52:23.765Z2018-10-14T03:58:11.596ZATL06_20181014035224_02370107_007_01.h5https://cdn.earthdata.nasa.gov/umm/granule/v1.6.6UMM-G1.6.6MULTIPOLYGON (((-3.22432 27.03415, -3.3486 27....
2104.0granuleG3570257164-NSIDC_CPRD1ATL06_20181014154641_02450101_007_01.h5C3564876127-NSIDC_CPRDNSIDC_CPRDapplication/iso:smap+xml2025-06-16T18:06:06.206Z[{'URL': 'https://data.nsidc.earthdatacloud.na......[{'Identifier': 'ATL06_20181014154641_02450101...2025-06-02T18:15:25.000Z[{'Name': 'Not provided', 'Size': 104.0, 'Size...2018-10-14T15:47:56.914Z2018-10-14T15:53:43.112ZATL06_20181014154641_02450101_007_01.h5https://cdn.earthdata.nasa.gov/umm/granule/v1.6.6UMM-G1.6.6MULTIPOLYGON (((-5.93621 27.04799, -6.06056 27...
380.0granuleG3570257403-NSIDC_CPRD1ATL06_20181014155343_02450102_007_01.h5C3564876127-NSIDC_CPRDNSIDC_CPRDapplication/iso:smap+xml2025-06-16T18:06:24.712Z[{'URL': 'https://data.nsidc.earthdatacloud.na......[{'Identifier': 'ATL06_20181014155343_02450102...2025-06-02T18:12:41.000Z[{'Name': 'Not provided', 'Size': 80.0, 'SizeU...2018-10-14T15:53:42.775Z2018-10-14T16:00:56.552ZATL06_20181014155343_02450102_007_01.h5https://cdn.earthdata.nasa.gov/umm/granule/v1.6.6UMM-G1.6.6MULTIPOLYGON (((-9.50764 54.67392, -9.69975 54...
496.0granuleG3570257166-NSIDC_CPRD1ATL06_20181015152102_02600101_007_01.h5C3564876127-NSIDC_CPRDNSIDC_CPRDapplication/iso:smap+xml2025-06-16T18:06:06.491Z[{'URL': 'https://data.nsidc.earthdatacloud.na......[{'Identifier': 'ATL06_20181015152102_02600101...2025-06-03T16:14:28.000Z[{'Name': 'Not provided', 'Size': 96.0, 'SizeU...2018-10-15T15:22:34.535Z2018-10-15T15:28:03.193ZATL06_20181015152102_02600101_007_01.h5https://cdn.earthdata.nasa.gov/umm/granule/v1.6.6UMM-G1.6.6MULTIPOLYGON (((-0.2261 27.04792, -0.35042 27....
5112.0granuleG3570231344-NSIDC_CPRD1ATL06_20181015152804_02600102_007_01.h5C3564876127-NSIDC_CPRDNSIDC_CPRDapplication/iso:smap+xml2025-06-16T17:33:31.008Z[{'URL': 'https://data.nsidc.earthdatacloud.na......[{'Identifier': 'ATL06_20181015152804_02600102...2025-06-03T16:14:22.000Z[{'Name': 'Not provided', 'Size': 112.0, 'Size...2018-10-15T15:28:02.856Z2018-10-15T15:36:25.498ZATL06_20181015152804_02600102_007_01.h5https://cdn.earthdata.nasa.gov/umm/granule/v1.6.6UMM-G1.6.6MULTIPOLYGON (((-4.58432 59.04652, -4.80021 59...
696.0granuleG3570249750-NSIDC_CPRD1ATL06_20181016025235_02670106_007_01.h5C3564876127-NSIDC_CPRDNSIDC_CPRDapplication/iso:smap+xml2025-06-16T17:57:09.206Z[{'URL': 'https://data.nsidc.earthdatacloud.na......[{'Identifier': 'ATL06_20181016025235_02670106...2025-06-04T17:20:18.000Z[{'Name': 'Not provided', 'Size': 96.0, 'SizeU...2018-10-16T02:52:33.608Z2018-10-16T03:00:39.995ZATL06_20181016025235_02670106_007_01.h5https://cdn.earthdata.nasa.gov/umm/granule/v1.6.6UMM-G1.6.6MULTIPOLYGON (((12.74714 59.53394, 12.52685 59...
7104.0granuleG3570257461-NSIDC_CPRD1ATL06_20181016145523_02750101_007_01.h5C3564876127-NSIDC_CPRDNSIDC_CPRDapplication/iso:smap+xml2025-06-16T18:06:31.979Z[{'URL': 'https://data.nsidc.earthdatacloud.na......[{'Identifier': 'ATL06_20181016145523_02750101...2025-06-04T19:44:06.000Z[{'Name': 'Not provided', 'Size': 104.0, 'Size...2018-10-16T14:56:27.623Z2018-10-16T15:02:23.180ZATL06_20181016145523_02750101_007_01.h5https://cdn.earthdata.nasa.gov/umm/granule/v1.6.6UMM-G1.6.6MULTIPOLYGON (((5.48451 27.04805, 5.35948 27.0...
872.0granuleG3570257463-NSIDC_CPRD1ATL06_20181016150225_02750102_007_01.h5C3564876127-NSIDC_CPRDNSIDC_CPRDapplication/iso:smap+xml2025-06-16T18:06:32.086Z[{'URL': 'https://data.nsidc.earthdatacloud.na......[{'Identifier': 'ATL06_20181016150225_02750102...2025-06-04T19:42:59.000Z[{'Name': 'Not provided', 'Size': 72.0, 'SizeU...2018-10-16T15:02:22.817Z2018-10-16T15:09:11.437ZATL06_20181016150225_02750102_007_01.h5https://cdn.earthdata.nasa.gov/umm/granule/v1.6.6UMM-G1.6.6MULTIPOLYGON (((2.1757 53.07381, 1.98995 53.06...
988.0granuleG3564883443-NSIDC_CPRD1ATL06_20181017040113_02830106_007_01.h5C3564876127-NSIDC_CPRDNSIDC_CPRDapplication/iso:smap+xml2025-06-10T15:41:17.843Z[{'URL': 'https://data.nsidc.earthdatacloud.na......[{'Identifier': 'ATL06_20181017040113_02830106...2025-06-04T18:09:35.000Z[{'Name': 'Not provided', 'Size': 88.0, 'SizeU...2018-10-17T04:01:13.240Z2018-10-17T04:09:41.818ZATL06_20181017040113_02830106_007_01.h5https://cdn.earthdata.nasa.gov/umm/granule/v1.6.6UMM-G1.6.6MULTIPOLYGON (((-5.18773 59.40557, -5.40723 59...
\n", + "

10 rows × 24 columns

\n", + "
" + ], + "text/plain": [ + " size meta.concept-type meta.concept-id meta.revision-id \\\n", + "0 96.0 granule G3570276137-NSIDC_CPRD 1 \n", + "1 88.0 granule G3570289327-NSIDC_CPRD 1 \n", + "2 104.0 granule G3570257164-NSIDC_CPRD 1 \n", + "3 80.0 granule G3570257403-NSIDC_CPRD 1 \n", + "4 96.0 granule G3570257166-NSIDC_CPRD 1 \n", + "5 112.0 granule G3570231344-NSIDC_CPRD 1 \n", + "6 96.0 granule G3570249750-NSIDC_CPRD 1 \n", + "7 104.0 granule G3570257461-NSIDC_CPRD 1 \n", + "8 72.0 granule G3570257463-NSIDC_CPRD 1 \n", + "9 88.0 granule G3564883443-NSIDC_CPRD 1 \n", + "\n", + " meta.native-id meta.collection-concept-id \\\n", + "0 ATL06_20181014034354_02370106_007_01.h5 C3564876127-NSIDC_CPRD \n", + "1 ATL06_20181014035224_02370107_007_01.h5 C3564876127-NSIDC_CPRD \n", + "2 ATL06_20181014154641_02450101_007_01.h5 C3564876127-NSIDC_CPRD \n", + "3 ATL06_20181014155343_02450102_007_01.h5 C3564876127-NSIDC_CPRD \n", + "4 ATL06_20181015152102_02600101_007_01.h5 C3564876127-NSIDC_CPRD \n", + "5 ATL06_20181015152804_02600102_007_01.h5 C3564876127-NSIDC_CPRD \n", + "6 ATL06_20181016025235_02670106_007_01.h5 C3564876127-NSIDC_CPRD \n", + "7 ATL06_20181016145523_02750101_007_01.h5 C3564876127-NSIDC_CPRD \n", + "8 ATL06_20181016150225_02750102_007_01.h5 C3564876127-NSIDC_CPRD \n", + "9 ATL06_20181017040113_02830106_007_01.h5 C3564876127-NSIDC_CPRD \n", + "\n", + " meta.provider-id meta.format meta.revision-date \\\n", + "0 NSIDC_CPRD application/iso:smap+xml 2025-06-16T18:34:11.022Z \n", + "1 NSIDC_CPRD application/iso:smap+xml 2025-06-16T18:53:14.951Z \n", + "2 NSIDC_CPRD application/iso:smap+xml 2025-06-16T18:06:06.206Z \n", + "3 NSIDC_CPRD application/iso:smap+xml 2025-06-16T18:06:24.712Z \n", + "4 NSIDC_CPRD application/iso:smap+xml 2025-06-16T18:06:06.491Z \n", + "5 NSIDC_CPRD application/iso:smap+xml 2025-06-16T17:33:31.008Z \n", + "6 NSIDC_CPRD application/iso:smap+xml 2025-06-16T17:57:09.206Z \n", + "7 NSIDC_CPRD application/iso:smap+xml 2025-06-16T18:06:31.979Z \n", + "8 NSIDC_CPRD application/iso:smap+xml 2025-06-16T18:06:32.086Z \n", + "9 NSIDC_CPRD application/iso:smap+xml 2025-06-10T15:41:17.843Z \n", + "\n", + " umm.RelatedUrls ... \\\n", + "0 [{'URL': 'https://data.nsidc.earthdatacloud.na... ... \n", + "1 [{'URL': 'https://data.nsidc.earthdatacloud.na... ... \n", + "2 [{'URL': 'https://data.nsidc.earthdatacloud.na... ... \n", + "3 [{'URL': 'https://data.nsidc.earthdatacloud.na... ... \n", + "4 [{'URL': 'https://data.nsidc.earthdatacloud.na... ... \n", + "5 [{'URL': 'https://data.nsidc.earthdatacloud.na... ... \n", + "6 [{'URL': 'https://data.nsidc.earthdatacloud.na... ... \n", + "7 [{'URL': 'https://data.nsidc.earthdatacloud.na... ... \n", + "8 [{'URL': 'https://data.nsidc.earthdatacloud.na... ... \n", + "9 [{'URL': 'https://data.nsidc.earthdatacloud.na... ... \n", + "\n", + " umm.DataGranule.Identifiers \\\n", + "0 [{'Identifier': 'ATL06_20181014034354_02370106... \n", + "1 [{'Identifier': 'ATL06_20181014035224_02370107... \n", + "2 [{'Identifier': 'ATL06_20181014154641_02450101... \n", + "3 [{'Identifier': 'ATL06_20181014155343_02450102... \n", + "4 [{'Identifier': 'ATL06_20181015152102_02600101... \n", + "5 [{'Identifier': 'ATL06_20181015152804_02600102... \n", + "6 [{'Identifier': 'ATL06_20181016025235_02670106... \n", + "7 [{'Identifier': 'ATL06_20181016145523_02750101... \n", + "8 [{'Identifier': 'ATL06_20181016150225_02750102... \n", + "9 [{'Identifier': 'ATL06_20181017040113_02830106... \n", + "\n", + " umm.DataGranule.ProductionDateTime \\\n", + "0 2025-06-02T18:10:45.000Z \n", + "1 2025-06-02T18:10:45.000Z \n", + "2 2025-06-02T18:15:25.000Z \n", + "3 2025-06-02T18:12:41.000Z \n", + "4 2025-06-03T16:14:28.000Z \n", + "5 2025-06-03T16:14:22.000Z \n", + "6 2025-06-04T17:20:18.000Z \n", + "7 2025-06-04T19:44:06.000Z \n", + "8 2025-06-04T19:42:59.000Z \n", + "9 2025-06-04T18:09:35.000Z \n", + "\n", + " umm.DataGranule.ArchiveAndDistributionInformation \\\n", + "0 [{'Name': 'Not provided', 'Size': 96.0, 'SizeU... \n", + "1 [{'Name': 'Not provided', 'Size': 88.0, 'SizeU... \n", + "2 [{'Name': 'Not provided', 'Size': 104.0, 'Size... \n", + "3 [{'Name': 'Not provided', 'Size': 80.0, 'SizeU... \n", + "4 [{'Name': 'Not provided', 'Size': 96.0, 'SizeU... \n", + "5 [{'Name': 'Not provided', 'Size': 112.0, 'Size... \n", + "6 [{'Name': 'Not provided', 'Size': 96.0, 'SizeU... \n", + "7 [{'Name': 'Not provided', 'Size': 104.0, 'Size... \n", + "8 [{'Name': 'Not provided', 'Size': 72.0, 'SizeU... \n", + "9 [{'Name': 'Not provided', 'Size': 88.0, 'SizeU... \n", + "\n", + " umm.TemporalExtent.RangeDateTime.BeginningDateTime \\\n", + "0 2018-10-14T03:45:14.224Z \n", + "1 2018-10-14T03:52:23.765Z \n", + "2 2018-10-14T15:47:56.914Z \n", + "3 2018-10-14T15:53:42.775Z \n", + "4 2018-10-15T15:22:34.535Z \n", + "5 2018-10-15T15:28:02.856Z \n", + "6 2018-10-16T02:52:33.608Z \n", + "7 2018-10-16T14:56:27.623Z \n", + "8 2018-10-16T15:02:22.817Z \n", + "9 2018-10-17T04:01:13.240Z \n", + "\n", + " umm.TemporalExtent.RangeDateTime.EndingDateTime \\\n", + "0 2018-10-14T03:52:24.099Z \n", + "1 2018-10-14T03:58:11.596Z \n", + "2 2018-10-14T15:53:43.112Z \n", + "3 2018-10-14T16:00:56.552Z \n", + "4 2018-10-15T15:28:03.193Z \n", + "5 2018-10-15T15:36:25.498Z \n", + "6 2018-10-16T03:00:39.995Z \n", + "7 2018-10-16T15:02:23.180Z \n", + "8 2018-10-16T15:09:11.437Z \n", + "9 2018-10-17T04:09:41.818Z \n", + "\n", + " umm.GranuleUR \\\n", + "0 ATL06_20181014034354_02370106_007_01.h5 \n", + "1 ATL06_20181014035224_02370107_007_01.h5 \n", + "2 ATL06_20181014154641_02450101_007_01.h5 \n", + "3 ATL06_20181014155343_02450102_007_01.h5 \n", + "4 ATL06_20181015152102_02600101_007_01.h5 \n", + "5 ATL06_20181015152804_02600102_007_01.h5 \n", + "6 ATL06_20181016025235_02670106_007_01.h5 \n", + "7 ATL06_20181016145523_02750101_007_01.h5 \n", + "8 ATL06_20181016150225_02750102_007_01.h5 \n", + "9 ATL06_20181017040113_02830106_007_01.h5 \n", + "\n", + " umm.MetadataSpecification.URL \\\n", + "0 https://cdn.earthdata.nasa.gov/umm/granule/v1.6.6 \n", + "1 https://cdn.earthdata.nasa.gov/umm/granule/v1.6.6 \n", + "2 https://cdn.earthdata.nasa.gov/umm/granule/v1.6.6 \n", + "3 https://cdn.earthdata.nasa.gov/umm/granule/v1.6.6 \n", + "4 https://cdn.earthdata.nasa.gov/umm/granule/v1.6.6 \n", + "5 https://cdn.earthdata.nasa.gov/umm/granule/v1.6.6 \n", + "6 https://cdn.earthdata.nasa.gov/umm/granule/v1.6.6 \n", + "7 https://cdn.earthdata.nasa.gov/umm/granule/v1.6.6 \n", + "8 https://cdn.earthdata.nasa.gov/umm/granule/v1.6.6 \n", + "9 https://cdn.earthdata.nasa.gov/umm/granule/v1.6.6 \n", + "\n", + " umm.MetadataSpecification.Name umm.MetadataSpecification.Version \\\n", + "0 UMM-G 1.6.6 \n", + "1 UMM-G 1.6.6 \n", + "2 UMM-G 1.6.6 \n", + "3 UMM-G 1.6.6 \n", + "4 UMM-G 1.6.6 \n", + "5 UMM-G 1.6.6 \n", + "6 UMM-G 1.6.6 \n", + "7 UMM-G 1.6.6 \n", + "8 UMM-G 1.6.6 \n", + "9 UMM-G 1.6.6 \n", + "\n", + " geometry \n", + "0 MULTIPOLYGON (((0.37223 54.41191, 0.18146 54.4... \n", + "1 MULTIPOLYGON (((-3.22432 27.03415, -3.3486 27.... \n", + "2 MULTIPOLYGON (((-5.93621 27.04799, -6.06056 27... \n", + "3 MULTIPOLYGON (((-9.50764 54.67392, -9.69975 54... \n", + "4 MULTIPOLYGON (((-0.2261 27.04792, -0.35042 27.... \n", + "5 MULTIPOLYGON (((-4.58432 59.04652, -4.80021 59... \n", + "6 MULTIPOLYGON (((12.74714 59.53394, 12.52685 59... \n", + "7 MULTIPOLYGON (((5.48451 27.04805, 5.35948 27.0... \n", + "8 MULTIPOLYGON (((2.1757 53.07381, 1.98995 53.06... \n", + "9 MULTIPOLYGON (((-5.18773 59.40557, -5.40723 59... \n", + "\n", + "[10 rows x 24 columns]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "results.to_gdf()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2f673f33-ac69-451d-a48c-8d439ed74641", + "metadata": {}, + "outputs": [], + "source": [ + "earthaccess.login()\n", + "files = earthaccess.download(results, \"./local_folder\")" + ] + }, + { + "cell_type": "markdown", + "id": "2bf3c43a-aa15-4959-b8f7-00034832fa40", + "metadata": {}, + "source": [ + "## Search collections" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "f680981e-8653-4272-a75f-b2379c468d46", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
KeyValue
Length184
PreviewATLAS/ICESat-2 L3B Daily and Monthly Gridded Polar Sea Surface Height Anomaly V004,SARAL+Near-Real-Time+Value-added+Operational+Geophysical+Data+Record+Sea+Surface+Height+Anomaly,ATLAS/ICESat-2 L3B Daily and Monthly Gridded Polar Sea Surface Height Anomaly V003\n", + "
Query parameters{'keyword': 'sea surface anomaly', 'cloud_hosted': True}
Query parameter optionsdefaultdict(, {})
\n" + ], + "text/plain": [ + "Results" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "datasets = earthaccess.search_datasets(\n", + " keyword=\"sea surface anomaly\",\n", + " cloud_hosted=True,\n", + ")\n", + "datasets" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "1a57dbd7-e03c-45c6-90f6-3733372dad19", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " Length: 184\n", + " Preview: [\n", + "ATLAS/ICESat-2 L3B Daily and Monthly Gridded Polar Sea Surface Height Anomaly V004\n", + "SARAL+Near-Real-Time+Value-added+Operational+Geophysical+Data+Record+Sea+Surface+Height+Anomaly\n", + "ATLAS/ICESat-2 L3B Daily and Monthly Gridded Polar Sea Surface Height Anomaly V003\n", + "]\n", + " Query parameters:\n", + " - {'keyword': 'sea surface anomaly', 'cloud_hosted': True}\n", + "\n", + " Query parameter options:\n", + " - defaultdict(, {})\n", + "\n" + ] + } + ], + "source": [ + "print(datasets)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "4eaabe9b-494f-4351-973d-219f1c808620", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'keyword': 'sea surface anomaly', 'cloud_hosted': True}" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "datasets.query_parameters" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "8889f0fc-c9da-456b-8048-bc7870e88c6d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "defaultdict(dict, {})" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "datasets.query_options" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "4b1e569a-8bb3-4a7f-89be-667e6dbc95f6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Results'" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "repr(datasets)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "b69871e5-1789-4a19-92ba-54ccde602a13", + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "Only supports DataGranule results", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mTypeError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[26]\u001b[39m\u001b[32m, line 2\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;66;03m# Not expected to work.\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m2\u001b[39m \u001b[43mdatasets\u001b[49m\u001b[43m.\u001b[49m\u001b[43mto_gdf\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/code/earthaccess/earthaccess/results.py:690\u001b[39m, in \u001b[36mto_gdf\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 0\u001b[39m \n", + "\u001b[31mTypeError\u001b[39m: Only supports DataGranule results" + ] + } + ], + "source": [ + "# Not expected to work.\n", + "datasets.to_gdf()" + ] + }, + { + "cell_type": "markdown", + "id": "932169e2-c88d-4fc2-bf81-91e2c739d972", + "metadata": {}, + "source": [ + "## What's next" + ] + }, + { + "cell_type": "markdown", + "id": "086a3da7-d927-4ad2-8ebb-185c0acec3ec", + "metadata": {}, + "source": [ + "### `QueryParameters` interface\n", + "\n", + "**Single responsibiliy: handles what you want to communicate, not how you communicate.**\n", + "\n", + "```python\n", + "query = earthaccess.GranuleQuery(\n", + " concept_id=...,\n", + ")\n", + "results = earthaccess.search(query)\n", + "```\n", + "\n", + "Notice that we didn't say `search_data` or `search_datasets` (those are confusing names, right?), the query object entirely expresses what the user wants." + ] + }, + { + "cell_type": "markdown", + "id": "13330deb-91c1-4c35-a5da-1b9dbd73316b", + "metadata": {}, + "source": [ + "### Save and load `Results`!\n", + "\n", + "**Enables reproducibility!**\n", + "\n", + "```python\n", + "results = earthaccess.search(...)\n", + "results.save(\"/my-results.json\")\n", + "```\n", + "\n", + "...\n", + "\n", + "```python\n", + "results = Results.load(\"/my-results.json\")\n", + "\n", + "# Reproduce:\n", + "earthaccess.download(results)\n", + "\n", + "# You can also repeat:\n", + "earthaccess.download(earthaccess.search(results.query))\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "8bad517a-7ef8-4e19-a703-4a85ac651978", + "metadata": {}, + "source": [ + "### Extract a client interface\n", + "\n", + "**Single responsibility: handles communication, not what you want to communicate.**\n", + "\n", + "```python\n", + "prod_client = ea.Client(maturity=ea.maturity.PROD)\n", + "prod_client.login()\n", + "prod_results = prod_client.search(query)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "94c143c2-1aff-440e-9d31-19cbcd505b26", + "metadata": {}, + "source": [ + "## Questions for you" + ] + }, + { + "cell_type": "markdown", + "id": "009136ce-c4e0-43ed-bb07-c20e1a5e5630", + "metadata": {}, + "source": [ + "### Do people want query results to have the original query attached?\n", + "\n", + "We think so! What about you?\n", + "\n", + "```python\n", + "results = earthaccess.search(...)\n", + "repeated = earthaccess.search(results.query)\n", + "```\n", + "\n", + "**The attached query object doesn't enable reproducibility, but it does enable repeatability and provides provenance.**\n", + "CMR's holdings can change between query executions, or CMR might be degraded.\n", + "\n", + "The `Results` object itself enables reproducibility, assuming the data is still hosted by NASA." + ] + }, + { + "cell_type": "markdown", + "id": "76aaad4c-4418-4992-b2d7-70fa04f43fa2", + "metadata": {}, + "source": [ + "### What happens if you operate on results objects?\n", + "\n", + "```python\n", + "results1 == results2 # ???\n", + "```\n", + "\n", + "```python\n", + "results_all = results1 + results2\n", + "results.query # ???\n", + "```\n", + "\n", + "```python \n", + "results.append() # ???\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "986f2dbf-3edf-4897-be9f-dabaf5220e23", + "metadata": {}, + "source": [ + "### How do we handle paging?\n", + "\n", + "Get everything or yeild pages?" + ] + }, + { + "cell_type": "markdown", + "id": "0965c1df-004d-4986-b34f-d1d35b822fc0", + "metadata": {}, + "source": [ + "#### What we think" + ] + }, + { + "cell_type": "markdown", + "id": "5ba14964-e4f6-40c8-b0eb-62dced46f065", + "metadata": {}, + "source": [ + "When `Results` are concatenated, returns a `Results` object which can still do `.to_gdf()` and `.query` is `None`." + ] + }, + { + "cell_type": "markdown", + "id": "d7dde3f1-d18f-48e5-bcb3-d5f94025d53d", + "metadata": {}, + "source": [ + "### What's missing / what do we care about in a `Results` object?\n", + "\n", + "* Provenance:\n", + " - [x] What were the query parameters?\n", + " - [ ] Was UAT/SIT/Prod used to get these results?\n", + " - [ ] What user made the request (ACLs)?\n", + " - [ ] When was request made/results generated?\n", + "\n", + "* Usage:\n", + " - [ ] Get \"assets\"? (S3 URIs, Data URLs)\n", + " - [ ] Extract metadata? E.g., just a list of granule IDs?" + ] + }, + { + "cell_type": "markdown", + "id": "8871c3e0-dffc-468f-8ca4-e3c58f40a8d7", + "metadata": {}, + "source": [ + "### What questions do you have?" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python (uv-env)", + "language": "python", + "name": "uv-env" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/demo_old.ipynb b/demo_old.ipynb new file mode 100644 index 000000000..7158a7dac --- /dev/null +++ b/demo_old.ipynb @@ -0,0 +1,196 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "13ec9f48-1d80-4a15-8c97-310860b8171d", + "metadata": {}, + "source": [ + "How to run:\n", + "\n", + "```bash\n", + "# Jupyter will automatically pick up .venv when starting a kernel;\n", + "# we use a tempdir to avoid accidentally using the dev version of earthaccess.\n", + "mkdir -p /tmp/foo\n", + "cd /tmp/foo\n", + "\n", + "ln -s demo_old.ipynb ~/Projects/earthaccess/demo_old.ipynb\n", + "\n", + "uv run --with earthaccess==0.17.0 --with jupyterlab --with jupyter-collaboration --with geopandas jupyter lab --IdentityProvider.token=\"\" demo_old.ipynb\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "faced14e-ef64-4c8c-b28a-50ebbdeb6998", + "metadata": {}, + "source": [ + "# Working with `earthaccess` results the old way" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b4daf0a-aaf1-48c3-82cc-5d911e48e9ca", + "metadata": {}, + "outputs": [], + "source": [ + "import earthaccess\n", + "\n", + "earthaccess.__version__" + ] + }, + { + "cell_type": "markdown", + "id": "3120af28-ee4a-4914-b7af-a16d62b03d1a", + "metadata": {}, + "source": [ + "## Search granules" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "258ae408", + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "outputs": [], + "source": [ + "results = earthaccess.search_data(\n", + " short_name='ATL06',\n", + " bounding_box=(-10, 20, 10, 50),\n", + " temporal=(\"1999-02\", \"2019-03\"),\n", + " count=10,\n", + ")\n", + "results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "19993b0e-76e0-484d-a007-91953d256634", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "print(results)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "113c09b3-2f2b-44ec-9d53-909246a899ff", + "metadata": {}, + "outputs": [], + "source": [ + "# No way to do these things\n", + "results.query_parameters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fe16f12f-007d-48af-b102-42c8c1d9dcc9", + "metadata": {}, + "outputs": [], + "source": [ + "results.save(...)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "75c84189-3f81-4298-bc02-c388f1e06d59", + "metadata": {}, + "outputs": [], + "source": [ + "results.load(...)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "025cf3b0-d548-4abe-a86d-6c9085450114", + "metadata": {}, + "outputs": [], + "source": [ + "results.to_gdf()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "68815c76-0073-48fb-b372-b829de13553b", + "metadata": {}, + "outputs": [], + "source": [ + "# You can do it with this incantation:\n", + "from geopandas import GeoDataFrame\n", + "\n", + "GeoDataFrame(\n", + " pd.json_normalize(self),\n", + " geometry=self,\n", + " crs=\"EPSG:4326\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "2bf3c43a-aa15-4959-b8f7-00034832fa40", + "metadata": {}, + "source": [ + "## Search collections" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f680981e-8653-4272-a75f-b2379c468d46", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "collections = earthaccess.search_datasets(\n", + " keyword=\"sea surface anomaly\",\n", + " cloud_hosted=True,\n", + ")\n", + "collections" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1a57dbd7-e03c-45c6-90f6-3733372dad19", + "metadata": {}, + "outputs": [], + "source": [ + "print(collections)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/earthaccess/api.py b/earthaccess/api.py index b879caf0e..dc75e6c13 100644 --- a/earthaccess/api.py +++ b/earthaccess/api.py @@ -16,7 +16,7 @@ from earthaccess.services import DataServices from .auth import Auth -from .results import DataCollection, DataGranule +from .results import DataCollection, DataGranule, Results from .search import CollectionQuery, DataCollections, DataGranules, GranuleQuery from .store import Store from .system import PROD, System @@ -88,7 +88,7 @@ def _normalize_location(location: str | None) -> str | None: return location -def search_datasets(count: int = -1, **kwargs: Any) -> list[DataCollection]: +def search_datasets(count: int = -1, **kwargs: Any) -> Results[DataCollection]: """Search datasets (collections) using NASA's CMR. [https://cmr.earthdata.nasa.gov/search/site/docs/search/api.html](https://cmr.earthdata.nasa.gov/search/site/docs/search/api.html) @@ -140,7 +140,7 @@ def search_datasets(count: int = -1, **kwargs: Any) -> list[DataCollection]: * **debug**: (bool) If True prints CMR request. Default: True Returns: - A list of DataCollection results that can be used to get information about a + A Results object of DataCollection results that can be used to get information about a dataset, e.g. concept_id, doi, etc. Raises: @@ -183,10 +183,11 @@ def search_datasets(count: int = -1, **kwargs: Any) -> list[DataCollection]: logger.info("Datasets found: %s", datasets_found) if count > 0: return query.get(count) + return query.get_all() -def search_data(count: int = -1, **kwargs: Any) -> list[DataGranule]: +def search_data(count: int = -1, **kwargs: Any) -> Results[DataGranule]: """Search for dataset files (granules) using NASA's CMR. [https://cmr.earthdata.nasa.gov/search/site/docs/search/api.html](https://cmr.earthdata.nasa.gov/search/site/docs/search/api.html) @@ -247,8 +248,8 @@ def search_data(count: int = -1, **kwargs: Any) -> list[DataGranule]: Returns: - a list of DataGranules that can be used to access the granule files by using - `download()` or `open()`. + a Results object of DataGranules that can be used to access the granule + files by using `download()` or `open()`. Raises: RuntimeError: The CMR query failed. diff --git a/earthaccess/results.py b/earthaccess/results.py index b99ed633b..ea1420c79 100644 --- a/earthaccess/results.py +++ b/earthaccess/results.py @@ -1,8 +1,17 @@ +from __future__ import annotations + import json +import pprint import uuid import warnings from functools import cache -from typing import Any, ClassVar +from textwrap import dedent, indent +from typing import TYPE_CHECKING, Any, ClassVar + +if TYPE_CHECKING: + from geopandas import GeoDataFrame + + from .search import DataCollections, DataGranules import requests @@ -609,3 +618,97 @@ def __geo_interface__(self) -> dict[str, object]: msg = f"Invalid Geometry in granule's horizontal spatial extent: {geometry}" raise ValueError(msg) + + +class Results: + """An immutable iterable of search results with convenience methods.""" + + # TODO: Needs to support both a lazy generator approach and an in-memory approach where + # items are never consumed. + def __init__( + self, + items: list[DataGranule] | list[DataCollection], + *, + query: DataGranules | DataCollections, + ) -> None: + self._items = items + self._query = query + + def __repr__(self) -> str: + return f"{self.__class__.__name__}" + + def __str__(self) -> str: + return ( + f"Hits: {self.query.hits()}\n" + f"Query parameters:\n" + f"{indent(pprint.pformat(self.query.params), ' ' * 4)}\n" + f"Query parameter options:\n" + f"{indent(pprint.pformat(self.query.options), ' ' * 4)}" + ) + + def _repr_html_(self) -> str: + return dedent(f""" + + + + + + + + + + + + + + + + + + + + + +
KeyValue
Query Type{self.query.__class__.__name__}
Hits{self.query.hits()}
Query parameters{self.query.params}
Query options{self.query.options}
+ """) + + @property + def query(self) -> DataGranules | DataCollections: + return self._query + + +class GranuleResults(Results): + def __init__( + self, + items: list[DataGranule], + *, + query: DataGranules, # TODO: DataGranules is a bad name! Should be DataGranuleQuery. + ) -> None: + super().__init__(items, query=query) + + def to_gdf(self) -> GeoDataFrame: + # TODO: Allow user to receive the raw normalized gdf and rename columns? + # TODO: Pre-massage the GDF to a small set of "known useful" columns and + # give them more reader-friendly names. Avoid future breaking changes + # that might arise from selecting too many columns. + try: + import pandas as pd # noqa: PLC0415 + from geopandas import GeoDataFrame # noqa: PLC0415 + except ImportError as e: + msg = "GeoPandas must be installed" + raise ImportError(msg) from e + return GeoDataFrame( + pd.json_normalize(self), + geometry=self, + crs="EPSG:4326", + ) + + +class CollectionResults(Results): + def __init__( + self, + items: list[DataCollection], + *, + query: DataCollections, + ) -> None: + super().__init__(items, query=query) diff --git a/earthaccess/search.py b/earthaccess/search.py index 6a3defb16..21447a547 100644 --- a/earthaccess/search.py +++ b/earthaccess/search.py @@ -15,7 +15,7 @@ from .auth import Auth from .daac import find_provider, find_provider_by_shortname -from .results import DataCollection, DataGranule +from .results import DataCollection, DataGranule, Results from .utils._search import get_results logger = logging.getLogger(__name__) @@ -81,7 +81,7 @@ def hits(self) -> int: return int(response.headers["CMR-Hits"]) @override - def get(self, limit: int = 2000) -> list[DataCollection]: + def get(self, limit: int = 2000) -> Results[DataCollection]: """Get all the collections (datasets) that match with our current parameters up to some limit, even if spanning multiple pages. @@ -100,10 +100,17 @@ def get(self, limit: int = 2000) -> list[DataCollection]: Raises: RuntimeError: The CMR query failed. """ - return [ - DataCollection(collection, self._fields) - for collection in get_results(self.session, self, limit) - ] + return Results( + [ + DataCollection(collection, self._fields) + for collection in get_results(self.session, self, limit) + ], + query=self, + ) + + @override + def get_all(self) -> Results[DataCollection]: + return Results(super().get_all(), query=self) @override def concept_id(self, IDs: Sequence[str]) -> Self: @@ -467,7 +474,7 @@ def hits(self) -> int: return int(response.headers["CMR-Hits"]) @override - def get(self, limit: int = 2000) -> list[DataGranule]: + def get(self, limit: int = 2000) -> Results[DataGranule]: """Get all the collections (datasets) that match with our current parameters up to some limit, even if spanning multiple pages. @@ -489,7 +496,14 @@ def get(self, limit: int = 2000) -> list[DataGranule]: response = get_results(self.session, self, limit) cloud = len(response) > 0 and self._is_cloud_hosted(response[0]) - return [DataGranule(granule, cloud_hosted=cloud) for granule in response] + return Results( + [DataGranule(granule, cloud_hosted=cloud) for granule in response], + query=self, + ) + + @override + def get_all(self) -> Results[DataGranule]: + return Results(super().get_all(), query=self) @override def parameters(self, **kwargs: Any) -> Self: