├── .circleci └── config.yml ├── .coveragerc ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── images └── calendar.png ├── requirements-dev.txt ├── requirements.txt ├── satsearch ├── __init__.py ├── cli.py ├── search.py └── version.py ├── setup.py ├── test ├── __init__.py ├── aoi1.geojson ├── aoi2.geojson ├── landsat-item1.json ├── landsat-item2.json ├── query.json ├── scenes.geojson ├── sentinel-response.json ├── test_cli.py └── test_search.py └── tutorial-1.ipynb /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Python CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-python/ for more details 4 | version: 2 5 | 6 | 7 | references: 8 | 9 | container_python: &container_python 10 | docker: 11 | - image: circleci/python:3.7.7 12 | working_dir: ~/project 13 | 14 | restore_repo: &restore_repo 15 | restore_cache: 16 | keys: 17 | - v1-repo-{{ .Branch }}-{{ .Revision }} 18 | - v1-repo-{{ .Branch }} 19 | - v1-repo 20 | 21 | jobs: 22 | 23 | checkout_code: 24 | <<: *container_python 25 | steps: 26 | - *restore_repo 27 | - checkout 28 | - save_cache: 29 | key: v1-repo-{{ .Branch }}-{{ .Revision }} 30 | paths: 31 | - ~/project 32 | 33 | install_and_test: 34 | <<: *container_python 35 | steps: 36 | - *restore_repo 37 | - restore_cache: 38 | keys: 39 | - v1-dependencies-{{ checksum "requirements.txt"}} 40 | - v1-dependencies 41 | - run: | 42 | python3 -m venv ~/venv 43 | . ~/venv/bin/activate 44 | pwd 45 | pip install -r requirements.txt 46 | pip install -r requirements-dev.txt 47 | STAC_API_URL=https://earth-search.aws.element84.com/v0 pytest --cov satsearch test/ 48 | - save_cache: 49 | key: v1-dependencies-{{ checksum "requirements.txt"}} 50 | paths: 51 | - ~/venv 52 | 53 | deploy: 54 | <<: *container_python 55 | steps: 56 | - *restore_repo 57 | - restore_cache: 58 | keys: 59 | - v1-dependencies-{{ checksum "requirements.txt"}} 60 | - v1-dependencies 61 | - run: 62 | name: Deploy 63 | command: | 64 | . ~/venv/bin/activate 65 | mkdir -p ~/.ssh 66 | ssh-keyscan github.com >> ~/.ssh/known_hosts 67 | pip install twine 68 | python setup.py sdist 69 | VERSION=`awk -F\' '{print $2,$4}' satsearch/version.py` 70 | git tag $VERSION 71 | git push origin $VERSION 72 | twine upload --username "${PYPI_USER}" --password "${PYPI_PASS}" dist/* 73 | 74 | 75 | workflows: 76 | version: 2 77 | build_test_deploy: 78 | jobs: 79 | - checkout_code 80 | - install_and_test: 81 | requires: 82 | - checkout_code 83 | - deploy: 84 | requires: 85 | - install_and_test 86 | filters: 87 | branches: 88 | only: master 89 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | 3 | show_missing = True 4 | 5 | exclude_lines = 6 | if __name__ == .__main__.: 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .*.swp 3 | *.log 4 | *~ 5 | *.egg-info 6 | build/ 7 | dist/ 8 | *.eggs 9 | MANIFEST 10 | .DS_Store 11 | .idea 12 | .tox 13 | .coverage 14 | .cache 15 | .pytest_cache/ 16 | test-download/ 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## [v0.3.0] - 2020-08-21 8 | 9 | ## Changed 10 | - Updated to work with STAC API v0.9.0 and v1.0.0-beta.2 11 | - `SATUTILS_API_URL` envvar changed to `STAC_API_URL` and default value removed. Specify with envvar or pass into Search when using library 12 | - When downloading, specify `filename_template` for location instead of both `datadir` and `filename`. 13 | - Update pagination to precisely follow STAC spec 14 | 15 | ## [v0.2.3] - 2019-06-25 16 | 17 | ### Changed 18 | - Default SATUTILS_API_URL changed to account for domain name change 19 | 20 | ## [v0.2.2] - 2019-09-20 21 | 22 | ### Changed 23 | - Parser module now handles reading JSON from file 24 | - sat-stac dependency bumped to 0.3.0 - tests updated 25 | - requestor-pays CLI switch, and requestor_pays keyword arg now properly spelled as requester-pays and requester_pays 26 | 27 | ### Fixed 28 | - Fixed issue with some comparison ops not being evaluated 29 | 30 | ## [v0.2.1] - 2019-02-14 31 | 32 | ### Fixed 33 | - Fix number found reported when using .found() function and searching by IDs 34 | - Fixed URL paths in windows by using urljoin instead of os.path.join 35 | 36 | ### Changed 37 | - update default API URL to sat-api.developmentseed.org 38 | - update default save path from ${eo:platform}/${date} to ${collection}/${date} 39 | - Default limit to search.items() changed from 1000 to 10000 40 | - Changed internal page size from 1000 to 500 (page size of queries to endpoint) 41 | 42 | ### Added 43 | - Warning issued when number of items found greater than limit 44 | - requestor-pays option to acknowledge paying of egress costs when downloading (defaults to False) 45 | 46 | 47 | ## [v0.2.0] - 2019-01-31 48 | 49 | ### Changed 50 | - Works with version 0.2.0 of sat-api (STAC 0.6.x) 51 | - Major refactor, uses sat-stac library 52 | 53 | 54 | ## [v0.1.0] - 2018-10-25 55 | 56 | Initial Release 57 | 58 | [Unreleased]: https://github.com/sat-utils/sat-search/compare/master...develop 59 | [v0.3.0]: https://github.com/sat-utils/sat-search/compare/0.2.3...v0.3.0 60 | [v0.2.3]: https://github.com/sat-utils/sat-search/compare/0.2.2...v0.2.3 61 | [v0.2.2]: https://github.com/sat-utils/sat-search/compare/0.2.1...v0.2.2 62 | [v0.2.1]: https://github.com/sat-utils/sat-search/compare/0.2.0...v0.2.1 63 | [v0.2.0]: https://github.com/sat-utils/sat-search/compare/0.1.0...v0.2.0 64 | [v0.1.0]: https://github.com/sat-utils/sat-search/tree/0.1.0 65 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Development Seed 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include requirements.txt 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sat-search 2 | 3 | [![CircleCI](https://circleci.com/gh/sat-utils/sat-search.svg?style=svg&circle-token=a66861b5cbba7acd4abd7975f804ab061a365e1b)](https://circleci.com/gh/sat-utils/sat-search) 4 | 5 | Sat-search is a Python 3 library and a command line tool for discovering and downloading publicly available satellite imagery using STAC compliant API. 6 | 7 | ## STAC APIs 8 | 9 | Starting with v0.3.0, sat-search does not have a default STAC endpoint. This can be passed as a parameter when using the library, or define the environment variable `STAC_API_URL`. Endpoints known to work are provided in this table: 10 | 11 | | Endpoint | Data | 12 | | -------- | ---- | 13 | | https://earth-search.aws.element84.com/v0 | Sentinel-2 | 14 | 15 | 16 | ## Installation 17 | 18 | Sat-search is a very lightweight application, with the only dependency being [sat-stac](https://github.com/sat-utils/sat-stac), which in turn has two dependencies: `requests` and `python-dateutil`. To install sat-search from PyPi: 19 | 20 | ```bash 21 | $ pip install sat-search 22 | ``` 23 | 24 | From source repository: 25 | 26 | ```bash 27 | $ git clone https://github.com/sat-utils/sat-search.git 28 | $ cd sat-search 29 | $ pip install . 30 | ``` 31 | 32 | #### Versions 33 | The latest version of sat-search is 0.2.2, which uses [STAC v0.7.0](https://github.com/radiantearth/stac-spec/tree/v0.7.0). To install other versions of sat-search, specify the version in the call to pip. 34 | 35 | ```bash 36 | pip install sat-search==0.2.0 37 | ``` 38 | 39 | The table below shows the corresponding versions between sat-search and STAC. Additional information can be found in the [CHANGELOG](CHANGELOG.md) 40 | 41 | | sat-search | STAC | 42 | | -------- | ---- | 43 | | 0.1.x | 0.5.x - 0.6.x | 44 | | 0.2.x | 0.5.x - 0.7.x | 45 | | 0.3.x | 0.9.x - 1.0.0-beta.2 | 46 | 47 | 48 | ## Using sat-search 49 | 50 | With sat-search you can search a STAC compliant API with full querying support (if supported by the API). Search results can be saved as a GeoJSON file then loaded later. Assets can be downloaded by the asset key, or "color" (common_name of the band) if provided. 51 | 52 | Sat-search is a Python 3 library that can incorporated into other applications. A [Jupyter notebook tutorial](tutorial-1.ipynb) is included that covers all the main features of the library. 53 | 54 | Sat-search also comes with a Command Line Interface (CLI), which is explained more below. 55 | 56 | #### The CLI 57 | The sat-search CLI has an extensive online help that can be printed with the `-h` switch. 58 | 59 | ``` 60 | $ sat-search -h 61 | usage: sat-search [-h] {search,load} ... 62 | 63 | sat-search (v0.3.0) 64 | 65 | positional arguments: 66 | {search,load} 67 | search Perform new search of items 68 | load Load items from previous search 69 | 70 | optional arguments: 71 | -h, --help show this help message and exit 72 | ``` 73 | 74 | As can be seen there are two subcommands, `search` and `load`, each of which has it's own help. 75 | 76 | #### `search` 77 | 78 | ``` 79 | $ sat-search search -h 80 | usage: sat-search search [-h] [--version] [-v VERBOSITY] 81 | [--print-md [PRINTMD [PRINTMD ...]]] 82 | [--print-cal PRINTCAL] [--save SAVE] 83 | [-c [COLLECTIONS [COLLECTIONS ...]]] 84 | [--ids [IDS [IDS ...]]] [--bbox BBOX BBOX BBOX BBOX] 85 | [--intersects INTERSECTS] [--datetime DATETIME] 86 | [-q [QUERY [QUERY ...]]] 87 | [--sortby [SORTBY [SORTBY ...]]] [--found] 88 | [--url URL] [--headers HEADERS] [--limit LIMIT] 89 | 90 | optional arguments: 91 | -h, --help show this help message and exit 92 | --version Print version and exit 93 | -v VERBOSITY, --verbosity VERBOSITY 94 | 0:quiet, 1:error, 2:warning, 3:info, 4:debug (default: 95 | 2) 96 | 97 | output options: 98 | --print-md [PRINTMD [PRINTMD ...]] 99 | Print specified metadata for matched scenes (default: 100 | None) 101 | --print-cal PRINTCAL Print calendar showing dates (default: None) 102 | --save SAVE Save results as GeoJSON (default: None) 103 | 104 | search options: 105 | -c [COLLECTIONS [COLLECTIONS ...]], --collections [COLLECTIONS [COLLECTIONS ...]] 106 | Name of collection (default: None) 107 | --ids [IDS [IDS ...]] 108 | One or more scene IDs from provided collection 109 | (ignores other parameters) (default: None) 110 | --bbox BBOX BBOX BBOX BBOX 111 | Bounding box (min lon, min lat, max lon, max lat) 112 | (default: None) 113 | --intersects INTERSECTS 114 | GeoJSON Feature (file or string) (default: None) 115 | --datetime DATETIME Single date/time or begin and end date/time (e.g., 116 | 2017-01-01/2017-02-15) (default: None) 117 | -q [QUERY [QUERY ...]], --query [QUERY [QUERY ...]] 118 | Query properties of form KEY=VALUE (<, >, <=, >=, = 119 | supported) (default: None) 120 | --sortby [SORTBY [SORTBY ...]] 121 | Sort by fields (default: None) 122 | --found Only output how many Items found (default: False) 123 | --url URL URL of the API (default: None) 124 | --headers HEADERS Additional request headers (JSON file or string) 125 | (default: None) 126 | --limit LIMIT Limits the total number of items returned (default: 127 | None) 128 | 129 | **Search options** 130 | 131 | - **collections** - Search only a specific collections. This is a shortcut, collection can also be provided as a query (e.g., `-q "collection=landsat-8-l1"`) 132 | - **ids** - Fetch the Item for the provided IDs in the given collection (collection must be provided). All other search options will be ignored. 133 | - **intersects** - Provide a GeoJSON Feature string or the name of a GeoJSON file containing a single Feature that is a Polygon of an AOI to be searched. 134 | - **datetime** - Provide a single partial or full datetime (e.g., 2017, 2017-10, 2017-10-11, 2017-10-11T12:00), or two seperated by a slash that defines a range. e.g., 2017-01-01/2017-06-30 will search for scenes acquired in the first 6 months of 2017. 135 | - **query** - Allows searching for any other scene properties by providing the pair as a string (e.g. `-p "landsat:row=42"`, `-p "eo:cloud_cover<10"`). Supported symbols include: =, <, >, >=, and <= 136 | - **sortby** - Sort by specific properties in ascending or descending order. A list of properties can be provided which will be used for sorting in that order of preference. By default a property will be sorted in descending order. To specify the order the property can be preceded with '<' (ascending) or '>' (descending). e.g., `--sort ">datetime" "}: Any STAC Item property may be used, e.g. "${eo:cloud_cover}", "${platform} 202 | The actual filename will be this prefix followed by the asset key and an appropriate extension. For example, specifying `filename_template` as "./${eo:platform}/${date}/${id}" will save assets for each Item under directories of the platform and the date. Thus, a landsat-8 Item from June 20, 2018 will have it's assets saved in a directory './landsat-8/2017-06-20/'. A metadata asset with the key `MTL` would be saved as './landsat-8/2017-06-20/LC80090292018275LGN00_MTL.TIF'. The last component of the filename_template is taken as the filename. See example directory structure below. 203 | 204 | ``` 205 | landsat-8/ 206 | └── 2018-10-02 207 | ├── LC80090292018275LGN00_MTL.txt 208 | ├── LC80090292018275LGN00_thumbnail.jpg 209 | ├── LC80090302018275LGN00_MTL.txt 210 | └── LC80090302018275LGN00_thumbnail.jpg 211 | ``` 212 | 213 | A shortcut to download all of the assets is available by providing "ALL" as the key to download. This will download every asset for every item. 214 | 215 | ``` 216 | sat-search load scenes.json --download ALL 217 | ``` 218 | 219 | ## Tutorial 220 | This [Jupyter notebook tutorial](tutorial-1.ipynb) covers all the main features of the library. 221 | 222 | ## About 223 | sat-search is part of a collection of tools called [sat-utils](https://github.com/sat-utils). 224 | -------------------------------------------------------------------------------- /images/calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sat-utils/sat-search/43fefe435d784d418bd7010581fd698d93880354/images/calendar.png -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pytest~=3.6.1 2 | pytest-cov~=2.5.1 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | sat-stac~=0.4.0 2 | -------------------------------------------------------------------------------- /satsearch/__init__.py: -------------------------------------------------------------------------------- 1 | from satsearch.search import Search 2 | from satsearch.version import __version__ 3 | 4 | import logging 5 | 6 | # quiet loggers 7 | logging.getLogger('urllib3').propagate = False 8 | logging.getLogger('requests').propagate = False 9 | -------------------------------------------------------------------------------- /satsearch/cli.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import logging 4 | import os 5 | import sys 6 | 7 | from .version import __version__ 8 | from satsearch import Search 9 | from satstac import ItemCollection 10 | from satstac.utils import dict_merge 11 | 12 | API_URL = os.getenv('STAC_API_URL', None) 13 | 14 | 15 | class SatUtilsParser(argparse.ArgumentParser): 16 | 17 | def __init__(self, *args, **kwargs): 18 | """ Initialize a SatUtilsParser """ 19 | super(SatUtilsParser, self).__init__(*args, **kwargs) 20 | self.formatter_class = argparse.ArgumentDefaultsHelpFormatter 21 | 22 | self.pparser = argparse.ArgumentParser(add_help=False) 23 | self.pparser.add_argument('--version', help='Print version and exit', action='version', version=__version__) 24 | self.pparser.add_argument('-v', '--verbosity', default=2, type=int, 25 | help='0:quiet, 1:error, 2:warning, 3:info, 4:debug') 26 | 27 | self.download_parser = argparse.ArgumentParser(add_help=False) 28 | self.download_group = self.download_parser.add_argument_group('download options') 29 | self.download_group.add_argument('--filename_template', default='${collection}/${date}/${id}', 30 | help='Save assets with this filename pattern based on metadata keys') 31 | self.download_group.add_argument('--download', help='Download assets', default=None, nargs='*') 32 | h = 'Acknowledge paying egress costs for downloads (if in requester pays bucket on AWS)' 33 | self.download_group.add_argument('--requester-pays', help=h, default=False, action='store_true', dest='requester_pays') 34 | 35 | self.output_parser = argparse.ArgumentParser(add_help=False) 36 | self.output_group = self.output_parser.add_argument_group('output options') 37 | h = 'Print specified metadata for matched scenes' 38 | self.output_group.add_argument('--print-md', help=h, default=None, nargs='*', dest='printmd') 39 | h = 'Print calendar showing dates' 40 | self.output_group.add_argument('--print-cal', help=h, dest='printcal') 41 | self.output_group.add_argument('--save', help='Save results as GeoJSON', default=None) 42 | 43 | def parse_args(self, *args, **kwargs): 44 | """ Parse arguments """ 45 | args = super(SatUtilsParser, self).parse_args(*args, **kwargs) 46 | args = vars(args) 47 | args = {k: v for k, v in args.items() if v is not None} 48 | 49 | if args.get('command', None) is None: 50 | self.print_help() 51 | sys.exit(0) 52 | 53 | # set logging level 54 | if 'verbosity' in args: 55 | logging.basicConfig(stream=sys.stdout, level=(50-args.pop('verbosity') * 10)) 56 | 57 | # if a filename, read the GeoJSON file 58 | if 'intersects' in args: 59 | if os.path.exists(args['intersects']): 60 | with open(args['intersects']) as f: 61 | data = json.loads(f.read()) 62 | if data['type'] == 'Feature': 63 | args['intersects'] = data['geometry'] 64 | elif data['type'] == 'FeatureCollection': 65 | args['intersects'] = data['features'][0]['geometry'] 66 | else: 67 | args['intersects'] = data 68 | 69 | # If a filename, read the JSON file 70 | if 'headers' in args: 71 | if os.path.exists(args['headers']): 72 | with open(args['headers']) as f: 73 | headers = json.loads(f.read()) 74 | else: 75 | headers = json.loads(args['headers']) 76 | args['headers'] = {k: str(v) for k,v in headers.items()} 77 | 78 | return args 79 | 80 | @classmethod 81 | def newbie(cls, *args, **kwargs): 82 | """ Create a newbie class, with all the skills needed """ 83 | parser = cls(*args, **kwargs) 84 | subparser = parser.add_subparsers(dest='command') 85 | parents = [parser.pparser, parser.output_parser] 86 | 87 | sparser = subparser.add_parser('search', help='Perform new search of items', parents=parents) 88 | """ Adds search arguments to a parser """ 89 | parser.search_group = sparser.add_argument_group('search options') 90 | parser.search_group.add_argument('-c', '--collections', help='Name of collection', nargs='*') 91 | h = 'One or more scene IDs from provided collection (ignores other parameters)' 92 | parser.search_group.add_argument('--ids', help=h, nargs='*', default=None) 93 | parser.search_group.add_argument('--bbox', help='Bounding box (min lon, min lat, max lon, max lat)', nargs=4) 94 | parser.search_group.add_argument('--intersects', help='GeoJSON Feature (file or string)') 95 | parser.search_group.add_argument('--datetime', help='Single date/time or begin and end date/time (e.g., 2017-01-01/2017-02-15)') 96 | parser.search_group.add_argument('-q', '--query', nargs='*', help='Query properties of form KEY=VALUE (<, >, <=, >=, = supported)') 97 | parser.search_group.add_argument('--sortby', help='Sort by fields', nargs='*') 98 | h = 'Only output how many Items found' 99 | parser.search_group.add_argument('--found', help=h, action='store_true', default=False) 100 | parser.search_group.add_argument('--url', help='URL of the API', default=API_URL) 101 | parser.search_group.add_argument('--headers', help='Additional request headers (JSON file or string)', default=None) 102 | parser.search_group.add_argument('--limit', help='Limits the total number of items returned', default=None) 103 | 104 | parents.append(parser.download_parser) 105 | lparser = subparser.add_parser('load', help='Load items from previous search', parents=parents) 106 | lparser.add_argument('items', help='GeoJSON file of Items') 107 | return parser 108 | 109 | class KeyValuePair(argparse.Action): 110 | """ Custom action for getting arbitrary key values from argparse """ 111 | def __call__(self, parser, namespace, values, option_string=None): 112 | for val in values: 113 | n, v = val.split('=') 114 | setattr(namespace, n, {'eq': v}) 115 | 116 | 117 | def main(items=None, printmd=None, printcal=None, 118 | found=False, filename_template='${collection}/${date}/${id}', 119 | save=None, download=None, requester_pays=False, headers=None, **kwargs): 120 | """ Main function for performing a search """ 121 | 122 | if items is None: 123 | ## if there are no items then perform a search 124 | search = Search.search(headers=headers, **kwargs) 125 | ## Commenting out found logic until functions correctly. 126 | if found: 127 | num = search.found(headers=headers) 128 | print('%s items found' % num) 129 | return num 130 | items = search.items(headers=headers) 131 | else: 132 | # otherwise, load a search from a file 133 | items = ItemCollection.open(items) 134 | 135 | print('%s items found' % len(items)) 136 | 137 | # print metadata 138 | if printmd is not None: 139 | print(items.summary(printmd)) 140 | 141 | # print calendar 142 | if printcal: 143 | print(items.calendar(printcal)) 144 | 145 | # save all metadata in JSON file 146 | if save is not None: 147 | items.save(filename=save) 148 | 149 | # download files given `download` keys 150 | if download is not None: 151 | if 'ALL' in download: 152 | # get complete set of assets 153 | download = set([k for i in items for k in i.assets]) 154 | for key in download: 155 | items.download(key=key, filename_template=filename_template, requester_pays=requester_pays) 156 | 157 | return items 158 | 159 | 160 | def cli(): 161 | parser = SatUtilsParser.newbie(description='sat-search (v%s)' % __version__) 162 | kwargs = parser.parse_args(sys.argv[1:]) 163 | 164 | cmd = kwargs.pop('command', None) 165 | if cmd is not None: 166 | main(**kwargs) 167 | 168 | 169 | if __name__ == "__main__": 170 | cli() 171 | -------------------------------------------------------------------------------- /satsearch/search.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import logging 4 | import requests 5 | 6 | from satstac import Collection, Item, ItemCollection 7 | from satstac.utils import dict_merge 8 | from urllib.parse import urljoin 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | class SatSearchError(Exception): 14 | pass 15 | 16 | 17 | class Search(object): 18 | """ One search query (possibly multiple pages) """ 19 | search_op_list = ['>=', '<=', '=', '>', '<'] 20 | search_op_to_stac_op = {'>=': 'gte', '<=': 'lte', '=': 'eq', '>': 'gt', '<': 'lt'} 21 | 22 | def __init__(self, url=os.getenv('STAC_API_URL', None), **kwargs): 23 | """ Initialize a Search object with parameters """ 24 | if url is None: 25 | raise SatSearchError("URL not provided, pass into Search or define STAC_API_URL environment variable") 26 | self.url = url.rstrip("/") + "/" 27 | self.kwargs = kwargs 28 | self.limit = int(self.kwargs['limit']) if 'limit' in self.kwargs else None 29 | 30 | @classmethod 31 | def search(cls, headers=None, **kwargs): 32 | if 'query' in kwargs and isinstance(kwargs['query'], list): 33 | queries = {} 34 | for q in kwargs['query']: 35 | for s in Search.search_op_list: 36 | parts = q.split(s) 37 | if len(parts) == 2: 38 | queries = dict_merge(queries, {parts[0]: {Search.search_op_to_stac_op[s]: parts[1]}}) 39 | break 40 | kwargs['query'] = queries 41 | directions = {'-': 'desc', '+': 'asc'} 42 | if 'sortby' in kwargs and isinstance(kwargs['sortby'], list): 43 | sorts = [] 44 | for a in kwargs['sortby']: 45 | if a[0] not in directions: 46 | a = '+' + a 47 | sorts.append({ 48 | 'field': a[1:], 49 | 'direction': directions[a[0]] 50 | }) 51 | kwargs['sortby'] = sorts 52 | return Search(**kwargs) 53 | 54 | def found(self, headers=None): 55 | """ Small query to determine total number of hits """ 56 | kwargs = { 57 | 'limit': 0 58 | } 59 | kwargs.update(self.kwargs) 60 | url = urljoin(self.url, 'search') 61 | 62 | results = self.query(url=url, headers=headers, **kwargs) 63 | # TODO - check for status_code 64 | logger.debug(f"Found: {json.dumps(results)}") 65 | found = 0 66 | if 'context' in results: 67 | found = results['context']['matched'] 68 | elif 'numberMatched' in results: 69 | found = results['numberMatched'] 70 | return found 71 | 72 | def query(self, url=None, headers=None, **kwargs): 73 | """ Get request """ 74 | url = url or urljoin(self.url, 'search') 75 | logger.debug('Query URL: %s, Body: %s' % (url, json.dumps(kwargs))) 76 | response = requests.post(url, json=kwargs, headers=headers) 77 | logger.debug(f"Response: {response.text}") 78 | # API error 79 | if response.status_code != 200: 80 | raise SatSearchError(response.text) 81 | return response.json() 82 | 83 | def collection(self, cid, headers=None): 84 | """ Get a Collection record """ 85 | url = urljoin(self.url, 'collections/%s' % cid) 86 | return Collection(self.query(url=url, headers=headers)) 87 | 88 | def items(self, limit=10000, page_limit=500, headers=None): 89 | """ Return all of the Items and Collections for this search """ 90 | found = self.found(headers=headers) 91 | limit = self.limit or limit 92 | if found > limit: 93 | logger.warning('There are more items found (%s) than the limit (%s) provided.' % (found, limit)) 94 | 95 | nextlink = { 96 | 'method': 'POST', 97 | 'href': urljoin(self.url, 'search'), 98 | 'headers': headers, 99 | 'body': self.kwargs, 100 | 'merge': False 101 | } 102 | 103 | items = [] 104 | while nextlink and len(items) < limit: 105 | if nextlink.get('method', 'GET') == 'GET': 106 | resp = self.query(url=nextlink['href'], headers=headers, **self.kwargs) 107 | else: 108 | _headers = nextlink.get('headers', {}) 109 | _body = nextlink.get('body', {}) 110 | _body.update({'limit': page_limit}) 111 | 112 | if nextlink.get('merge', False): 113 | _headers.update(headers or {}) 114 | _body.update(self.kwargs) 115 | 116 | resp = self.query(url=nextlink['href'], headers=headers, **_body) 117 | items += [Item(i) for i in resp['features']] 118 | links = [l for l in resp['links'] if l['rel'] == 'next'] 119 | nextlink = links[0] if len(links) == 1 else None 120 | 121 | # retrieve collections 122 | collections = [] 123 | try: 124 | for c in set([item._data['collection'] for item in items if 'collection' in item._data]): 125 | collections.append(self.collection(c, headers=headers)) 126 | #del collections[c]['links'] 127 | except: 128 | pass 129 | logger.debug(f"Found: {len(items)}") 130 | return ItemCollection(items, collections=collections) 131 | -------------------------------------------------------------------------------- /satsearch/version.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.3.0' 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from setuptools import setup, find_packages 3 | from imp import load_source 4 | from os import path 5 | import io 6 | 7 | __version__ = load_source('satsearch.version', 'satsearch/version.py').__version__ 8 | 9 | here = path.abspath(path.dirname(__file__)) 10 | 11 | # get the dependencies and installs 12 | with io.open(path.join(here, 'requirements.txt'), encoding='utf-8') as f: 13 | all_reqs = f.read().split('\n') 14 | 15 | install_requires = [x.strip() for x in all_reqs if 'git+' not in x] 16 | dependency_links = [x.strip().replace('git+', '') for x in all_reqs if 'git+' not in x] 17 | 18 | setup( 19 | name='sat-search', 20 | author='Matthew Hanson (matthewhanson)', 21 | author_email='matt.a.hanson@gmail.com', 22 | version=__version__, 23 | description='A python client for sat-api', 24 | url='https://github.com/sat-utils/sat-search', 25 | license='MIT', 26 | classifiers=[ 27 | 'Framework :: Pytest', 28 | 'Topic :: Scientific/Engineering :: GIS', 29 | 'Topic :: Scientific/Engineering', 30 | 'Intended Audience :: Developers', 31 | 'Intended Audience :: Science/Research', 32 | 'License :: Freeware', 33 | 'Programming Language :: Python :: 2.7', 34 | 'Programming Language :: Python :: 3.3', 35 | 'Programming Language :: Python :: 3.4', 36 | 'Programming Language :: Python :: 3.5', 37 | ], 38 | keywords='', 39 | entry_points={ 40 | 'console_scripts': ['sat-search=satsearch.cli:cli'], 41 | }, 42 | packages=find_packages(exclude=['docs', 'tests*']), 43 | include_package_data=True, 44 | install_requires=install_requires, 45 | dependency_links=dependency_links, 46 | setup_requires=['pytest-runner'], 47 | tests_require=['pytest'], 48 | ) 49 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import logging 3 | 4 | logging.getLogger('urllib3').propagate = False 5 | logging.basicConfig(stream=sys.stdout, level=logging.CRITICAL) 6 | -------------------------------------------------------------------------------- /test/aoi1.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Feature", 3 | "properties": {}, 4 | "geometry": { 5 | "type": "Polygon", 6 | "coordinates": [ 7 | [ 8 | [ 9 | -66.3958740234375, 10 | 43.305193797650546 11 | ], 12 | [ 13 | -64.390869140625, 14 | 43.305193797650546 15 | ], 16 | [ 17 | -64.390869140625, 18 | 44.22945656830167 19 | ], 20 | [ 21 | -66.3958740234375, 22 | 44.22945656830167 23 | ], 24 | [ 25 | -66.3958740234375, 26 | 43.305193797650546 27 | ] 28 | ] 29 | ] 30 | } 31 | } -------------------------------------------------------------------------------- /test/aoi2.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Feature", 3 | "properties": {}, 4 | "geometry": { 5 | "type": "Polygon", 6 | "coordinates": [ 7 | [ 8 | [ 9 | 11.984710693359375, 10 | 44.815941348210835 11 | ], 12 | [ 13 | 12.752380371093748, 14 | 44.815941348210835 15 | ], 16 | [ 17 | 12.752380371093748, 18 | 45.67740123855739 19 | ], 20 | [ 21 | 11.984710693359375, 22 | 45.67740123855739 23 | ], 24 | [ 25 | 11.984710693359375, 26 | 44.815941348210835 27 | ] 28 | ] 29 | ] 30 | } 31 | } -------------------------------------------------------------------------------- /test/landsat-item1.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Feature", 3 | "id": "LC81692212019263", 4 | "bbox": [ 5 | -173.08024, 6 | 52.00372, 7 | -169.43115, 8 | 54.17563 9 | ], 10 | "geometry": { 11 | "type": "Polygon", 12 | "coordinates": [ 13 | [ 14 | [ 15 | -169.43509345268382, 16 | 52.480608048464546 17 | ], 18 | [ 19 | -172.2580177302994, 20 | 52.00576775847521 21 | ], 22 | [ 23 | -173.07835274956315, 24 | 53.70820214108535 25 | ], 26 | [ 27 | -170.24839015990904, 28 | 54.17408690459604 29 | ], 30 | [ 31 | -169.43509345268382, 32 | 52.480608048464546 33 | ] 34 | ] 35 | ] 36 | }, 37 | "properties": { 38 | "collection": "landsat-8-l1", 39 | "eo:gsd": 15, 40 | "eo:platform": "landsat-8", 41 | "eo:instrument": "OLI_TIRS", 42 | "eo:off_nadir": 0, 43 | "eo:bands": [ 44 | { 45 | "name": "B1", 46 | "common_name": "coastal", 47 | "gsd": 30, 48 | "center_wavelength": 0.44, 49 | "full_width_half_max": 0.02 50 | }, 51 | { 52 | "name": "B2", 53 | "common_name": "blue", 54 | "gsd": 30, 55 | "center_wavelength": 0.48, 56 | "full_width_half_max": 0.06 57 | }, 58 | { 59 | "name": "B3", 60 | "common_name": "green", 61 | "gsd": 30, 62 | "center_wavelength": 0.56, 63 | "full_width_half_max": 0.06 64 | }, 65 | { 66 | "name": "B4", 67 | "common_name": "red", 68 | "gsd": 30, 69 | "center_wavelength": 0.65, 70 | "full_width_half_max": 0.04 71 | }, 72 | { 73 | "name": "B5", 74 | "common_name": "nir", 75 | "gsd": 30, 76 | "center_wavelength": 0.86, 77 | "full_width_half_max": 0.03 78 | }, 79 | { 80 | "name": "B6", 81 | "common_name": "swir16", 82 | "gsd": 30, 83 | "center_wavelength": 1.6, 84 | "full_width_half_max": 0.08 85 | }, 86 | { 87 | "name": "B7", 88 | "common_name": "swir22", 89 | "gsd": 30, 90 | "center_wavelength": 2.2, 91 | "full_width_half_max": 0.2 92 | }, 93 | { 94 | "name": "B8", 95 | "common_name": "pan", 96 | "gsd": 15, 97 | "center_wavelength": 0.59, 98 | "full_width_half_max": 0.18 99 | }, 100 | { 101 | "name": "B9", 102 | "common_name": "cirrus", 103 | "gsd": 30, 104 | "center_wavelength": 1.37, 105 | "full_width_half_max": 0.02 106 | }, 107 | { 108 | "name": "B10", 109 | "common_name": "lwir11", 110 | "gsd": 100, 111 | "center_wavelength": 10.9, 112 | "full_width_half_max": 0.8 113 | }, 114 | { 115 | "name": "B11", 116 | "common_name": "lwir12", 117 | "gsd": 100, 118 | "center_wavelength": 12, 119 | "full_width_half_max": 1 120 | } 121 | ], 122 | "datetime": "2019-09-20T08:53:31.493794+00:00", 123 | "eo:sun_azimuth": -41.99387147, 124 | "eo:sun_elevation": -27.90765031, 125 | "eo:cloud_cover": -1, 126 | "eo:row": "221", 127 | "eo:column": "169", 128 | "landsat:product_id": "LC08_L1GT_169221_20190920_20190920_01_RT", 129 | "landsat:scene_id": "LC81692212019263LGN00", 130 | "landsat:processing_level": "L1GT", 131 | "landsat:tier": "RT", 132 | "landsat:revision": "00", 133 | "eo:epsg": 3262 134 | }, 135 | "assets": { 136 | "index": { 137 | "type": "text/html", 138 | "title": "HTML index page", 139 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/221/LC08_L1GT_169221_20190920_20190920_01_RT/LC08_L1GT_169221_20190920_20190920_01_RT_MTL.txt" 140 | }, 141 | "thumbnail": { 142 | "title": "Thumbnail image", 143 | "type": "image/jpeg", 144 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/221/LC08_L1GT_169221_20190920_20190920_01_RT/LC08_L1GT_169221_20190920_20190920_01_RT_thumb_large.jpg" 145 | }, 146 | "B1": { 147 | "type": "image/x.geotiff", 148 | "eo:bands": [ 149 | 0 150 | ], 151 | "title": "Band 1 (coastal)", 152 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/221/LC08_L1GT_169221_20190920_20190920_01_RT/LC08_L1GT_169221_20190920_20190920_01_RT_B1.TIF" 153 | }, 154 | "B2": { 155 | "type": "image/x.geotiff", 156 | "eo:bands": [ 157 | 1 158 | ], 159 | "title": "Band 2 (blue)", 160 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/221/LC08_L1GT_169221_20190920_20190920_01_RT/LC08_L1GT_169221_20190920_20190920_01_RT_B2.TIF" 161 | }, 162 | "B3": { 163 | "type": "image/x.geotiff", 164 | "eo:bands": [ 165 | 2 166 | ], 167 | "title": "Band 3 (green)", 168 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/221/LC08_L1GT_169221_20190920_20190920_01_RT/LC08_L1GT_169221_20190920_20190920_01_RT_B3.TIF" 169 | }, 170 | "B4": { 171 | "type": "image/x.geotiff", 172 | "eo:bands": [ 173 | 3 174 | ], 175 | "title": "Band 4 (red)", 176 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/221/LC08_L1GT_169221_20190920_20190920_01_RT/LC08_L1GT_169221_20190920_20190920_01_RT_B4.TIF" 177 | }, 178 | "B5": { 179 | "type": "image/x.geotiff", 180 | "eo:bands": [ 181 | 4 182 | ], 183 | "title": "Band 5 (nir)", 184 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/221/LC08_L1GT_169221_20190920_20190920_01_RT/LC08_L1GT_169221_20190920_20190920_01_RT_B5.TIF" 185 | }, 186 | "B6": { 187 | "type": "image/x.geotiff", 188 | "eo:bands": [ 189 | 5 190 | ], 191 | "title": "Band 6 (swir16)", 192 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/221/LC08_L1GT_169221_20190920_20190920_01_RT/LC08_L1GT_169221_20190920_20190920_01_RT_B6.TIF" 193 | }, 194 | "B7": { 195 | "type": "image/x.geotiff", 196 | "eo:bands": [ 197 | 6 198 | ], 199 | "title": "Band 7 (swir22)", 200 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/221/LC08_L1GT_169221_20190920_20190920_01_RT/LC08_L1GT_169221_20190920_20190920_01_RT_B7.TIF" 201 | }, 202 | "B8": { 203 | "type": "image/x.geotiff", 204 | "eo:bands": [ 205 | 7 206 | ], 207 | "title": "Band 8 (pan)", 208 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/221/LC08_L1GT_169221_20190920_20190920_01_RT/LC08_L1GT_169221_20190920_20190920_01_RT_B8.TIF" 209 | }, 210 | "B9": { 211 | "type": "image/x.geotiff", 212 | "eo:bands": [ 213 | 8 214 | ], 215 | "title": "Band 9 (cirrus)", 216 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/221/LC08_L1GT_169221_20190920_20190920_01_RT/LC08_L1GT_169221_20190920_20190920_01_RT_B9.TIF" 217 | }, 218 | "B10": { 219 | "type": "image/x.geotiff", 220 | "eo:bands": [ 221 | 9 222 | ], 223 | "title": "Band 10 (lwir)", 224 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/221/LC08_L1GT_169221_20190920_20190920_01_RT/LC08_L1GT_169221_20190920_20190920_01_RT_B10.TIF" 225 | }, 226 | "B11": { 227 | "type": "image/x.geotiff", 228 | "eo:bands": [ 229 | 10 230 | ], 231 | "title": "Band 11 (lwir)", 232 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/221/LC08_L1GT_169221_20190920_20190920_01_RT/LC08_L1GT_169221_20190920_20190920_01_RT_B11.TIF" 233 | }, 234 | "ANG": { 235 | "title": "Angle coefficients file", 236 | "type": "text/plain", 237 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/221/LC08_L1GT_169221_20190920_20190920_01_RT/LC08_L1GT_169221_20190920_20190920_01_RT_ANG.txt" 238 | }, 239 | "MTL": { 240 | "title": "original metadata file", 241 | "type": "text/plain", 242 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/221/LC08_L1GT_169221_20190920_20190920_01_RT/LC08_L1GT_169221_20190920_20190920_01_RT_MTL.txt" 243 | }, 244 | "BQA": { 245 | "title": "Band quality data", 246 | "type": "image/x.geotiff", 247 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/221/LC08_L1GT_169221_20190920_20190920_01_RT/LC08_L1GT_169221_20190920_20190920_01_RT_BQA.TIF" 248 | } 249 | }, 250 | "links": [ 251 | { 252 | "rel": "self", 253 | "href": "https://earth-search.aws.element84.com/collections/landsat-8-l1/items/LC81692212019263" 254 | }, 255 | { 256 | "rel": "parent", 257 | "href": "https://earth-search.aws.element84.com/collections/landsat-8-l1" 258 | }, 259 | { 260 | "rel": "collection", 261 | "href": "https://earth-search.aws.element84.com/collections/landsat-8-l1" 262 | }, 263 | { 264 | "rel": "root", 265 | "href": "https://earth-search.aws.element84.com/stac" 266 | } 267 | ] 268 | } -------------------------------------------------------------------------------- /test/landsat-item2.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Feature", 3 | "id": "LC81691102019263", 4 | "bbox": [ 5 | 2.49764, 6 | -72.22691, 7 | 10.73227, 8 | -69.54865 9 | ], 10 | "geometry": { 11 | "type": "Polygon", 12 | "coordinates": [ 13 | [ 14 | [ 15 | 5.728700356172139, 16 | -69.55150325883106 17 | ], 18 | [ 19 | 10.724505278283958, 20 | -70.62787508933098 21 | ], 22 | [ 23 | 7.495515149535066, 24 | -72.22537395842913 25 | ], 26 | [ 27 | 2.505902050290957, 28 | -71.16167254656607 29 | ], 30 | [ 31 | 5.728700356172139, 32 | -69.55150325883106 33 | ] 34 | ] 35 | ] 36 | }, 37 | "properties": { 38 | "collection": "landsat-8-l1", 39 | "eo:gsd": 15, 40 | "eo:platform": "landsat-8", 41 | "eo:instrument": "OLI_TIRS", 42 | "eo:off_nadir": 0, 43 | "eo:bands": [ 44 | { 45 | "name": "B1", 46 | "common_name": "coastal", 47 | "gsd": 30, 48 | "center_wavelength": 0.44, 49 | "full_width_half_max": 0.02 50 | }, 51 | { 52 | "name": "B2", 53 | "common_name": "blue", 54 | "gsd": 30, 55 | "center_wavelength": 0.48, 56 | "full_width_half_max": 0.06 57 | }, 58 | { 59 | "name": "B3", 60 | "common_name": "green", 61 | "gsd": 30, 62 | "center_wavelength": 0.56, 63 | "full_width_half_max": 0.06 64 | }, 65 | { 66 | "name": "B4", 67 | "common_name": "red", 68 | "gsd": 30, 69 | "center_wavelength": 0.65, 70 | "full_width_half_max": 0.04 71 | }, 72 | { 73 | "name": "B5", 74 | "common_name": "nir", 75 | "gsd": 30, 76 | "center_wavelength": 0.86, 77 | "full_width_half_max": 0.03 78 | }, 79 | { 80 | "name": "B6", 81 | "common_name": "swir16", 82 | "gsd": 30, 83 | "center_wavelength": 1.6, 84 | "full_width_half_max": 0.08 85 | }, 86 | { 87 | "name": "B7", 88 | "common_name": "swir22", 89 | "gsd": 30, 90 | "center_wavelength": 2.2, 91 | "full_width_half_max": 0.2 92 | }, 93 | { 94 | "name": "B8", 95 | "common_name": "pan", 96 | "gsd": 15, 97 | "center_wavelength": 0.59, 98 | "full_width_half_max": 0.18 99 | }, 100 | { 101 | "name": "B9", 102 | "common_name": "cirrus", 103 | "gsd": 30, 104 | "center_wavelength": 1.37, 105 | "full_width_half_max": 0.02 106 | }, 107 | { 108 | "name": "B10", 109 | "common_name": "lwir11", 110 | "gsd": 100, 111 | "center_wavelength": 10.9, 112 | "full_width_half_max": 0.8 113 | }, 114 | { 115 | "name": "B11", 116 | "common_name": "lwir12", 117 | "gsd": 100, 118 | "center_wavelength": 12, 119 | "full_width_half_max": 1 120 | } 121 | ], 122 | "datetime": "2019-09-20T08:09:14.090119+00:00", 123 | "eo:sun_azimuth": 50.98693016, 124 | "eo:sun_elevation": 11.08298526, 125 | "eo:cloud_cover": 0, 126 | "eo:row": "110", 127 | "eo:column": "169", 128 | "landsat:product_id": "LC08_L1GT_169110_20190920_20190920_01_RT", 129 | "landsat:scene_id": "LC81691102019263LGN00", 130 | "landsat:processing_level": "L1GT", 131 | "landsat:tier": "RT", 132 | "landsat:revision": "00" 133 | }, 134 | "assets": { 135 | "index": { 136 | "type": "text/html", 137 | "title": "HTML index page", 138 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/110/LC08_L1GT_169110_20190920_20190920_01_RT/LC08_L1GT_169110_20190920_20190920_01_RT_MTL.txt" 139 | }, 140 | "thumbnail": { 141 | "title": "Thumbnail image", 142 | "type": "image/jpeg", 143 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/110/LC08_L1GT_169110_20190920_20190920_01_RT/LC08_L1GT_169110_20190920_20190920_01_RT_thumb_large.jpg" 144 | }, 145 | "B1": { 146 | "type": "image/x.geotiff", 147 | "eo:bands": [ 148 | 0 149 | ], 150 | "title": "Band 1 (coastal)", 151 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/110/LC08_L1GT_169110_20190920_20190920_01_RT/LC08_L1GT_169110_20190920_20190920_01_RT_B1.TIF" 152 | }, 153 | "B2": { 154 | "type": "image/x.geotiff", 155 | "eo:bands": [ 156 | 1 157 | ], 158 | "title": "Band 2 (blue)", 159 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/110/LC08_L1GT_169110_20190920_20190920_01_RT/LC08_L1GT_169110_20190920_20190920_01_RT_B2.TIF" 160 | }, 161 | "B3": { 162 | "type": "image/x.geotiff", 163 | "eo:bands": [ 164 | 2 165 | ], 166 | "title": "Band 3 (green)", 167 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/110/LC08_L1GT_169110_20190920_20190920_01_RT/LC08_L1GT_169110_20190920_20190920_01_RT_B3.TIF" 168 | }, 169 | "B4": { 170 | "type": "image/x.geotiff", 171 | "eo:bands": [ 172 | 3 173 | ], 174 | "title": "Band 4 (red)", 175 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/110/LC08_L1GT_169110_20190920_20190920_01_RT/LC08_L1GT_169110_20190920_20190920_01_RT_B4.TIF" 176 | }, 177 | "B5": { 178 | "type": "image/x.geotiff", 179 | "eo:bands": [ 180 | 4 181 | ], 182 | "title": "Band 5 (nir)", 183 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/110/LC08_L1GT_169110_20190920_20190920_01_RT/LC08_L1GT_169110_20190920_20190920_01_RT_B5.TIF" 184 | }, 185 | "B6": { 186 | "type": "image/x.geotiff", 187 | "eo:bands": [ 188 | 5 189 | ], 190 | "title": "Band 6 (swir16)", 191 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/110/LC08_L1GT_169110_20190920_20190920_01_RT/LC08_L1GT_169110_20190920_20190920_01_RT_B6.TIF" 192 | }, 193 | "B7": { 194 | "type": "image/x.geotiff", 195 | "eo:bands": [ 196 | 6 197 | ], 198 | "title": "Band 7 (swir22)", 199 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/110/LC08_L1GT_169110_20190920_20190920_01_RT/LC08_L1GT_169110_20190920_20190920_01_RT_B7.TIF" 200 | }, 201 | "B8": { 202 | "type": "image/x.geotiff", 203 | "eo:bands": [ 204 | 7 205 | ], 206 | "title": "Band 8 (pan)", 207 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/110/LC08_L1GT_169110_20190920_20190920_01_RT/LC08_L1GT_169110_20190920_20190920_01_RT_B8.TIF" 208 | }, 209 | "B9": { 210 | "type": "image/x.geotiff", 211 | "eo:bands": [ 212 | 8 213 | ], 214 | "title": "Band 9 (cirrus)", 215 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/110/LC08_L1GT_169110_20190920_20190920_01_RT/LC08_L1GT_169110_20190920_20190920_01_RT_B9.TIF" 216 | }, 217 | "B10": { 218 | "type": "image/x.geotiff", 219 | "eo:bands": [ 220 | 9 221 | ], 222 | "title": "Band 10 (lwir)", 223 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/110/LC08_L1GT_169110_20190920_20190920_01_RT/LC08_L1GT_169110_20190920_20190920_01_RT_B10.TIF" 224 | }, 225 | "B11": { 226 | "type": "image/x.geotiff", 227 | "eo:bands": [ 228 | 10 229 | ], 230 | "title": "Band 11 (lwir)", 231 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/110/LC08_L1GT_169110_20190920_20190920_01_RT/LC08_L1GT_169110_20190920_20190920_01_RT_B11.TIF" 232 | }, 233 | "ANG": { 234 | "title": "Angle coefficients file", 235 | "type": "text/plain", 236 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/110/LC08_L1GT_169110_20190920_20190920_01_RT/LC08_L1GT_169110_20190920_20190920_01_RT_ANG.txt" 237 | }, 238 | "MTL": { 239 | "title": "original metadata file", 240 | "type": "text/plain", 241 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/110/LC08_L1GT_169110_20190920_20190920_01_RT/LC08_L1GT_169110_20190920_20190920_01_RT_MTL.txt" 242 | }, 243 | "BQA": { 244 | "title": "Band quality data", 245 | "type": "image/x.geotiff", 246 | "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/169/110/LC08_L1GT_169110_20190920_20190920_01_RT/LC08_L1GT_169110_20190920_20190920_01_RT_BQA.TIF" 247 | } 248 | }, 249 | "links": [ 250 | { 251 | "rel": "self", 252 | "href": "https://earth-search.aws.element84.com/collections/landsat-8-l1/items/LC81691102019263" 253 | }, 254 | { 255 | "rel": "parent", 256 | "href": "https://earth-search.aws.element84.com/collections/landsat-8-l1" 257 | }, 258 | { 259 | "rel": "collection", 260 | "href": "https://earth-search.aws.element84.com/collections/landsat-8-l1" 261 | }, 262 | { 263 | "rel": "root", 264 | "href": "https://earth-search.aws.element84.com/stac" 265 | } 266 | ] 267 | } -------------------------------------------------------------------------------- /test/query.json: -------------------------------------------------------------------------------- 1 | { 2 | "page": 1, 3 | "limit": 0, 4 | "intersects": { 5 | "type": "Feature", 6 | "properties": {}, 7 | "geometry": { 8 | "type": "Polygon", 9 | "coordinates": [ 10 | [ 11 | [ 12 | -66.3958740234375, 13 | 43.305193797650546 14 | ], 15 | [ 16 | -64.390869140625, 17 | 43.305193797650546 18 | ], 19 | [ 20 | -64.390869140625, 21 | 44.22945656830167 22 | ], 23 | [ 24 | -66.3958740234375, 25 | 44.22945656830167 26 | ], 27 | [ 28 | -66.3958740234375, 29 | 43.305193797650546 30 | ] 31 | ] 32 | ] 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /test/scenes.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "id": "S2B_19TCH_20180209_0", 7 | "bbox": [ 8 | -71.46676936182894, 9 | 42.338371079679106, 10 | -70.09532154452742, 11 | 43.347431265475954 12 | ], 13 | "geometry": { 14 | "type": "Polygon", 15 | "coordinates": [ 16 | [ 17 | [ 18 | -71.42776890002204, 19 | 42.338371079679106 20 | ], 21 | [ 22 | -71.46676936182894, 23 | 43.32623760511659 24 | ], 25 | [ 26 | -70.11293433656888, 27 | 43.347431265475954 28 | ], 29 | [ 30 | -70.09532154452742, 31 | 42.35884880571143 32 | ], 33 | [ 34 | -71.42776890002204, 35 | 42.338371079679106 36 | ] 37 | ] 38 | ] 39 | }, 40 | "properties": { 41 | "collection": "sentinel-2-l1c", 42 | "eo:gsd": 10, 43 | "eo:instrument": "MSI", 44 | "eo:off_nadir": 0, 45 | "eo:bands": [ 46 | { 47 | "name": "B01", 48 | "common_name": "coastal", 49 | "gsd": 60, 50 | "center_wavelength": 0.4439, 51 | "full_width_half_max": 0.027 52 | }, 53 | { 54 | "name": "B02", 55 | "common_name": "blue", 56 | "gsd": 10, 57 | "center_wavelength": 0.4966, 58 | "full_width_half_max": 0.098 59 | }, 60 | { 61 | "name": "B03", 62 | "common_name": "green", 63 | "gsd": 10, 64 | "center_wavelength": 0.56, 65 | "full_width_half_max": 0.045 66 | }, 67 | { 68 | "name": "B04", 69 | "common_name": "red", 70 | "gsd": 10, 71 | "center_wavelength": 0.6645, 72 | "full_width_half_max": 0.038 73 | }, 74 | { 75 | "name": "B05", 76 | "gsd": 20, 77 | "center_wavelength": 0.7039, 78 | "full_width_half_max": 0.019 79 | }, 80 | { 81 | "name": "B06", 82 | "gsd": 20, 83 | "center_wavelength": 0.7402, 84 | "full_width_half_max": 0.018 85 | }, 86 | { 87 | "name": "B07", 88 | "gsd": 20, 89 | "center_wavelength": 0.7825, 90 | "full_width_half_max": 0.028 91 | }, 92 | { 93 | "name": "B08", 94 | "common_name": "nir", 95 | "gsd": 10, 96 | "center_wavelength": 0.8351, 97 | "full_width_half_max": 0.145 98 | }, 99 | { 100 | "name": "B8A", 101 | "gsd": 20, 102 | "center_wavelength": 0.8648, 103 | "full_width_half_max": 0.033 104 | }, 105 | { 106 | "name": "B09", 107 | "gsd": 60, 108 | "center_wavelength": 0.945, 109 | "full_width_half_max": 0.026 110 | }, 111 | { 112 | "name": "B10", 113 | "common_name": "cirrus", 114 | "gsd": 60, 115 | "center_wavelength": 1.3735, 116 | "full_width_half_max": 0.075 117 | }, 118 | { 119 | "name": "B11", 120 | "common_name": "swir16", 121 | "gsd": 20, 122 | "center_wavelength": 1.6137, 123 | "full_width_half_max": 0.143 124 | }, 125 | { 126 | "name": "B12", 127 | "common_name": "swir22", 128 | "gsd": 20, 129 | "center_wavelength": 2.22024, 130 | "full_width_half_max": 0.242 131 | } 132 | ], 133 | "datetime": "2018-02-09T15:35:49.460000+00:00", 134 | "eo:platform": "sentinel-2b", 135 | "eo:cloud_cover": 63.87, 136 | "sentinel:utm_zone": 19, 137 | "sentinel:latitude_band": "T", 138 | "sentinel:grid_square": "CH", 139 | "sentinel:sequence": "0", 140 | "sentinel:product_id": "S2B_MSIL1C_20180209T153549_N0206_R111_T19TCH_20180209T173152" 141 | }, 142 | "assets": { 143 | "thumbnail": { 144 | "title": "Thumbnail", 145 | "href": "https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/19/T/CH/2018/2/9/0/preview.jpg" 146 | }, 147 | "info": { 148 | "title": "Basic JSON metadata", 149 | "href": "https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/19/T/CH/2018/2/9/0/tileInfo.json" 150 | }, 151 | "metadata": { 152 | "title": "Complete XML metadata", 153 | "href": "https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/19/T/CH/2018/2/9/0/metadata.xml" 154 | }, 155 | "tki": { 156 | "title": "True color image", 157 | "type": "image/jp2", 158 | "eo:bands": [ 159 | 3, 160 | 2, 161 | 1 162 | ], 163 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/19/T/CH/2018/2/9/0/TKI.jp2" 164 | }, 165 | "B01": { 166 | "title": "Band 1 (coastal)", 167 | "type": "image/jp2", 168 | "eo:bands": [ 169 | 0 170 | ], 171 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/19/T/CH/2018/2/9/0/B01.TIF" 172 | }, 173 | "B02": { 174 | "title": "Band 2 (blue)", 175 | "type": "image/jp2", 176 | "eo:bands": [ 177 | 2 178 | ], 179 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/19/T/CH/2018/2/9/0/B02.TIF" 180 | }, 181 | "B03": { 182 | "title": "Band 3 (green)", 183 | "type": "image/jp2", 184 | "eo:bands": [ 185 | 2 186 | ], 187 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/19/T/CH/2018/2/9/0/B03.TIF" 188 | }, 189 | "B04": { 190 | "title": "Band 4 (red)", 191 | "type": "image/jp2", 192 | "eo:bands": [ 193 | 3 194 | ], 195 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/19/T/CH/2018/2/9/0/B04.TIF" 196 | }, 197 | "B05": { 198 | "title": "Band 5", 199 | "type": "image/jp2", 200 | "eo:bands": [ 201 | 4 202 | ], 203 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/19/T/CH/2018/2/9/0/B05.TIF" 204 | }, 205 | "B06": { 206 | "title": "Band 6", 207 | "type": "image/jp2", 208 | "eo:bands": [ 209 | 5 210 | ], 211 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/19/T/CH/2018/2/9/0/B06.TIF" 212 | }, 213 | "B07": { 214 | "title": "Band 7", 215 | "type": "image/jp2", 216 | "eo:bands": [ 217 | 6 218 | ], 219 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/19/T/CH/2018/2/9/0/B07.TIF" 220 | }, 221 | "B08": { 222 | "title": "Band 8 (nir)", 223 | "type": "image/jp2", 224 | "eo:bands": [ 225 | 7 226 | ], 227 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/19/T/CH/2018/2/9/0/B08.TIF" 228 | }, 229 | "B8A": { 230 | "title": "Band 8A", 231 | "type": "image/jp2", 232 | "eo:bands": [ 233 | 8 234 | ], 235 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/19/T/CH/2018/2/9/0/B08.TIF" 236 | }, 237 | "B09": { 238 | "title": "Band 9", 239 | "type": "image/jp2", 240 | "eo:bands": [ 241 | 9 242 | ], 243 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/19/T/CH/2018/2/9/0/B09.TIF" 244 | }, 245 | "B10": { 246 | "title": "Band 10 (cirrus)", 247 | "type": "image/jp2", 248 | "eo:bands": [ 249 | 10 250 | ], 251 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/19/T/CH/2018/2/9/0/B10.TIF" 252 | }, 253 | "B11": { 254 | "title": "Band 11 (swir16)", 255 | "type": "image/jp2", 256 | "eo:bands": [ 257 | 11 258 | ], 259 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/19/T/CH/2018/2/9/0/B11.TIF" 260 | }, 261 | "B12": { 262 | "title": "Band 12 (swir22)", 263 | "type": "image/jp2", 264 | "eo:bands": [ 265 | 12 266 | ], 267 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/19/T/CH/2018/2/9/0/B11.TIF" 268 | } 269 | }, 270 | "links": [ 271 | { 272 | "rel": "self", 273 | "href": "https://n34f767n91.execute-api.us-east-1.amazonaws.com/prod/collections/sentinel-2-l1c/items/S2B_19TCH_20180209_0" 274 | }, 275 | { 276 | "rel": "parent", 277 | "href": "https://n34f767n91.execute-api.us-east-1.amazonaws.com/prod/collections/sentinel-2-l1c" 278 | }, 279 | { 280 | "rel": "collection", 281 | "href": "https://n34f767n91.execute-api.us-east-1.amazonaws.com/prod/collections/sentinel-2-l1c" 282 | }, 283 | { 284 | "rel": "root", 285 | "href": "https://n34f767n91.execute-api.us-east-1.amazonaws.com/prod/stac" 286 | } 287 | ] 288 | }, 289 | { 290 | "type": "Feature", 291 | "id": "LC80120302018040LGN00", 292 | "bbox": [ 293 | -72.35676, 294 | 42.06972, 295 | -69.37983, 296 | 44.27597 297 | ], 298 | "geometry": { 299 | "type": "Polygon", 300 | "coordinates": [ 301 | [ 302 | [ 303 | -71.70427065474362, 304 | 44.27323464128468 305 | ], 306 | [ 307 | -69.38341470057205, 308 | 43.81273612340411 309 | ], 310 | [ 311 | -70.03551650243377, 312 | 42.07132214395075 313 | ], 314 | [ 315 | -72.3535639979816, 316 | 42.539143195930194 317 | ], 318 | [ 319 | -71.70427065474362, 320 | 44.27323464128468 321 | ] 322 | ] 323 | ] 324 | }, 325 | "properties": { 326 | "collection": "landsat-8-l1", 327 | "datetime": "2018-02-09T15:26:33.244458+00:00", 328 | "eo:sun_azimuth": 154.76501101, 329 | "eo:sun_elevation": 28.61363289, 330 | "eo:cloud_cover": 69, 331 | "eo:row": "030", 332 | "eo:column": "012", 333 | "landsat:product_id": "LC08_L1TP_012030_20180209_20180222_01_T1", 334 | "landsat:scene_id": "LC80120302018040LGN00", 335 | "landsat:processing_level": "L1TP", 336 | "landsat:tier": "T1", 337 | "eo:epsg": 32619, 338 | "eo:instrument": "OLI_TIRS", 339 | "eo:off_nadir": 0, 340 | "eo:platform": "landsat-8", 341 | "eo:bands": [ 342 | { 343 | "full_width_half_max": 0.02, 344 | "center_wavelength": 0.44, 345 | "name": "B1", 346 | "gsd": 30, 347 | "common_name": "coastal" 348 | }, 349 | { 350 | "full_width_half_max": 0.06, 351 | "center_wavelength": 0.48, 352 | "name": "B2", 353 | "gsd": 30, 354 | "common_name": "blue" 355 | }, 356 | { 357 | "full_width_half_max": 0.06, 358 | "center_wavelength": 0.56, 359 | "name": "B3", 360 | "gsd": 30, 361 | "common_name": "green" 362 | }, 363 | { 364 | "full_width_half_max": 0.04, 365 | "center_wavelength": 0.65, 366 | "name": "B4", 367 | "gsd": 30, 368 | "common_name": "red" 369 | }, 370 | { 371 | "full_width_half_max": 0.03, 372 | "center_wavelength": 0.86, 373 | "name": "B5", 374 | "gsd": 30, 375 | "common_name": "nir" 376 | }, 377 | { 378 | "full_width_half_max": 0.08, 379 | "center_wavelength": 1.6, 380 | "name": "B6", 381 | "gsd": 30, 382 | "common_name": "swir16" 383 | }, 384 | { 385 | "full_width_half_max": 0.2, 386 | "center_wavelength": 2.2, 387 | "name": "B7", 388 | "gsd": 30, 389 | "common_name": "swir22" 390 | }, 391 | { 392 | "full_width_half_max": 0.18, 393 | "center_wavelength": 0.59, 394 | "name": "B8", 395 | "gsd": 15, 396 | "common_name": "pan" 397 | }, 398 | { 399 | "full_width_half_max": 0.02, 400 | "center_wavelength": 1.37, 401 | "name": "B9", 402 | "gsd": 30, 403 | "common_name": "cirrus" 404 | }, 405 | { 406 | "full_width_half_max": 0.8, 407 | "center_wavelength": 10.9, 408 | "name": "B10", 409 | "gsd": 100, 410 | "common_name": "lwir11" 411 | }, 412 | { 413 | "full_width_half_max": 1, 414 | "center_wavelength": 12, 415 | "name": "B11", 416 | "gsd": 100, 417 | "common_name": "lwir12" 418 | } 419 | ], 420 | "eo:gsd": 15 421 | }, 422 | "assets": { 423 | "index": { 424 | "type": "text/html", 425 | "title": "HTML index page", 426 | "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/012/030/LC08_L1TP_012030_20180209_20180222_01_T1/index.html" 427 | }, 428 | "thumbnail": { 429 | "title": "Thumbnail image", 430 | "type": "image/jpeg", 431 | "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/012/030/LC08_L1TP_012030_20180209_20180222_01_T1/LC08_L1TP_012030_20180209_20180222_01_T1_thumb_large.jpg" 432 | }, 433 | "B1": { 434 | "type": "image/x.geotiff", 435 | "eo:bands": [ 436 | 0 437 | ], 438 | "title": "Band 1 (coastal)", 439 | "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/012/030/LC08_L1TP_012030_20180209_20180222_01_T1/LC08_L1TP_012030_20180209_20180222_01_T1_B1.TIF" 440 | }, 441 | "B2": { 442 | "type": "image/x.geotiff", 443 | "eo:bands": [ 444 | 1 445 | ], 446 | "title": "Band 2 (blue)", 447 | "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/012/030/LC08_L1TP_012030_20180209_20180222_01_T1/LC08_L1TP_012030_20180209_20180222_01_T1_B2.TIF" 448 | }, 449 | "B3": { 450 | "type": "image/x.geotiff", 451 | "eo:bands": [ 452 | 2 453 | ], 454 | "title": "Band 3 (green)", 455 | "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/012/030/LC08_L1TP_012030_20180209_20180222_01_T1/LC08_L1TP_012030_20180209_20180222_01_T1_B3.TIF" 456 | }, 457 | "B4": { 458 | "type": "image/x.geotiff", 459 | "eo:bands": [ 460 | 3 461 | ], 462 | "title": "Band 4 (red)", 463 | "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/012/030/LC08_L1TP_012030_20180209_20180222_01_T1/LC08_L1TP_012030_20180209_20180222_01_T1_B4.TIF" 464 | }, 465 | "B5": { 466 | "type": "image/x.geotiff", 467 | "eo:bands": [ 468 | 4 469 | ], 470 | "title": "Band 5 (nir)", 471 | "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/012/030/LC08_L1TP_012030_20180209_20180222_01_T1/LC08_L1TP_012030_20180209_20180222_01_T1_B5.TIF" 472 | }, 473 | "B6": { 474 | "type": "image/x.geotiff", 475 | "eo:bands": [ 476 | 5 477 | ], 478 | "title": "Band 6 (swir16)", 479 | "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/012/030/LC08_L1TP_012030_20180209_20180222_01_T1/LC08_L1TP_012030_20180209_20180222_01_T1_B6.TIF" 480 | }, 481 | "B7": { 482 | "type": "image/x.geotiff", 483 | "eo:bands": [ 484 | 6 485 | ], 486 | "title": "Band 7 (swir22)", 487 | "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/012/030/LC08_L1TP_012030_20180209_20180222_01_T1/LC08_L1TP_012030_20180209_20180222_01_T1_B7.TIF" 488 | }, 489 | "B8": { 490 | "type": "image/x.geotiff", 491 | "eo:bands": [ 492 | 7 493 | ], 494 | "title": "Band 8 (pan)", 495 | "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/012/030/LC08_L1TP_012030_20180209_20180222_01_T1/LC08_L1TP_012030_20180209_20180222_01_T1_B8.TIF" 496 | }, 497 | "B9": { 498 | "type": "image/x.geotiff", 499 | "eo:bands": [ 500 | 8 501 | ], 502 | "title": "Band 9 (cirrus)", 503 | "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/012/030/LC08_L1TP_012030_20180209_20180222_01_T1/LC08_L1TP_012030_20180209_20180222_01_T1_B9.TIF" 504 | }, 505 | "B10": { 506 | "type": "image/x.geotiff", 507 | "eo:bands": [ 508 | 9 509 | ], 510 | "title": "Band 10 (lwir)", 511 | "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/012/030/LC08_L1TP_012030_20180209_20180222_01_T1/LC08_L1TP_012030_20180209_20180222_01_T1_B10.TIF" 512 | }, 513 | "B11": { 514 | "type": "image/x.geotiff", 515 | "eo:bands": [ 516 | 10 517 | ], 518 | "title": "Band 11 (lwir)", 519 | "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/012/030/LC08_L1TP_012030_20180209_20180222_01_T1/LC08_L1TP_012030_20180209_20180222_01_T1_B11.TIF" 520 | }, 521 | "ANG": { 522 | "title": "Angle coefficients file", 523 | "type": "text/plain", 524 | "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/012/030/LC08_L1TP_012030_20180209_20180222_01_T1/LC08_L1TP_012030_20180209_20180222_01_T1_ANG.txt" 525 | }, 526 | "MTL": { 527 | "title": "original metadata file", 528 | "type": "text/plain", 529 | "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/012/030/LC08_L1TP_012030_20180209_20180222_01_T1/LC08_L1TP_012030_20180209_20180222_01_T1_MTL.txt" 530 | }, 531 | "BQA": { 532 | "title": "Band quality data", 533 | "type": "image/x.geotiff", 534 | "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/012/030/LC08_L1TP_012030_20180209_20180222_01_T1/LC08_L1TP_012030_20180209_20180222_01_T1_BQA.TIF" 535 | } 536 | }, 537 | "links": [ 538 | { 539 | "rel": "self", 540 | "href": "https://n34f767n91.execute-api.us-east-1.amazonaws.com/prod/collections/landsat-8-l1/items/LC80120302018040LGN00" 541 | }, 542 | { 543 | "rel": "parent", 544 | "href": "https://n34f767n91.execute-api.us-east-1.amazonaws.com/prod/collections/landsat-8-l1" 545 | }, 546 | { 547 | "rel": "collection", 548 | "href": "https://n34f767n91.execute-api.us-east-1.amazonaws.com/prod/collections/landsat-8-l1" 549 | }, 550 | { 551 | "rel": "root", 552 | "href": "https://n34f767n91.execute-api.us-east-1.amazonaws.com/prod/stac" 553 | } 554 | ] 555 | } 556 | ], 557 | "collections": [ 558 | { 559 | "id": "sentinel-2-l1c", 560 | "title": "Sentinel 2 L1C", 561 | "description": "Sentinel-2a and Sentinel-2b imagery", 562 | "keywords": [ 563 | "sentinel", 564 | "earth observation", 565 | "esa" 566 | ], 567 | "version": "0.1.0", 568 | "stac_version": "0.6.0", 569 | "extent": { 570 | "spatial": [ 571 | -180, 572 | -90, 573 | 180, 574 | 90 575 | ], 576 | "temporal": [ 577 | "2013-06-01", 578 | null 579 | ] 580 | }, 581 | "providers": [ 582 | { 583 | "name": "ESA", 584 | "roles": [ 585 | "producer" 586 | ], 587 | "url": "https://earth.esa.int/web/guest/home" 588 | }, 589 | { 590 | "name": "Synergise", 591 | "roles": [ 592 | "processor" 593 | ], 594 | "url": "https://registry.opendata.aws/sentinel-2/" 595 | }, 596 | { 597 | "name": "AWS", 598 | "roles": [ 599 | "host" 600 | ], 601 | "url": "http://sentinel-pds.s3-website.eu-central-1.amazonaws.com/" 602 | }, 603 | { 604 | "name": "Development Seed", 605 | "roles": [ 606 | "processor" 607 | ], 608 | "url": "https://github.com/sat-utils/sat-stac-sentinel" 609 | } 610 | ], 611 | "license": "proprietary", 612 | "properties": { 613 | "collection": "sentinel-2-l1c", 614 | "eo:gsd": 10, 615 | "eo:instrument": "MSI", 616 | "eo:off_nadir": 0, 617 | "eo:bands": [ 618 | { 619 | "name": "B01", 620 | "common_name": "coastal", 621 | "gsd": 60, 622 | "center_wavelength": 0.4439, 623 | "full_width_half_max": 0.027 624 | }, 625 | { 626 | "name": "B02", 627 | "common_name": "blue", 628 | "gsd": 10, 629 | "center_wavelength": 0.4966, 630 | "full_width_half_max": 0.098 631 | }, 632 | { 633 | "name": "B03", 634 | "common_name": "green", 635 | "gsd": 10, 636 | "center_wavelength": 0.56, 637 | "full_width_half_max": 0.045 638 | }, 639 | { 640 | "name": "B04", 641 | "common_name": "red", 642 | "gsd": 10, 643 | "center_wavelength": 0.6645, 644 | "full_width_half_max": 0.038 645 | }, 646 | { 647 | "name": "B05", 648 | "gsd": 20, 649 | "center_wavelength": 0.7039, 650 | "full_width_half_max": 0.019 651 | }, 652 | { 653 | "name": "B06", 654 | "gsd": 20, 655 | "center_wavelength": 0.7402, 656 | "full_width_half_max": 0.018 657 | }, 658 | { 659 | "name": "B07", 660 | "gsd": 20, 661 | "center_wavelength": 0.7825, 662 | "full_width_half_max": 0.028 663 | }, 664 | { 665 | "name": "B08", 666 | "common_name": "nir", 667 | "gsd": 10, 668 | "center_wavelength": 0.8351, 669 | "full_width_half_max": 0.145 670 | }, 671 | { 672 | "name": "B8A", 673 | "gsd": 20, 674 | "center_wavelength": 0.8648, 675 | "full_width_half_max": 0.033 676 | }, 677 | { 678 | "name": "B09", 679 | "gsd": 60, 680 | "center_wavelength": 0.945, 681 | "full_width_half_max": 0.026 682 | }, 683 | { 684 | "name": "B10", 685 | "common_name": "cirrus", 686 | "gsd": 60, 687 | "center_wavelength": 1.3735, 688 | "full_width_half_max": 0.075 689 | }, 690 | { 691 | "name": "B11", 692 | "common_name": "swir16", 693 | "gsd": 20, 694 | "center_wavelength": 1.6137, 695 | "full_width_half_max": 0.143 696 | }, 697 | { 698 | "name": "B12", 699 | "common_name": "swir22", 700 | "gsd": 20, 701 | "center_wavelength": 2.22024, 702 | "full_width_half_max": 0.242 703 | } 704 | ] 705 | }, 706 | "assets": { 707 | "thumbnail": { 708 | "title": "Thumbnail" 709 | }, 710 | "info": { 711 | "title": "Basic JSON metadata" 712 | }, 713 | "metadata": { 714 | "title": "Complete XML metadata" 715 | }, 716 | "tki": { 717 | "title": "True color image", 718 | "type": "image/jp2", 719 | "eo:bands": [ 720 | 3, 721 | 2, 722 | 1 723 | ] 724 | }, 725 | "B01": { 726 | "title": "Band 1 (coastal)", 727 | "type": "image/jp2", 728 | "eo:bands": [ 729 | 0 730 | ] 731 | }, 732 | "B02": { 733 | "title": "Band 2 (blue)", 734 | "type": "image/jp2", 735 | "eo:bands": [ 736 | 2 737 | ] 738 | }, 739 | "B03": { 740 | "title": "Band 3 (green)", 741 | "type": "image/jp2", 742 | "eo:bands": [ 743 | 2 744 | ] 745 | }, 746 | "B04": { 747 | "title": "Band 4 (red)", 748 | "type": "image/jp2", 749 | "eo:bands": [ 750 | 3 751 | ] 752 | }, 753 | "B05": { 754 | "title": "Band 5", 755 | "type": "image/jp2", 756 | "eo:bands": [ 757 | 4 758 | ] 759 | }, 760 | "B06": { 761 | "title": "Band 6", 762 | "type": "image/jp2", 763 | "eo:bands": [ 764 | 5 765 | ] 766 | }, 767 | "B07": { 768 | "title": "Band 7", 769 | "type": "image/jp2", 770 | "eo:bands": [ 771 | 6 772 | ] 773 | }, 774 | "B08": { 775 | "title": "Band 8 (nir)", 776 | "type": "image/jp2", 777 | "eo:bands": [ 778 | 7 779 | ] 780 | }, 781 | "B8A": { 782 | "title": "Band 8A", 783 | "type": "image/jp2", 784 | "eo:bands": [ 785 | 8 786 | ] 787 | }, 788 | "B09": { 789 | "title": "Band 9", 790 | "type": "image/jp2", 791 | "eo:bands": [ 792 | 9 793 | ] 794 | }, 795 | "B10": { 796 | "title": "Band 10 (cirrus)", 797 | "type": "image/jp2", 798 | "eo:bands": [ 799 | 10 800 | ] 801 | }, 802 | "B11": { 803 | "title": "Band 11 (swir16)", 804 | "type": "image/jp2", 805 | "eo:bands": [ 806 | 11 807 | ] 808 | }, 809 | "B12": { 810 | "title": "Band 12 (swir22)", 811 | "type": "image/jp2", 812 | "eo:bands": [ 813 | 12 814 | ] 815 | } 816 | }, 817 | "links": [ 818 | { 819 | "rel": "self", 820 | "href": "https://n34f767n91.execute-api.us-east-1.amazonaws.com/prod/collections/sentinel-2-l1c" 821 | }, 822 | { 823 | "rel": "parent", 824 | "href": "https://n34f767n91.execute-api.us-east-1.amazonaws.com/prod/stac" 825 | }, 826 | { 827 | "rel": "root", 828 | "href": "https://n34f767n91.execute-api.us-east-1.amazonaws.com/prod/stac" 829 | }, 830 | { 831 | "rel": "items", 832 | "href": "https://n34f767n91.execute-api.us-east-1.amazonaws.com/prod/collections/sentinel-2-l1c/items" 833 | } 834 | ] 835 | }, 836 | { 837 | "id": "landsat-8-l1", 838 | "title": "Landsat 8 L1", 839 | "description": "Landat 8 imagery radiometrically calibrated and orthorectified using gound points and Digital Elevation Model (DEM) data to correct relief displacement.", 840 | "keywords": [ 841 | "landsat", 842 | "earth observation", 843 | "usgs" 844 | ], 845 | "version": "0.1.0", 846 | "stac_version": "0.6.0", 847 | "extent": { 848 | "spatial": [ 849 | -180, 850 | -90, 851 | 180, 852 | 90 853 | ], 854 | "temporal": [ 855 | "2013-06-01", 856 | null 857 | ] 858 | }, 859 | "providers": [ 860 | { 861 | "name": "USGS", 862 | "roles": [ 863 | "producer" 864 | ], 865 | "url": "https://landsat.usgs.gov/" 866 | }, 867 | { 868 | "name": "Planet Labs", 869 | "roles": [ 870 | "processor" 871 | ], 872 | "url": "https://github.com/landsat-pds/landsat_ingestor" 873 | }, 874 | { 875 | "name": "AWS", 876 | "roles": [ 877 | "host" 878 | ], 879 | "url": "https://landsatonaws.com/" 880 | }, 881 | { 882 | "name": "Development Seed", 883 | "roles": [ 884 | "processor" 885 | ], 886 | "url": "https://github.com/sat-utils/sat-api" 887 | } 888 | ], 889 | "license": "PDDL-1.0", 890 | "properties": { 891 | "collection": "landsat-8-l1", 892 | "eo:gsd": 15, 893 | "eo:platform": "landsat-8", 894 | "eo:instrument": "OLI_TIRS", 895 | "eo:off_nadir": 0, 896 | "eo:bands": [ 897 | { 898 | "name": "B1", 899 | "common_name": "coastal", 900 | "gsd": 30, 901 | "center_wavelength": 0.44, 902 | "full_width_half_max": 0.02 903 | }, 904 | { 905 | "name": "B2", 906 | "common_name": "blue", 907 | "gsd": 30, 908 | "center_wavelength": 0.48, 909 | "full_width_half_max": 0.06 910 | }, 911 | { 912 | "name": "B3", 913 | "common_name": "green", 914 | "gsd": 30, 915 | "center_wavelength": 0.56, 916 | "full_width_half_max": 0.06 917 | }, 918 | { 919 | "name": "B4", 920 | "common_name": "red", 921 | "gsd": 30, 922 | "center_wavelength": 0.65, 923 | "full_width_half_max": 0.04 924 | }, 925 | { 926 | "name": "B5", 927 | "common_name": "nir", 928 | "gsd": 30, 929 | "center_wavelength": 0.86, 930 | "full_width_half_max": 0.03 931 | }, 932 | { 933 | "name": "B6", 934 | "common_name": "swir16", 935 | "gsd": 30, 936 | "center_wavelength": 1.6, 937 | "full_width_half_max": 0.08 938 | }, 939 | { 940 | "name": "B7", 941 | "common_name": "swir22", 942 | "gsd": 30, 943 | "center_wavelength": 2.2, 944 | "full_width_half_max": 0.2 945 | }, 946 | { 947 | "name": "B8", 948 | "common_name": "pan", 949 | "gsd": 15, 950 | "center_wavelength": 0.59, 951 | "full_width_half_max": 0.18 952 | }, 953 | { 954 | "name": "B9", 955 | "common_name": "cirrus", 956 | "gsd": 30, 957 | "center_wavelength": 1.37, 958 | "full_width_half_max": 0.02 959 | }, 960 | { 961 | "name": "B10", 962 | "common_name": "lwir11", 963 | "gsd": 100, 964 | "center_wavelength": 10.9, 965 | "full_width_half_max": 0.8 966 | }, 967 | { 968 | "name": "B11", 969 | "common_name": "lwir12", 970 | "gsd": 100, 971 | "center_wavelength": 12, 972 | "full_width_half_max": 1 973 | } 974 | ] 975 | }, 976 | "assets": { 977 | "index": { 978 | "type": "text/html", 979 | "title": "HTML index page" 980 | }, 981 | "thumbnail": { 982 | "title": "Thumbnail image", 983 | "type": "image/jpeg" 984 | }, 985 | "B1": { 986 | "type": "image/x.geotiff", 987 | "eo:bands": [ 988 | 0 989 | ], 990 | "title": "Band 1 (coastal)" 991 | }, 992 | "B2": { 993 | "type": "image/x.geotiff", 994 | "eo:bands": [ 995 | 1 996 | ], 997 | "title": "Band 2 (blue)" 998 | }, 999 | "B3": { 1000 | "type": "image/x.geotiff", 1001 | "eo:bands": [ 1002 | 2 1003 | ], 1004 | "title": "Band 3 (green)" 1005 | }, 1006 | "B4": { 1007 | "type": "image/x.geotiff", 1008 | "eo:bands": [ 1009 | 3 1010 | ], 1011 | "title": "Band 4 (red)" 1012 | }, 1013 | "B5": { 1014 | "type": "image/x.geotiff", 1015 | "eo:bands": [ 1016 | 4 1017 | ], 1018 | "title": "Band 5 (nir)" 1019 | }, 1020 | "B6": { 1021 | "type": "image/x.geotiff", 1022 | "eo:bands": [ 1023 | 5 1024 | ], 1025 | "title": "Band 6 (swir16)" 1026 | }, 1027 | "B7": { 1028 | "type": "image/x.geotiff", 1029 | "eo:bands": [ 1030 | 6 1031 | ], 1032 | "title": "Band 7 (swir22)" 1033 | }, 1034 | "B8": { 1035 | "type": "image/x.geotiff", 1036 | "eo:bands": [ 1037 | 7 1038 | ], 1039 | "title": "Band 8 (pan)" 1040 | }, 1041 | "B9": { 1042 | "type": "image/x.geotiff", 1043 | "eo:bands": [ 1044 | 8 1045 | ], 1046 | "title": "Band 9 (cirrus)" 1047 | }, 1048 | "B10": { 1049 | "type": "image/x.geotiff", 1050 | "eo:bands": [ 1051 | 9 1052 | ], 1053 | "title": "Band 10 (lwir)" 1054 | }, 1055 | "B11": { 1056 | "type": "image/x.geotiff", 1057 | "eo:bands": [ 1058 | 10 1059 | ], 1060 | "title": "Band 11 (lwir)" 1061 | }, 1062 | "ANG": { 1063 | "title": "Angle coefficients file", 1064 | "type": "text/plain" 1065 | }, 1066 | "MTL": { 1067 | "title": "original metadata file", 1068 | "type": "text/plain" 1069 | }, 1070 | "BQA": { 1071 | "title": "Band quality data", 1072 | "type": "image/x.geotiff" 1073 | } 1074 | }, 1075 | "links": [ 1076 | { 1077 | "rel": "self", 1078 | "href": "https://n34f767n91.execute-api.us-east-1.amazonaws.com/prod/collections/landsat-8-l1" 1079 | }, 1080 | { 1081 | "rel": "parent", 1082 | "href": "https://n34f767n91.execute-api.us-east-1.amazonaws.com/prod/stac" 1083 | }, 1084 | { 1085 | "rel": "root", 1086 | "href": "https://n34f767n91.execute-api.us-east-1.amazonaws.com/prod/stac" 1087 | }, 1088 | { 1089 | "rel": "items", 1090 | "href": "https://n34f767n91.execute-api.us-east-1.amazonaws.com/prod/collections/landsat-8-l1/items" 1091 | } 1092 | ] 1093 | } 1094 | ], 1095 | "search": { 1096 | "intersects": { 1097 | "type": "Feature", 1098 | "properties": {}, 1099 | "geometry": { 1100 | "type": "Polygon", 1101 | "coordinates": [ 1102 | [ 1103 | [ 1104 | -70.9503936767578, 1105 | 43.0287452513488 1106 | ], 1107 | [ 1108 | -70.78010559082031, 1109 | 43.0287452513488 1110 | ], 1111 | [ 1112 | -70.78010559082031, 1113 | 43.14258116631987 1114 | ], 1115 | [ 1116 | -70.9503936767578, 1117 | 43.14258116631987 1118 | ], 1119 | [ 1120 | -70.9503936767578, 1121 | 43.0287452513488 1122 | ] 1123 | ] 1124 | ] 1125 | } 1126 | }, 1127 | "time": "2018-02-09" 1128 | } 1129 | } -------------------------------------------------------------------------------- /test/sentinel-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Feature", 3 | "properties": { 4 | "id": "L1C_T49QBA_A006865_20180630T032902", 5 | "c:id": "sentinel-2-l1c", 6 | "datetime": "2018-06-30T03:29:02.990Z", 7 | "eo:platform": "Sentinel-2B", 8 | "eo:cloud_cover": 0, 9 | "eo:epsg": "32649", 10 | "sentinel:product_id": "S2B_MSIL1C_20180630T031539_N0206_R118_T49QBA_20180630T074227" 11 | }, 12 | "bbox": [ 13 | 108.15089336343961, 14 | 17.97943517464906, 15 | 108.69545292812572, 16 | 18.978165276750932 17 | ], 18 | "geometry": { 19 | "type": "Polygon", 20 | "coordinates": [ 21 | [ 22 | [ 23 | 108.15089336343961, 24 | 18.97058416379064 25 | ], 26 | [ 27 | 108.69545292812572, 28 | 18.978165276750932 29 | ], 30 | [ 31 | 108.57427698673041, 32 | 18.486901200780036 33 | ], 34 | [ 35 | 108.45112222544195, 36 | 17.983376887887612 37 | ], 38 | [ 39 | 108.16722825280434, 40 | 17.97943517464906 41 | ], 42 | [ 43 | 108.15089336343961, 44 | 18.97058416379064 45 | ] 46 | ] 47 | ] 48 | }, 49 | "assets": { 50 | "B01": { 51 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/49/Q/BA/2018/6/30/0/B01.jp2", 52 | "eo:bands": [ 53 | "B01" 54 | ] 55 | }, 56 | "B02": { 57 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/49/Q/BA/2018/6/30/0/B02.jp2", 58 | "eo:bands": [ 59 | "B02" 60 | ] 61 | }, 62 | "B03": { 63 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/49/Q/BA/2018/6/30/0/B03.jp2", 64 | "eo:bands": [ 65 | "B03" 66 | ] 67 | }, 68 | "B04": { 69 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/49/Q/BA/2018/6/30/0/B04.jp2", 70 | "eo:bands": [ 71 | "B04" 72 | ] 73 | }, 74 | "B05": { 75 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/49/Q/BA/2018/6/30/0/B05.jp2", 76 | "eo:bands": [ 77 | "B05" 78 | ] 79 | }, 80 | "B06": { 81 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/49/Q/BA/2018/6/30/0/B06.jp2", 82 | "eo:bands": [ 83 | "B06" 84 | ] 85 | }, 86 | "B07": { 87 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/49/Q/BA/2018/6/30/0/B07.jp2", 88 | "eo:bands": [ 89 | "B07" 90 | ] 91 | }, 92 | "B08": { 93 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/49/Q/BA/2018/6/30/0/B08.jp2", 94 | "eo:bands": [ 95 | "B08" 96 | ] 97 | }, 98 | "B09": { 99 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/49/Q/BA/2018/6/30/0/B09.jp2", 100 | "eo:bands": [ 101 | "B09" 102 | ] 103 | }, 104 | "B10": { 105 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/49/Q/BA/2018/6/30/0/B10.jp2", 106 | "eo:bands": [ 107 | "B10" 108 | ] 109 | }, 110 | "B11": { 111 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/49/Q/BA/2018/6/30/0/B11.jp2", 112 | "eo:bands": [ 113 | "B11" 114 | ] 115 | }, 116 | "B12": { 117 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/49/Q/BA/2018/6/30/0/B12.jp2", 118 | "eo:bands": [ 119 | "B12" 120 | ] 121 | }, 122 | "B8A": { 123 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/49/Q/BA/2018/6/30/0/B8A.jp2", 124 | "eo:bands": [ 125 | "B8A" 126 | ] 127 | }, 128 | "thumbnail": { 129 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/49/Q/BA/2018/6/30/0/preview.jpg" 130 | }, 131 | "tki": { 132 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/49/Q/BA/2018/6/30/0/TKI.jp2", 133 | "description": "True Color Image" 134 | }, 135 | "metadata": { 136 | "href": "https://sentinel-s2-l1c.s3.amazonaws.com/tiles/49/Q/BA/2018/6/30/0/metadata.xml" 137 | } 138 | }, 139 | "links": { 140 | "self": { 141 | "rel": "self", 142 | "href": "https://sat-api-dev.developmentseed.org/search/stac?id=L1C_T49QBA_A006865_20180630T032902" 143 | }, 144 | "collection": { 145 | "href": "https://sat-api-dev.developmentseed.org/collections/sentinel-2-l1c/definition" 146 | } 147 | } 148 | } -------------------------------------------------------------------------------- /test/test_cli.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | from unittest.mock import patch 5 | import json 6 | import shutil 7 | 8 | from satsearch.cli import main, SatUtilsParser, cli 9 | 10 | 11 | testpath = os.path.dirname(__file__) 12 | 13 | 14 | class Test(unittest.TestCase): 15 | """ Test main module """ 16 | 17 | @classmethod 18 | def get_test_parser(cls): 19 | """ Get testing parser with search and load subcommands """ 20 | parser = SatUtilsParser.newbie(description='sat-search testing') 21 | return parser 22 | 23 | def test_empty_parse_args(self): 24 | """ Parse empty arguments """ 25 | parser = self.get_test_parser() #import pdb; pdb.set_trace() 26 | with self.assertRaises(SystemExit): 27 | args = parser.parse_args([]) 28 | 29 | def test_empty_parse_search_args(self): 30 | """ Parse empty arguments """ 31 | parser = self.get_test_parser() 32 | args = parser.parse_args(['search']) 33 | self.assertEqual(len(args), 3) 34 | self.assertFalse(args['found']) 35 | 36 | def test_parse_args(self): 37 | """ Parse arguments """ 38 | parser = self.get_test_parser() 39 | args = 'search --datetime 2017-01-01 -q eo:cloud_cover<10 platform=sentinel-2a'.split(' ') 40 | 41 | args = parser.parse_args(args) 42 | self.assertEqual(len(args), 5) 43 | self.assertEqual(args['datetime'], '2017-01-01') 44 | #assert(args['eo:cloud_cover'] == '0/20') 45 | #self.assertEqual(args['cloud_from'], 0) 46 | #self.assertEqual(args['cloud_to'], 20) 47 | #self.assertEqual(args['satellite_name'], 'Landsat-8') 48 | #self.assertEqual(args['dayOrNight'], 'DAY') 49 | 50 | def _test_parse_args_badcloud(self): 51 | parser = self.get_test_parser() 52 | with self.assertRaises(ValueError): 53 | args = parser.parse_args('search --datetime 2017-01-01 -q platform=sentinel-2a'.split(' ')) 54 | 55 | def test_main(self): 56 | """ Run main function """ 57 | items = main(datetime='2020-01-01', collections=['sentinel-s2-l1c'], query=['eo:cloud_cover=0', 'data_coverage>80']) 58 | self.assertEqual(len(items), 207) 59 | 60 | def test_main_found(self): 61 | """ Run main function """ 62 | found = main(datetime='2020-01-01', found=True) 63 | min_found = 17819 64 | assert(found >= min_found) 65 | 66 | def test_main_load(self): 67 | items = main(items=os.path.join(testpath, 'scenes.geojson')) 68 | assert(len(items) == 2) 69 | 70 | def test_main_options(self): 71 | """ Test main program with output options """ 72 | fname = os.path.join(testpath, 'test_main-save.json') 73 | items = main(datetime='2020-01-01', save=fname, printcal=True, printmd=[], 74 | collections=['sentinel-s2-l2a'], query=['eo:cloud_cover=0', 'data_coverage>80']) 75 | min_items = 212 76 | assert(len(items), min_items) 77 | self.assertTrue(os.path.exists(fname)) 78 | os.remove(fname) 79 | self.assertFalse(os.path.exists(fname)) 80 | 81 | def test_cli(self): 82 | """ Run CLI program """ 83 | with patch.object(sys, 'argv', 'sat-search search --datetime 2017-01-01 --found -q platform=sentinel-2b'.split(' ')): 84 | cli() 85 | 86 | def test_cli_intersects(self): 87 | cmd = 'sat-search search --intersects %s -q platform=sentinel-2b --found' % os.path.join(testpath, 'aoi1.geojson') 88 | with patch.object(sys, 'argv', cmd.split(' ')): 89 | cli() 90 | 91 | def test_main_download(self): 92 | """ Test main program with downloading """ 93 | with open(os.path.join(testpath, 'aoi1.geojson')) as f: 94 | aoi = json.load(f) 95 | filename_template = os.path.join(testpath, "test-download/${platform}/${id}") 96 | items = main(datetime='2020-06-07', intersects=aoi['geometry'], 97 | filename_template=filename_template, download=['thumbnail', 'info'], **{'collections': ['sentinel-s2-l1c']}) 98 | for item in items: 99 | bname = os.path.splitext(item.get_path(filename_template))[0] 100 | assert(os.path.exists(bname + '_thumbnail.jpg')) 101 | assert(os.path.exists(bname + '_info.json')) 102 | #shutil.rmtree(os.path.join(testpath,'landsat-8')) 103 | -------------------------------------------------------------------------------- /test/test_search.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | import json 4 | import unittest 5 | 6 | from satstac import Item 7 | from satsearch.search import SatSearchError, Search 8 | 9 | API_URL = 'https://earth-search.aws.element84.com/v0' 10 | 11 | 12 | class Test(unittest.TestCase): 13 | 14 | path = os.path.dirname(__file__) 15 | results = [] 16 | 17 | @classmethod 18 | def setUpClass(cls): 19 | fnames = glob.glob(os.path.join(cls.path, '*-item*.json')) 20 | for fname in fnames: 21 | with open(fname) as f: 22 | cls.results.append(json.load(f)) 23 | 24 | def get_searches(self): 25 | """ Initialize and return search object """ 26 | return [Search(datetime=r['properties']['datetime'], url=API_URL) for r in self.results] 27 | 28 | def test_search_init(self): 29 | """ Initialize a search object """ 30 | search = self.get_searches()[0] 31 | dts = [r['properties']['datetime'] for r in self.results] 32 | 33 | assert(len(search.kwargs) == 1) 34 | assert('datetime' in search.kwargs) 35 | for kw in search.kwargs: 36 | self.assertTrue(search.kwargs[kw] in dts) 37 | 38 | def _test_search_for_items_by_date(self): 39 | """ Search for specific item """ 40 | search = self.get_searches()[0] 41 | sids = [r['id'] for r in self.results] 42 | items = search.items() 43 | assert(len(items) == 1) 44 | for s in items: 45 | self.assertTrue(s.id in sids) 46 | 47 | def test_empty_search(self): 48 | """ Perform search for 0 results """ 49 | search = Search(datetime='2001-01-01') 50 | self.assertEqual(search.found(), 0) 51 | 52 | def test_geo_search(self): 53 | """ Perform simple query """ 54 | with open(os.path.join(self.path, 'aoi1.geojson')) as f: 55 | aoi = json.load(f) 56 | search = Search(datetime='2020-06-07', intersects=aoi['geometry']) 57 | min_found = 12 58 | assert(search.found() >= min_found) 59 | items = search.items() 60 | assert(len(items) >= min_found) 61 | assert(isinstance(items[0], Item)) 62 | 63 | def test_search_sort(self): 64 | """ Perform search with sort """ 65 | with open(os.path.join(self.path, 'aoi1.geojson')) as f: 66 | aoi = json.load(f) 67 | search = Search.search(datetime='2020-06-07', intersects=aoi['geometry'], sortby=['-properties.datetime']) 68 | items = search.items() 69 | min_found = 12 70 | assert(len(items) >= min_found) 71 | 72 | def test_get_ids_search(self): 73 | """ Get Items by ID through normal search """ 74 | ids = ['S2A_28QBH_20200611_0_L2A', 'S2A_28QCH_20200611_0_L2A'] 75 | search = Search.search(ids=ids) 76 | items = search.items() 77 | assert(search.found() == 4) 78 | assert(len(items) == 4) 79 | 80 | def test_search_query_operator(self): 81 | expected = {'collections': ['sentinel-s2-l1c'], 'query': {'eo:cloud_cover': {'lte': '10'}, 'data_coverage': {'gt': '80'}}} 82 | instance = Search.search(collections=['sentinel-s2-l1c'], 83 | query=['eo:cloud_cover<=10', 'data_coverage>80']) 84 | assert instance.kwargs == expected 85 | -------------------------------------------------------------------------------- /tutorial-1.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# sat-search\n", 8 | "\n", 9 | "This notebook is a tutorial on how to use sat-search to search STAC APIs, save the results, and download assets.\n", 10 | "\n", 11 | "Sat-search is built using [sat-stac](https://github.com/sat-utils/sat-stac) which provides the core Python classes used to represent STAC catalogs: `Collection`, `Item`, and `Items`. It is recommended to review the [tutorial on STAC Classes](https://github.com/sat-utils/sat-stac/blob/master/tutorial-2.ipynb) for more information on how to use these objects returned from searching.\n", 12 | "\n", 13 | "Only the `search` module is in sat-search is used as a library, and it contains a single class, `Search`. The `parser` module is used for creating a CLI parser, and `main` contains the main function used in the CLI.\n", 14 | "\n", 15 | "**API endpoint**: Sat-search required an endpoint to be passed in or defined by the STAC_API_URL environment variable. This tutorial uses https://earth-search.aws.element84.com/v0 but any STAC endpoint can be used." 16 | ] 17 | }, 18 | { 19 | "cell_type": "markdown", 20 | "metadata": {}, 21 | "source": [ 22 | "## Initializing a Search object\n", 23 | "\n", 24 | "The first step in performing a search is to create a Search object with all the desired query parameters. Query parameters need to follow the querying as provided in the [STAC specification](https://github.com/radiantearth/stac-spec), although an abbreviated form is also supported (see below).\n", 25 | "\n", 26 | "Another place to look at the STAC query format is in the [sat-api docs](http://sat-utils.github.io/sat-api/), specifically see the section on [full-features querying](http://sat-utils.github.io/sat-api/#search-stac-items-by-full-featured-filtering-) which is what sat-search uses to POST queries to an API. Any field that can be provided in the [searchBody](http://sat-utils.github.io/sat-api/#tocssearchbody) can be provided as a keyword parameter when creating the search. These fields include:\n", 27 | "\n", 28 | "- bbox: bounding box of the form [minlon, minlat, maxlon, maxlat]\n", 29 | "- intersects: A GeoJSON geometry\n", 30 | "- time: A single date-time, a period string, or a range (seperated by /)\n", 31 | "- sort: A dictionary of fields to sort along with ascending/descending\n", 32 | "- query: Dictionary of properties to query on, supports eq, lt, gt, lte, gte\n", 33 | "\n", 34 | "Examples of queries are in the sat-api docs, but an example JSON query that would be POSTed might be:\n", 35 | "\n", 36 | "```\n", 37 | "{\n", 38 | " \"bbox\": [\n", 39 | " -110,\n", 40 | " 39.5,\n", 41 | " -105,\n", 42 | " 40.5\n", 43 | " ],\n", 44 | " \"time\": \"2018-02-12T00:00:00Z/2018-03-18T12:31:12Z\",\n", 45 | " \"query\": {\n", 46 | " \"eo:cloud_cover\": {\n", 47 | " \"lt\": 10\n", 48 | " }\n", 49 | " },\n", 50 | " \"sort\": [\n", 51 | " {\n", 52 | " \"field\": \"eo:cloud_cover\",\n", 53 | " \"direction\": \"desc\"\n", 54 | " }\n", 55 | " ]\n", 56 | "}\n", 57 | "```\n", 58 | "\n", 59 | "### Simple queries\n", 60 | "\n", 61 | "In sat-search, each of the fields in the query is simply provided as a keyword argument" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": 1, 67 | "metadata": {}, 68 | "outputs": [ 69 | { 70 | "name": "stdout", 71 | "output_type": "stream", 72 | "text": [ 73 | "bbox search: 10305 items\n", 74 | "time search: 474806 items\n", 75 | "cloud_cover search: 7359438 items\n" 76 | ] 77 | } 78 | ], 79 | "source": [ 80 | "from satsearch import Search\n", 81 | "\n", 82 | "search = Search(bbox=[-110, 39.5, -105, 40.5])\n", 83 | "print('bbox search: %s items' % search.found())\n", 84 | "\n", 85 | "search = Search(datetime='2018-02-12T00:00:00Z/2018-03-18T12:31:12Z')\n", 86 | "print('time search: %s items' % search.found())\n", 87 | "\n", 88 | "search = Search(query={'eo:cloud_cover': {'lt': 10}})\n", 89 | "print('cloud_cover search: %s items' % search.found())" 90 | ] 91 | }, 92 | { 93 | "cell_type": "markdown", 94 | "metadata": {}, 95 | "source": [ 96 | "### Complex query\n", 97 | "\n", 98 | "Now we combine all these filters and add in a sort filter to order the results (which will be shown further below)." 99 | ] 100 | }, 101 | { 102 | "cell_type": "code", 103 | "execution_count": 2, 104 | "metadata": {}, 105 | "outputs": [ 106 | { 107 | "name": "stdout", 108 | "output_type": "stream", 109 | "text": [ 110 | "21 items\n" 111 | ] 112 | } 113 | ], 114 | "source": [ 115 | "search = Search(bbox=[-110, 39.5, -105, 40.5],\n", 116 | " datetime='2018-02-12T00:00:00Z/2018-03-18T12:31:12Z',\n", 117 | " query={'eo:cloud_cover': {'lt': 10}},\n", 118 | " collections=['sentinel-s2-l2a'])\n", 119 | "print('%s items' % search.found())" 120 | ] 121 | }, 122 | { 123 | "cell_type": "markdown", 124 | "metadata": {}, 125 | "source": [ 126 | "### Intersects query\n", 127 | "\n", 128 | "The intersects query works the same way, except a geometry is provided." 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": 3, 134 | "metadata": {}, 135 | "outputs": [ 136 | { 137 | "name": "stdout", 138 | "output_type": "stream", 139 | "text": [ 140 | "intersects search: 4815 items\n" 141 | ] 142 | } 143 | ], 144 | "source": [ 145 | "geom = {\n", 146 | " \"type\": \"Polygon\",\n", 147 | " \"coordinates\": [\n", 148 | " [\n", 149 | " [\n", 150 | " -66.3958740234375,\n", 151 | " 43.305193797650546\n", 152 | " ],\n", 153 | " [\n", 154 | " -64.390869140625,\n", 155 | " 43.305193797650546\n", 156 | " ],\n", 157 | " [\n", 158 | " -64.390869140625,\n", 159 | " 44.22945656830167\n", 160 | " ],\n", 161 | " [\n", 162 | " -66.3958740234375,\n", 163 | " 44.22945656830167\n", 164 | " ],\n", 165 | " [\n", 166 | " -66.3958740234375,\n", 167 | " 43.305193797650546\n", 168 | " ]\n", 169 | " ]\n", 170 | " ]\n", 171 | "}\n", 172 | "\n", 173 | "search = Search(intersects=geom)\n", 174 | "print('intersects search: %s items' % search.found())" 175 | ] 176 | }, 177 | { 178 | "cell_type": "markdown", 179 | "metadata": {}, 180 | "source": [ 181 | "## Alternate search syntax\n", 182 | "\n", 183 | "This all works fine, except the syntax for creating queries is a bit verbose, so sat-search allows an alternate syntax using simple strings of the property and equality symbols. \n", 184 | "\n", 185 | "A typical query is shown below for eo:cloud_cover, along with the alternate versions that use the alternate syntax." 186 | ] 187 | }, 188 | { 189 | "cell_type": "code", 190 | "execution_count": 4, 191 | "metadata": {}, 192 | "outputs": [ 193 | { 194 | "name": "stdout", 195 | "output_type": "stream", 196 | "text": [ 197 | "7359438 items found\n", 198 | "7359438 items found\n" 199 | ] 200 | } 201 | ], 202 | "source": [ 203 | "query = {\n", 204 | " \"eo:cloud_cover\": {\n", 205 | " \"lt\": 10\n", 206 | " }\n", 207 | "}\n", 208 | "\n", 209 | "search = Search(query=query)\n", 210 | "print('%s items found' % search.found())\n", 211 | "\n", 212 | "search = Search.search(query=[\"eo:cloud_cover<10\"])\n", 213 | "print('%s items found' % search.found())" 214 | ] 215 | }, 216 | { 217 | "cell_type": "markdown", 218 | "metadata": {}, 219 | "source": [ 220 | "## Fetching results\n", 221 | "\n", 222 | "The examples above use the Search::found() function, but this only returns the total number of hits by performing a fast query with limit=0 (returns no items). To fetch the actual Items use the `Search::items()` function. This returns a sat-stac `Items` object." 223 | ] 224 | }, 225 | { 226 | "cell_type": "code", 227 | "execution_count": 5, 228 | "metadata": {}, 229 | "outputs": [ 230 | { 231 | "name": "stdout", 232 | "output_type": "stream", 233 | "text": [ 234 | "15 items\n", 235 | "15 items\n", 236 | "2 collections\n", 237 | "[sentinel-s2-l2a, sentinel-s2-l1c]\n", 238 | "S2B_12TXK_20180204_0_L1C\n", 239 | "S2B_12TYK_20180204_0_L1C\n", 240 | "S2B_12TWK_20180204_0_L1C\n", 241 | "S2B_13TBE_20180204_0_L1C\n", 242 | "S2B_13TBE_20180204_0_L2A\n", 243 | "S2B_12TYK_20180204_0_L2A\n", 244 | "S2B_12TWK_20180204_0_L2A\n", 245 | "S2B_13TCE_20180204_0_L2A\n", 246 | "S2B_12TXK_20180204_0_L2A\n", 247 | "S2A_12SWJ_20180202_0_L1C\n", 248 | "S2A_12SXJ_20180202_0_L1C\n", 249 | "S2A_12SXJ_20180202_0_L2A\n", 250 | "S2A_12SWJ_20180202_0_L2A\n", 251 | "S2B_13SED_20180201_0_L1C\n", 252 | "S2B_13SED_20180201_0_L2A\n" 253 | ] 254 | } 255 | ], 256 | "source": [ 257 | "search = Search(bbox=[-110, 39.5, -105, 40.5],\n", 258 | " datetime='2018-02-01/2018-02-04',\n", 259 | " property=[\"eo:cloud_cover<5\"])\n", 260 | "print('%s items' % search.found())\n", 261 | "\n", 262 | "items = search.items()\n", 263 | "print('%s items' % len(items))\n", 264 | "print('%s collections' % len(items._collections))\n", 265 | "print(items._collections)\n", 266 | "\n", 267 | "for item in items:\n", 268 | " print(item)" 269 | ] 270 | }, 271 | { 272 | "cell_type": "markdown", 273 | "metadata": {}, 274 | "source": [ 275 | "### Limit\n", 276 | "\n", 277 | "The `search.items()` function does take 1 argument, `limit`. This is the total number of items that will be returned. Behind the scenes sat-search may make multiple queries to the API, up until either the limit, or the total number of hits, whichever is greater." 278 | ] 279 | }, 280 | { 281 | "cell_type": "code", 282 | "execution_count": 6, 283 | "metadata": {}, 284 | "outputs": [ 285 | { 286 | "name": "stderr", 287 | "output_type": "stream", 288 | "text": [ 289 | "There are more items found (15) than the limit (2) provided.\n" 290 | ] 291 | }, 292 | { 293 | "name": "stdout", 294 | "output_type": "stream", 295 | "text": [ 296 | "Items (2):\n", 297 | "date id \n", 298 | "2018-02-04 S2B_12TXK_20180204_0_L1C \n", 299 | "2018-02-04 S2B_12TYK_20180204_0_L1C \n", 300 | "\n" 301 | ] 302 | } 303 | ], 304 | "source": [ 305 | "items = search.items(limit=2)\n", 306 | "print(items.summary())" 307 | ] 308 | }, 309 | { 310 | "cell_type": "markdown", 311 | "metadata": {}, 312 | "source": [ 313 | "## Returned `Items`\n", 314 | "\n", 315 | "The returned `Items` object has several useful functions and is covered in detail in the [sat-stac STAC classes tutorial](https://github.com/sat-utils/sat-stac/blob/master/tutorial-2.ipynb). The `Items` object contains all the returned Items (`Items._items`), along with any Collection references by those Items (`Items._collections`), and the search parameters used (`Items._search`) Below are some examples." 316 | ] 317 | }, 318 | { 319 | "cell_type": "code", 320 | "execution_count": 7, 321 | "metadata": {}, 322 | "outputs": [ 323 | { 324 | "name": "stdout", 325 | "output_type": "stream", 326 | "text": [ 327 | "Items (2):\n", 328 | "date id \n", 329 | "2018-02-04 S2B_12TXK_20180204_0_L1C \n", 330 | "2018-02-04 S2B_12TYK_20180204_0_L1C \n", 331 | "\n" 332 | ] 333 | } 334 | ], 335 | "source": [ 336 | "print(items.summary())" 337 | ] 338 | }, 339 | { 340 | "cell_type": "code", 341 | "execution_count": 8, 342 | "metadata": { 343 | "scrolled": false 344 | }, 345 | "outputs": [ 346 | { 347 | "name": "stdout", 348 | "output_type": "stream", 349 | "text": [ 350 | "Items (6):\n", 351 | "date id \n", 352 | "2018-02-07 S2B_12TWK_20180207_0_L2A \n", 353 | "2018-02-07 S2B_12TXK_20180207_0_L2A \n", 354 | "2018-02-07 S2B_12SWJ_20180207_0_L2A \n", 355 | "2018-02-07 S2B_12SXJ_20180207_0_L2A \n", 356 | "2018-02-02 S2A_12SXJ_20180202_0_L2A \n", 357 | "2018-02-02 S2A_12SWJ_20180202_0_L2A \n", 358 | "\n", 359 | "Items (6):\n", 360 | "date id eo:cloud_cover \n", 361 | "2018-02-07 S2B_12TWK_20180207_0_L2A 2.45 \n", 362 | "2018-02-07 S2B_12TXK_20180207_0_L2A 17.91 \n", 363 | "2018-02-07 S2B_12SWJ_20180207_0_L2A 12.85 \n", 364 | "2018-02-07 S2B_12SXJ_20180207_0_L2A 2.54 \n", 365 | "2018-02-02 S2A_12SXJ_20180202_0_L2A 5.74 \n", 366 | "2018-02-02 S2A_12SWJ_20180202_0_L2A 18.46 \n", 367 | "\n" 368 | ] 369 | } 370 | ], 371 | "source": [ 372 | "from satstac import ItemCollection\n", 373 | "\n", 374 | "search = Search.search(bbox=[-110, 39.5, -105, 40.5],\n", 375 | " datetime='2018-02-01/2018-02-10',\n", 376 | " query=[\"eo:cloud_cover<25\"],\n", 377 | " collections=['sentinel-s2-l2a'])\n", 378 | "items = search.items()\n", 379 | "print(items.summary())\n", 380 | "\n", 381 | "items.save('test.json')\n", 382 | "items2 = ItemCollection.open('test.json')\n", 383 | "\n", 384 | "print(items2.summary(['date', 'id', 'eo:cloud_cover']))" 385 | ] 386 | }, 387 | { 388 | "cell_type": "code", 389 | "execution_count": 9, 390 | "metadata": {}, 391 | "outputs": [ 392 | { 393 | "name": "stdout", 394 | "output_type": "stream", 395 | "text": [ 396 | "['downloads/2018-02-07/S2B_12TWK_20180207_0_L2A_metadata.xml', 'downloads/2018-02-07/S2B_12TXK_20180207_0_L2A_metadata.xml', 'downloads/2018-02-07/S2B_12SWJ_20180207_0_L2A_metadata.xml', 'downloads/2018-02-07/S2B_12SXJ_20180207_0_L2A_metadata.xml', 'downloads/2018-02-02/S2A_12SXJ_20180202_0_L2A_metadata.xml', 'downloads/2018-02-02/S2A_12SWJ_20180202_0_L2A_metadata.xml']\n" 397 | ] 398 | } 399 | ], 400 | "source": [ 401 | "# download a specific asset from all items and put in a directory by date in 'downloads'\n", 402 | "filenames = items.download('metadata', filename_template='downloads/${date}/${id}')\n", 403 | "print(filenames)" 404 | ] 405 | }, 406 | { 407 | "cell_type": "code", 408 | "execution_count": null, 409 | "metadata": {}, 410 | "outputs": [], 411 | "source": [] 412 | } 413 | ], 414 | "metadata": { 415 | "kernelspec": { 416 | "display_name": "Python 3", 417 | "language": "python", 418 | "name": "python3" 419 | }, 420 | "language_info": { 421 | "codemirror_mode": { 422 | "name": "ipython", 423 | "version": 3 424 | }, 425 | "file_extension": ".py", 426 | "mimetype": "text/x-python", 427 | "name": "python", 428 | "nbconvert_exporter": "python", 429 | "pygments_lexer": "ipython3", 430 | "version": "3.8.3" 431 | } 432 | }, 433 | "nbformat": 4, 434 | "nbformat_minor": 2 435 | } 436 | --------------------------------------------------------------------------------