├── requirements.txt ├── aws_sat_api ├── scripts │ ├── __init__.py │ └── cli.py ├── __init__.py ├── errors.py ├── aws.py ├── utils.py └── search.py ├── tox.ini ├── CHANGELOG.md ├── LICENSE ├── setup.py ├── .gitignore ├── README.md └── tests ├── fixtures ├── tileInfo.json ├── s2_search_2017.json ├── LC81781192017016LGN00_MTL.json ├── LC81782462014232LGN00_MTL.json └── LC08_L1GT_178119_20180103_20180103_01_RT_MTL.json ├── test_utils.py ├── test_aws.py └── test_search.py /requirements.txt: -------------------------------------------------------------------------------- 1 | boto3 2 | -------------------------------------------------------------------------------- /aws_sat_api/scripts/__init__.py: -------------------------------------------------------------------------------- 1 | """aws_sat_api""" 2 | -------------------------------------------------------------------------------- /aws_sat_api/__init__.py: -------------------------------------------------------------------------------- 1 | """aws_sat_api.""" 2 | 3 | __version__ = '2.0.2' 4 | -------------------------------------------------------------------------------- /aws_sat_api/errors.py: -------------------------------------------------------------------------------- 1 | """Errors and warnings.""" 2 | 3 | 4 | class SatApiError(Exception): 5 | """Base exception class.""" 6 | 7 | 8 | class InvalidLandsatSceneId(SatApiError): 9 | """Invalid Landsat-8 scene id.""" 10 | 11 | 12 | class InvalidSentinelSceneId(SatApiError): 13 | """Invalid Sentinel-2 scene id.""" 14 | 15 | 16 | class InvalidCBERSSceneId(SatApiError): 17 | """Invalid CBERS scene id.""" 18 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | max-complexity = 10 4 | inline-quotes = double 5 | 6 | 7 | [tox] 8 | envlist = py36 9 | 10 | 11 | [testenv] 12 | extras = test 13 | commands= 14 | python -m pytest --cov aws_sat_api --cov-report term-missing --ignore=venv 15 | 16 | 17 | [testenv:flake8] 18 | basepython = python3 19 | skip_install = true 20 | deps = 21 | flake8 22 | commands = 23 | flake8 aws_sat_api 24 | 25 | 26 | [testenv:pylint] 27 | basepython = python3 28 | skip_install = true 29 | deps = 30 | pyflakes 31 | pylint 32 | commands = 33 | pylint aws_sat_api 34 | 35 | 36 | [testenv:doc] 37 | select = D1 38 | ignore = D105 39 | deps = 40 | pydocstyle 41 | commands = 42 | python -m pydocstyle aws_sat_api 43 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 2.0.2 5 | ----- 6 | - Fix Sentinel2 search for a single day interval. (#7) 7 | 8 | 2.0.1 9 | ----- 10 | - Update sentinel-2 preview address 11 | 12 | 2.0.0 13 | ----- 14 | - Switch all Sentinel-2 bucket to `requester pays` 15 | - add date filter for sentinel2 search (#5) 16 | 17 | 1.0.0 18 | ----- 19 | - Including support for CBERS-4 AWFI, PAN10M and PAN5M sensors (fredliporace) 20 | 21 | 0.0.5 22 | ----- 23 | - do not error when not mtl or tileInfo file are missing 24 | 25 | 0.0.4 26 | ----- 27 | - add CBERS-4 Browse url 28 | 29 | 0.0.3 30 | ----- 31 | - fix function failure when missing info in Sentinel-2 tileInfo.json 32 | 33 | 0.0.2 34 | ----- 35 | - fix bad Sentinel-2 year range 36 | 37 | 0.0.1 38 | ----- 39 | - initial release 40 | -------------------------------------------------------------------------------- /aws_sat_api/aws.py: -------------------------------------------------------------------------------- 1 | """AWS S3 functions.""" 2 | 3 | import os 4 | 5 | from boto3.session import Session as boto3_session 6 | 7 | region = os.environ.get('AWS_REGION', 'us-east-1') 8 | 9 | 10 | def list_directory(bucket, prefix, s3=None, request_pays=False): 11 | """AWS s3 list directory.""" 12 | if not s3: 13 | session = boto3_session(region_name=region) 14 | s3 = session.client('s3') 15 | 16 | pag = s3.get_paginator('list_objects_v2') 17 | 18 | params = { 19 | 'Bucket': bucket, 20 | 'Prefix': prefix, 21 | 'Delimiter': '/'} 22 | 23 | if request_pays: 24 | params['RequestPayer'] = 'requester' 25 | 26 | directories = [] 27 | for subset in pag.paginate(**params): 28 | if 'CommonPrefixes' in subset.keys(): 29 | directories.extend(subset.get('CommonPrefixes')) 30 | 31 | return [r['Prefix'] for r in directories] 32 | 33 | 34 | def get_object(bucket, key, s3=None, request_pays=False): 35 | """AWS s3 get object content.""" 36 | if not s3: 37 | session = boto3_session(region_name=region) 38 | s3 = session.client('s3') 39 | 40 | params = { 41 | 'Bucket': bucket, 42 | 'Key': key} 43 | 44 | if request_pays: 45 | params['RequestPayer'] = 'requester' 46 | 47 | response = s3.get_object(**params) 48 | return response['Body'].read() 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, RemotePixel.ca 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | 4 | from setuptools import setup, find_packages 5 | 6 | with open('aws_sat_api/__init__.py') as f: 7 | for line in f: 8 | if line.find("__version__") >= 0: 9 | version = line.split("=")[1].strip() 10 | version = version.strip('"') 11 | version = version.strip("'") 12 | continue 13 | 14 | 15 | def read(fname): 16 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 17 | 18 | 19 | # Runtime requirements. 20 | inst_reqs = ["boto3"] 21 | 22 | extra_reqs = { 23 | 'test': ['mock', 'pytest', 'pytest-cov', 'codecov']} 24 | 25 | setup(name='aws_sat_api', 26 | version=version, 27 | description=u"""""", 28 | long_description="""""", 29 | python_requires='>=3', 30 | classifiers=[ 31 | 'Intended Audience :: Information Technology', 32 | 'Intended Audience :: Science/Research', 33 | 'License :: OSI Approved :: BSD License', 34 | 'Programming Language :: Python :: 3.6', 35 | 'Topic :: Scientific/Engineering :: GIS'], 36 | keywords='', 37 | author=u"Vincent Sarago", 38 | author_email='contact@remotepixel.ca', 39 | url='https://github.com/remotepixel/aws-sat-api-py', 40 | license='BSD', 41 | packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), 42 | include_package_data=True, 43 | zip_safe=False, 44 | install_requires=inst_reqs, 45 | extras_require=extra_reqs, 46 | entry_points=""" 47 | [console_scripts] 48 | awssat=aws_sat_api.scripts.cli:awssat 49 | """) 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # Pycharm 7 | .idea 8 | _venv 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | env/ 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | .hypothesis/ 52 | .pytest_cache 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # pyenv 79 | .python-version 80 | 81 | # celery beat schedule file 82 | celerybeat-schedule 83 | 84 | # SageMath parsed files 85 | *.sage.py 86 | 87 | # dotenv 88 | .env 89 | 90 | # virtualenv 91 | .venv 92 | venv/ 93 | ENV/ 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # mkdocs documentation 103 | /site 104 | 105 | # mypy 106 | .mypy_cache/ 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | :warning: Using this module could result in AWS charge (Sentinel-2 and CBERS buckets are requester-pays buckets) 2 | 3 | 4 | # aws-sat-api-py 5 | Python port of https://github.com/RemotePixel/aws-sat-api 6 | 7 | A really simple non-spatial API to get Landsat-8, Sentinel-2(A and B) and CBERS-4 images hosed on AWS S3 8 | 9 | # Installation 10 | 11 | ##### Requirement 12 | - AWS Account (to access AWS S3). 13 | Please see http://boto3.readthedocs.io/en/latest/guide/configuration.html to configure AWS Credentials env. 14 | 15 | 16 | ```bash 17 | pip install aws-sat-api 18 | ``` 19 | 20 | ### Usage 21 | 22 | ```Python 23 | 24 | from aws_sat_api.search import landsat, cbers, sentinel2 25 | 26 | l8_path = 178 27 | l8_row = 80 28 | full_search = False 29 | l8_meta = landsat(l8_path, l8_row, full_search) 30 | 31 | 32 | cbers_path = 178 33 | cbers_row = 80 34 | cbers_sensor = 'MUX' 35 | cbers_meta = cbers(cbers_path, cbers_row, sensor) 36 | 37 | 38 | utm = 16 39 | lat = 'S' 40 | grid = 'DF' 41 | full_search = False 42 | level = 'l1c' 43 | s2_meta = sentinel2(utm, lat, grid, full_search, level) 44 | ``` 45 | 46 | 47 | ### CLI 48 | 49 | ``` 50 | awssat --help 51 | Usage: awssat [OPTIONS] COMMAND [ARGS]... 52 | 53 | Search. 54 | 55 | Options: 56 | --help Show this message and exit. 57 | 58 | Commands: 59 | cbers CBERS search CLI. 60 | landsat Landsat search CLI. 61 | sentinel Sentinel search CLI. 62 | ``` 63 | 64 | Example: 65 | 66 | Get Zoom 8 mercator tiles covering all landsat images for path/row 015/033 and 015/034 67 | ``` 68 | awssat landsat -pr 015-033,015-034 | jq -c '. | {"type": "Feature", "properties": {}, geometry: .geometry}' | fio collect | fio extent | supermercado burn 8 | xt -d'-' 69 | 8-72-96 70 | 8-73-96 71 | 8-74-96 72 | 8-72-97 73 | 8-73-97 74 | 8-74-97 75 | 8-72-98 76 | 8-73-98 77 | 8-74-98 78 | 8-72-99 79 | 8-73-99 80 | 8-74-99 81 | 8-72-100 82 | 8-73-100 83 | 8-74-100 84 | ``` 85 | -------------------------------------------------------------------------------- /tests/fixtures/tileInfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "path" : "tiles/38/S/NG/2017/10/9/1", 3 | "timestamp" : "2017-10-09T07:59:20.679Z", 4 | "utmZone" : 38, 5 | "latitudeBand" : "S", 6 | "gridSquare" : "NG", 7 | "datastrip" : { 8 | "id" : "S2B_OPER_MSI_L1C_DS_MTI__20171009T100818_S20171009T075920_N02.05", 9 | "path" : "products/2017/10/9/S2A_MSIL1C_20171009T074821_N0205_R135_T38SNG_20171009T075920/datastrip/0" 10 | }, 11 | "tileGeometry" : { 12 | "type" : "Polygon", 13 | "crs" : { 14 | "type" : "name", 15 | "properties" : { 16 | "name" : "urn:ogc:def:crs:EPSG:8.8.1:32638" 17 | } 18 | }, 19 | "coordinates" : [ [ [ 499980.0, 4200000.0 ], [ 609780.0, 4200000.0 ], [ 609780.0, 4090200.0 ], [ 499980.0, 4090200.0 ], [ 499980.0, 4200000.0 ] ] ] 20 | }, 21 | "tileDataGeometry" : { 22 | "type" : "Polygon", 23 | "crs" : { 24 | "type" : "name", 25 | "properties" : { 26 | "name" : "urn:ogc:def:crs:EPSG:8.8.1:32638" 27 | } 28 | }, 29 | "coordinates" : [ [ [ 532901.762326178, 4150176.842073918 ], [ 532872.573588683, 4150058.509701258 ], [ 555321.935398523, 4143548.044945508 ], [ 555327.616750179, 4143566.134286545 ], [ 555518.1188922, 4143510.799528175 ], [ 555551.228382867, 4143636.391058131 ], [ 580868.165576596, 4135804.965008364 ], [ 569204.532129796, 4090201.0 ], [ 499981.0, 4090201.0 ], [ 499981.0, 4159528.288182492 ], [ 507993.096191663, 4157332.421991669 ], [ 508024.681607829, 4157347.934675552 ], [ 508206.736881553, 4157298.002158208 ], [ 508251.91480713, 4157470.398578801 ], [ 532855.688765657, 4150351.809518428 ], [ 532848.33767652, 4150249.904459715 ], [ 532875.520118499, 4150240.929409222 ], [ 532865.297039636, 4150200.729052221 ], [ 532901.762326178, 4150176.842073918 ] ] ] 30 | }, 31 | "tileOrigin" : { 32 | "type" : "Point", 33 | "crs" : { 34 | "type" : "name", 35 | "properties" : { 36 | "name" : "urn:ogc:def:crs:EPSG:8.8.1:32638" 37 | } 38 | }, 39 | "coordinates" : [ 499980.0, 4200000.0 ] 40 | }, 41 | "dataCoveragePercentage" : 36.52, 42 | "cloudyPixelPercentage" : 5.01, 43 | "productName" : "S2B_MSIL1C_20171009T074821_N0205_R135_T38SNG_20171009T075920", 44 | "productPath" : "products/2017/10/9/S2B_MSIL1C_20171009T074821_N0205_R135_T38SNG_20171009T075920" 45 | } 46 | -------------------------------------------------------------------------------- /aws_sat_api/utils.py: -------------------------------------------------------------------------------- 1 | """utility functions.""" 2 | 3 | import os 4 | import re 5 | import datetime 6 | 7 | from aws_sat_api.errors import (InvalidLandsatSceneId, InvalidCBERSSceneId) 8 | 9 | 10 | def landsat_parse_scene_id(sceneid): 11 | """Parse Landsat-8 scene id. 12 | 13 | Author @perrygeo - http://www.perrygeo.com 14 | """ 15 | pre_collection = r'(L[COTEM]8\d{6}\d{7}[A-Z]{3}\d{2})' 16 | collection_1 = r'(L[COTEM]08_L\d{1}[A-Z]{2}_\d{6}_\d{8}_\d{8}_\d{2}_(T1|T2|RT))' 17 | if not re.match('^{}|{}$'.format(pre_collection, collection_1), sceneid): 18 | raise InvalidLandsatSceneId('Could not match {}'.format(sceneid)) 19 | 20 | precollection_pattern = ( 21 | r'^L' 22 | r'(?P\w{1})' 23 | r'(?P\w{1})' 24 | r'(?P[0-9]{3})' 25 | r'(?P[0-9]{3})' 26 | r'(?P[0-9]{4})' 27 | r'(?P[0-9]{3})' 28 | r'(?P\w{3})' 29 | r'(?P[0-9]{2})$') 30 | 31 | collection_pattern = ( 32 | r'^L' 33 | r'(?P\w{1})' 34 | r'(?P\w{2})' 35 | r'_' 36 | r'(?P\w{4})' 37 | r'_' 38 | r'(?P[0-9]{3})' 39 | r'(?P[0-9]{3})' 40 | r'_' 41 | r'(?P[0-9]{4}[0-9]{2}[0-9]{2})' 42 | r'_' 43 | r'(?P[0-9]{4}[0-9]{2}[0-9]{2})' 44 | r'_' 45 | r'(?P\w{2})' 46 | r'_' 47 | r'(?P\w{2})$') 48 | 49 | meta = None 50 | for pattern in [collection_pattern, precollection_pattern]: 51 | match = re.match(pattern, sceneid, re.IGNORECASE) 52 | if match: 53 | meta = match.groupdict() 54 | break 55 | 56 | if meta.get('acquisitionJulianDay'): 57 | date = datetime.datetime(int(meta['acquisitionYear']), 1, 1) \ 58 | + datetime.timedelta(int(meta['acquisitionJulianDay']) - 1) 59 | meta['acquisition_date'] = date.strftime('%Y%m%d') 60 | meta['category'] = 'pre' 61 | 62 | collection = meta.get('collection', '') 63 | if collection != '': 64 | collection = 'c{}'.format(int(collection)) 65 | 66 | meta['scene_id'] = sceneid 67 | meta['satellite'] = 'L{}'.format(meta['satellite'].lstrip('0')) 68 | meta['key'] = os.path.join(collection, 'L8', meta['path'], meta['row'], sceneid, sceneid) 69 | 70 | return meta 71 | 72 | 73 | def cbers_parse_scene_id(sceneid): 74 | """Parse CBERS scene id.""" 75 | if not re.match('^CBERS_4_\w+_[0-9]{8}_[0-9]{3}_[0-9]{3}_L[0-9]$', sceneid): 76 | raise InvalidCBERSSceneId('Could not match {}'.format(sceneid)) 77 | 78 | cbers_pattern = ( 79 | r'(?P\w{5})' 80 | r'_' 81 | r'(?P[0-9]{1})' 82 | r'_' 83 | r'(?P\w+)' 84 | r'_' 85 | r'(?P[0-9]{4}[0-9]{2}[0-9]{2})' 86 | r'_' 87 | r'(?P[0-9]{3})' 88 | r'_' 89 | r'(?P[0-9]{3})' 90 | r'_' 91 | r'(?PL[0-9]{1})$') 92 | 93 | meta = None 94 | match = re.match(cbers_pattern, sceneid, re.IGNORECASE) 95 | if match: 96 | meta = match.groupdict() 97 | 98 | meta['scene_id'] = sceneid 99 | meta['key'] = 'CBERS4/{}/{}/{}/{}'.format(meta['sensor'], meta['path'], meta['row'], sceneid) 100 | 101 | return meta 102 | 103 | def zeroPad(n, l): 104 | """ Add leading 0.""" 105 | return str(n).zfill(l) 106 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | """tests aws_sat_api.utils""" 2 | 3 | import pytest 4 | 5 | from aws_sat_api import utils 6 | from aws_sat_api.errors import (InvalidLandsatSceneId, InvalidCBERSSceneId) 7 | 8 | 9 | def test_landsat_id_pre_invalid(): 10 | """ 11 | Should raise an error with invalid sceneid 12 | """ 13 | 14 | scene = 'L0300342017083LGN00' 15 | with pytest.raises(InvalidLandsatSceneId): 16 | utils.landsat_parse_scene_id(scene) 17 | 18 | 19 | def test_landsat_id_c1_invalid(): 20 | """ 21 | Should raise an error with invalid sceneid 22 | """ 23 | 24 | scene = 'LC08_005004_20170410_20170414_01_T1' 25 | with pytest.raises(InvalidLandsatSceneId): 26 | utils.landsat_parse_scene_id(scene) 27 | 28 | 29 | def test_landsat_id_pre_valid(): 30 | """ 31 | Should work as expected (parse landsat pre sceneid) 32 | """ 33 | 34 | scene = 'LC80300342017083LGN00' 35 | expected_content = { 36 | 'acquisitionJulianDay': '083', 37 | 'acquisitionYear': '2017', 38 | 'archiveVersion': '00', 39 | 'category': 'pre', 40 | 'acquisition_date': '20170324', 41 | 'groundStationIdentifier': 'LGN', 42 | 'key': 'L8/030/034/LC80300342017083LGN00/LC80300342017083LGN00', 43 | 'path': '030', 44 | 'row': '034', 45 | 'satellite': 'L8', 46 | 'scene_id': 'LC80300342017083LGN00', 47 | 'sensor': 'C'} 48 | 49 | assert utils.landsat_parse_scene_id(scene) == expected_content 50 | 51 | 52 | def test_landsat_id_c1_valid(): 53 | """ 54 | Should work as expected (parse landsat c1 sceneid) 55 | """ 56 | 57 | scene = 'LC08_L1TP_005004_20170410_20170414_01_T1' 58 | expected_content = { 59 | 'category': 'T1', 60 | 'collection': '01', 61 | 'acquisition_date': '20170410', 62 | 'key': 'c1/L8/005/004/LC08_L1TP_005004_20170410_\ 63 | 20170414_01_T1/LC08_L1TP_005004_20170410_20170414_01_T1', 64 | 'path': '005', 65 | 'correction_level': 'L1TP', 66 | 'ingestion_date': '20170414', 67 | 'row': '004', 68 | 'satellite': 'L8', 69 | 'scene_id': 'LC08_L1TP_005004_20170410_20170414_01_T1', 70 | 'sensor': 'C'} 71 | 72 | assert utils.landsat_parse_scene_id(scene) == expected_content 73 | 74 | 75 | def test_cbers_id_invalid(): 76 | """ 77 | Should raise an error with invalid sceneid 78 | """ 79 | 80 | scene = 'CBERS_4_MUX_20171121_057_094' 81 | with pytest.raises(InvalidCBERSSceneId): 82 | utils.cbers_parse_scene_id(scene) 83 | 84 | def test_cbers_id_valid(): 85 | """ 86 | Should work as expected (parse cbers scene id) 87 | """ 88 | 89 | scene = 'CBERS_4_MUX_20171121_057_094_L2' 90 | expected_content = { 91 | 'acquisition_date': '20171121', 92 | 'sensor': 'MUX', 93 | 'key': 'CBERS4/MUX/057/094/CBERS_4_MUX_20171121_057_094_L2', 94 | 'path': '057', 95 | 'processing_level': 'L2', 96 | 'row': '094', 97 | 'version': '4', 98 | 'scene_id': 'CBERS_4_MUX_20171121_057_094_L2', 99 | 'satellite': 'CBERS'} 100 | 101 | assert utils.cbers_parse_scene_id(scene) == expected_content 102 | 103 | scene = 'CBERS_4_AWFI_20171121_057_094_L2' 104 | expected_content = { 105 | 'acquisition_date': '20171121', 106 | 'sensor': 'AWFI', 107 | 'key': 'CBERS4/AWFI/057/094/CBERS_4_AWFI_20171121_057_094_L2', 108 | 'path': '057', 109 | 'processing_level': 'L2', 110 | 'row': '094', 111 | 'version': '4', 112 | 'scene_id': 'CBERS_4_AWFI_20171121_057_094_L2', 113 | 'satellite': 'CBERS'} 114 | 115 | assert utils.cbers_parse_scene_id(scene) == expected_content 116 | 117 | 118 | def test_zeroPad_valid(): 119 | assert utils.zeroPad(3, 4) == '0003' 120 | 121 | 122 | def test_zeroPad_validString(): 123 | assert utils.zeroPad('3', 2) == '03' 124 | -------------------------------------------------------------------------------- /aws_sat_api/scripts/cli.py: -------------------------------------------------------------------------------- 1 | """CLI.""" 2 | 3 | import re 4 | import json 5 | 6 | import click 7 | 8 | from aws_sat_api import search 9 | 10 | 11 | @click.group(short_help="AWS Satellite API") 12 | def awssat(): 13 | """Search.""" 14 | pass 15 | 16 | 17 | class CustomType: 18 | """Click CustomType.""" 19 | 20 | class PathRow(click.ParamType): 21 | """PathRow.""" 22 | 23 | name = "pathrow" 24 | 25 | def convert(self, value, param, ctx): 26 | """Validate and parse path-row.""" 27 | try: 28 | pr = [x for x in value.split(",")] 29 | assert all(re.match(r'\d+-\d+', b) for b in pr) 30 | return pr 31 | 32 | except (ValueError, AttributeError, AssertionError): 33 | raise click.ClickException( 34 | "pathrow." 35 | ) 36 | 37 | pathrow = PathRow() 38 | 39 | class S2Tile(click.ParamType): 40 | """PathRow.""" 41 | 42 | name = "s2tile" 43 | 44 | def convert(self, value, param, ctx): 45 | """Validate and parse sentineltile.""" 46 | try: 47 | pr = [x for x in value.split(",")] 48 | assert all(re.match(r'[0-9]{2}\w{1}\w{2}', b) for b in pr) 49 | return pr 50 | 51 | except (ValueError, AttributeError, AssertionError): 52 | raise click.ClickException( 53 | "tile." 54 | ) 55 | 56 | s2tile = S2Tile() 57 | 58 | 59 | @awssat.command(name="landsat") 60 | @click.option( 61 | "--path", 62 | "-p", 63 | type=str, 64 | help="path", 65 | ) 66 | @click.option( 67 | "--row", 68 | "-r", 69 | type=str, 70 | help="row", 71 | ) 72 | @click.option( 73 | "--pathrow", 74 | "-pr", 75 | type=CustomType.pathrow, 76 | help="path-row", 77 | ) 78 | @click.option( 79 | "--full/--simple", 80 | default=True, 81 | help="full" 82 | ) 83 | def landsat( 84 | path, 85 | row, 86 | pathrow, 87 | full, 88 | ): 89 | """Landsat search CLI.""" 90 | # TODO: add tests for pathrow and path+row options 91 | if pathrow: 92 | pr_info = [dict(path=x.split('-')[0], row=x.split('-')[1]) for x in pathrow] 93 | else: 94 | pr_info = [dict(path=path, row=row)] 95 | 96 | for el in pr_info: 97 | for scene in search.landsat(**el, full=full): 98 | click.echo(json.dumps(scene)) 99 | 100 | 101 | @awssat.command(name="sentinel") 102 | @click.option( 103 | "--utm", 104 | "-u", 105 | type=str, 106 | help="utm", 107 | ) 108 | @click.option( 109 | "--lat", 110 | "-l", 111 | type=str, 112 | help="lat", 113 | ) 114 | @click.option( 115 | "--grid", 116 | "-g", 117 | type=str, 118 | help="grid", 119 | ) 120 | @click.option( 121 | "--tile", 122 | "-t", 123 | type=CustomType.s2tile, 124 | help="tile", 125 | ) 126 | @click.option( 127 | "--level", 128 | type=click.Choice(['l1c', 'l2a']), 129 | default='l1c', 130 | help="level", 131 | ) 132 | @click.option( 133 | "--full/--simple", 134 | default=True, 135 | help="full" 136 | ) 137 | def sentinel( 138 | utm, 139 | lat, 140 | grid, 141 | tile, 142 | level, 143 | full, 144 | ): 145 | """Sentinel search CLI.""" 146 | # TODO: add tests for tile and utm+grid+lat options 147 | if tile: 148 | sentinel_pattern = r'^(?P[0-9]{1,2})(?P\w{1})(?P\w{2})$' 149 | tile_info = [ 150 | re.match(sentinel_pattern, x, re.IGNORECASE).groupdict() 151 | for x in tile 152 | ] 153 | else: 154 | tile_info = [dict(utm=utm, lat=lat, grid=grid)] 155 | 156 | for el in tile_info: 157 | for scene in search.sentinel2(**el, level=level, full=full): 158 | click.echo(json.dumps(scene)) 159 | 160 | 161 | @awssat.command(name="cbers") 162 | @click.option( 163 | "--path", 164 | "-p", 165 | type=str, 166 | help="path", 167 | ) 168 | @click.option( 169 | "--row", 170 | "-r", 171 | type=str, 172 | help="row", 173 | ) 174 | @click.option( 175 | "--pathrow", 176 | "-pr", 177 | type=CustomType.pathrow, 178 | help="path-row", 179 | ) 180 | @click.option( 181 | "--sensor", 182 | "-s", 183 | type=click.Choice(["MUX", "AWFI", "PAN5M", "PAN10M"]), 184 | default="MUX", 185 | help="CBERS4 sensor", 186 | ) 187 | def cbers( 188 | path, 189 | row, 190 | pathrow, 191 | sensor, 192 | ): 193 | """CBERS search CLI.""" 194 | # TODO: add tests for pathrow and path+row options 195 | if pathrow: 196 | pr_info = [dict(path=x.split('-')[0], row=x.split('-')[1]) for x in pathrow] 197 | else: 198 | pr_info = [dict(path=path, row=row)] 199 | 200 | for el in pr_info: 201 | for scene in search.cbers(**el, sensor=sensor): 202 | click.echo(json.dumps(scene)) 203 | -------------------------------------------------------------------------------- /tests/test_aws.py: -------------------------------------------------------------------------------- 1 | """tests aws_sat_api.aws""" 2 | 3 | from io import BytesIO 4 | 5 | import pytest 6 | 7 | from mock import patch 8 | from botocore.exceptions import ClientError 9 | 10 | from aws_sat_api import aws 11 | 12 | 13 | @pytest.fixture(autouse=True) 14 | def testing_env_var(monkeypatch): 15 | # This is optional (just make sure we don't hit aws) 16 | monkeypatch.setenv('AWS_ACCESS_KEY_ID', 'foo') 17 | monkeypatch.setenv('AWS_SECRET_ACCESS_KEY', 'bar') 18 | monkeypatch.delenv('AWS_PROFILE', raising=False) 19 | monkeypatch.setenv('AWS_CONFIG_FILE', '/tmp/asdfasdfaf/does/not/exist') 20 | monkeypatch.setenv('AWS_SHARED_CREDENTIALS_FILE', 21 | '/tmp/asdfasdfaf/does/not/exist2') 22 | 23 | 24 | @patch('aws_sat_api.aws.boto3_session') 25 | def test_aws_list_directory_valid(session): 26 | """Should work as expected 27 | """ 28 | 29 | session.return_value.client.return_value.get_paginator.return_value.paginate.return_value = [ 30 | {'CommonPrefixes': [{'Prefix': 'L8/178/246/LC81782462014232LGN00/'}]}] 31 | 32 | bucket = "landsat-pds" 33 | prefix = "L8/178/246/" 34 | 35 | expected_value = [ 36 | 'L8/178/246/LC81782462014232LGN00/'] 37 | 38 | assert aws.list_directory(bucket, prefix) == expected_value 39 | 40 | 41 | @patch('aws_sat_api.aws.boto3_session.client') 42 | def test_aws_list_directory_validClient(client): 43 | """Should work as expected 44 | """ 45 | 46 | client.return_value.get_paginator.return_value.paginate.return_value = [ 47 | {'CommonPrefixes': [{'Prefix': 'L8/178/246/LC81782462014232LGN00/'}]}] 48 | 49 | bucket = "landsat-pds" 50 | prefix = "L8/178/246/" 51 | 52 | expected_value = [ 53 | 'L8/178/246/LC81782462014232LGN00/'] 54 | 55 | assert aws.list_directory(bucket, prefix, s3=client()) == expected_value 56 | 57 | 58 | @patch('aws_sat_api.aws.boto3_session') 59 | def test_aws_list_directory_validPays(session): 60 | """Should work as expected 61 | """ 62 | 63 | session.return_value.client.return_value.get_paginator.return_value.paginate.return_value = [ 64 | {'CommonPrefixes': [{'Prefix': 'L8/178/246/LC81782462014232LGN00/'}]}] 65 | 66 | bucket = "landsat-pds" 67 | prefix = "L8/178/246/" 68 | 69 | expected_value = ['L8/178/246/LC81782462014232LGN00/'] 70 | 71 | assert aws.list_directory(bucket, prefix, request_pays=True) == expected_value 72 | pag = session.return_value.client.return_value.get_paginator.return_value.paginate 73 | assert pag.call_args[1].get('RequestPayer') == 'requester' 74 | 75 | 76 | @patch('aws_sat_api.aws.boto3_session') 77 | def test_aws_list_directory_nodir(session): 78 | """Should return an empty list 79 | """ 80 | 81 | session.return_value.client.return_value.get_paginator.return_value.paginate.return_value = [{}] 82 | 83 | bucket = "landsat-pds" 84 | prefix = "L8/178/246/" 85 | 86 | assert not aws.list_directory(bucket, prefix) 87 | 88 | 89 | @patch('aws_sat_api.aws.boto3_session') 90 | def test_aws_list_directory_awserror(session): 91 | """Should raise an 'ClientError' error 92 | """ 93 | 94 | session.return_value.client.return_value.get_paginator.return_value.paginate.side_effect = [ 95 | ClientError({'Error': {'Code': 500, 'Message': 'Error'}}, 'list_objects_v2')] 96 | 97 | bucket = "landsat-pds" 98 | prefix = "L8/178/246/" 99 | 100 | with pytest.raises(ClientError): 101 | aws.list_directory(bucket, prefix) 102 | 103 | 104 | @patch('aws_sat_api.aws.boto3_session') 105 | def test_aws_get_object_valid(session): 106 | """Should work as expected 107 | """ 108 | 109 | session.return_value.client.return_value.get_object.return_value = {'Body': BytesIO(b'0101010')} 110 | 111 | bucket = "landsat-pds" 112 | key = "L8/178/246/LC81782462014232LGN00/LC81782462014232LGN00_MTL.json" 113 | 114 | assert aws.get_object(bucket, key) 115 | 116 | 117 | @patch('aws_sat_api.aws.boto3_session') 118 | def test_aws_get_object_validPays(session): 119 | """Should work as expected 120 | """ 121 | 122 | session.return_value.client.return_value.get_object.return_value = {'Body': BytesIO(b'0101010')} 123 | 124 | bucket = "landsat-pds" 125 | key = "L8/178/246/LC81782462014232LGN00/LC81782462014232LGN00_MTL.json" 126 | 127 | assert aws.get_object(bucket, key, request_pays=True) 128 | assert session.return_value.client.return_value.get_object.call_args[1].get('RequestPayer') == 'requester' 129 | 130 | 131 | @patch('aws_sat_api.aws.boto3_session.client') 132 | def test_aws_get_object_validClient(client): 133 | """Should work as expected 134 | """ 135 | 136 | client.return_value.get_object.return_value = {'Body': BytesIO(b'0101010')} 137 | 138 | bucket = "landsat-pds" 139 | key = "L8/178/246/LC81782462014232LGN00/LC81782462014232LGN00_MTL.json" 140 | 141 | assert aws.get_object(bucket, key, s3=client()) 142 | 143 | 144 | @patch('aws_sat_api.aws.boto3_session') 145 | def test_aws_get_object_awserror(session): 146 | """ 147 | Should raise an 'ClientError' error 148 | """ 149 | 150 | session.return_value.client.return_value.get_object.side_effect = ClientError({'Error': { 151 | 'Code': 500, 'Message': 'Error'}}, 'get_object') 152 | 153 | bucket = "landsat-pds" 154 | key = "L8/178/246/LC81782462014232LGN00/LC81782462014232LGN00_MTL.json" 155 | 156 | with pytest.raises(ClientError): 157 | aws.get_object(bucket, key) 158 | -------------------------------------------------------------------------------- /tests/fixtures/s2_search_2017.json: -------------------------------------------------------------------------------- 1 | {"months":["tiles/22/K/HV/2017/1/","tiles/22/K/HV/2017/10/","tiles/22/K/HV/2017/11/","tiles/22/K/HV/2017/12/","tiles/22/K/HV/2017/2/","tiles/22/K/HV/2017/3/","tiles/22/K/HV/2017/4/","tiles/22/K/HV/2017/5/","tiles/22/K/HV/2017/6/","tiles/22/K/HV/2017/7/","tiles/22/K/HV/2017/8/","tiles/22/K/HV/2017/9/"],"days":[["tiles/22/K/HV/2017/1/12/","tiles/22/K/HV/2017/1/15/","tiles/22/K/HV/2017/1/2/","tiles/22/K/HV/2017/1/25/","tiles/22/K/HV/2017/1/5/"],["tiles/22/K/HV/2017/10/14/","tiles/22/K/HV/2017/10/17/","tiles/22/K/HV/2017/10/19/","tiles/22/K/HV/2017/10/2/","tiles/22/K/HV/2017/10/22/","tiles/22/K/HV/2017/10/24/","tiles/22/K/HV/2017/10/29/","tiles/22/K/HV/2017/10/4/","tiles/22/K/HV/2017/10/9/"],["tiles/22/K/HV/2017/11/11/","tiles/22/K/HV/2017/11/13/","tiles/22/K/HV/2017/11/16/","tiles/22/K/HV/2017/11/18/","tiles/22/K/HV/2017/11/23/","tiles/22/K/HV/2017/11/26/","tiles/22/K/HV/2017/11/28/","tiles/22/K/HV/2017/11/3/","tiles/22/K/HV/2017/11/6/","tiles/22/K/HV/2017/11/8/"],["tiles/22/K/HV/2017/12/1/","tiles/22/K/HV/2017/12/13/","tiles/22/K/HV/2017/12/16/","tiles/22/K/HV/2017/12/18/","tiles/22/K/HV/2017/12/21/","tiles/22/K/HV/2017/12/23/","tiles/22/K/HV/2017/12/26/","tiles/22/K/HV/2017/12/28/","tiles/22/K/HV/2017/12/6/","tiles/22/K/HV/2017/12/8/"],["tiles/22/K/HV/2017/2/1/","tiles/22/K/HV/2017/2/11/","tiles/22/K/HV/2017/2/14/","tiles/22/K/HV/2017/2/21/","tiles/22/K/HV/2017/2/24/"],["tiles/22/K/HV/2017/3/13/","tiles/22/K/HV/2017/3/16/","tiles/22/K/HV/2017/3/23/","tiles/22/K/HV/2017/3/3/"],["tiles/22/K/HV/2017/4/12/","tiles/22/K/HV/2017/4/2/","tiles/22/K/HV/2017/4/22/","tiles/22/K/HV/2017/4/25/","tiles/22/K/HV/2017/4/5/"],["tiles/22/K/HV/2017/5/12/","tiles/22/K/HV/2017/5/15/","tiles/22/K/HV/2017/5/2/","tiles/22/K/HV/2017/5/22/"],["tiles/22/K/HV/2017/6/1/","tiles/22/K/HV/2017/6/11/","tiles/22/K/HV/2017/6/21/","tiles/22/K/HV/2017/6/24/","tiles/22/K/HV/2017/6/4/"],["tiles/22/K/HV/2017/7/1/","tiles/22/K/HV/2017/7/11/","tiles/22/K/HV/2017/7/14/","tiles/22/K/HV/2017/7/21/","tiles/22/K/HV/2017/7/26/","tiles/22/K/HV/2017/7/29/","tiles/22/K/HV/2017/7/31/","tiles/22/K/HV/2017/7/6/","tiles/22/K/HV/2017/7/9/"],["tiles/22/K/HV/2017/8/10/","tiles/22/K/HV/2017/8/15/","tiles/22/K/HV/2017/8/18/","tiles/22/K/HV/2017/8/23/","tiles/22/K/HV/2017/8/25/","tiles/22/K/HV/2017/8/3/","tiles/22/K/HV/2017/8/30/"],["tiles/22/K/HV/2017/9/12/","tiles/22/K/HV/2017/9/19/","tiles/22/K/HV/2017/9/24/","tiles/22/K/HV/2017/9/27/","tiles/22/K/HV/2017/9/29/","tiles/22/K/HV/2017/9/4/","tiles/22/K/HV/2017/9/7/","tiles/22/K/HV/2017/9/9/"]],"versions":[["tiles/22/K/HV/2017/1/12/0/"],["tiles/22/K/HV/2017/1/15/0/"],["tiles/22/K/HV/2017/1/2/0/"],["tiles/22/K/HV/2017/1/25/0/"],["tiles/22/K/HV/2017/1/5/0/"],["tiles/22/K/HV/2017/2/1/0/"],["tiles/22/K/HV/2017/2/11/0/"],["tiles/22/K/HV/2017/2/14/0/"],["tiles/22/K/HV/2017/2/21/0/"],["tiles/22/K/HV/2017/2/24/0/"],["tiles/22/K/HV/2017/3/13/0/"],["tiles/22/K/HV/2017/3/16/0/"],["tiles/22/K/HV/2017/3/23/0/"],["tiles/22/K/HV/2017/3/3/0/"],["tiles/22/K/HV/2017/4/12/0/"],["tiles/22/K/HV/2017/4/2/0/"],["tiles/22/K/HV/2017/4/22/0/"],["tiles/22/K/HV/2017/4/25/0/"],["tiles/22/K/HV/2017/4/5/0/"],["tiles/22/K/HV/2017/5/12/0/"],["tiles/22/K/HV/2017/5/15/0/"],["tiles/22/K/HV/2017/5/2/0/"]],"results":[{"sat":"S2A","path":"tiles/22/K/HV/2017/1/12/0/","utm_zone":"22","latitude_band":"K","grid_square":"HV","num":"0","acquisition_date":"20170112","browseURL":"https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/22/K/HV/2017/1/12/0/preview.jpg","scene_id":"S2A_tile_20170112_22KHV_0"},{"sat":"S2A","path":"tiles/22/K/HV/2017/1/15/0/","utm_zone":"22","latitude_band":"K","grid_square":"HV","num":"0","acquisition_date":"20170115","browseURL":"https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/22/K/HV/2017/1/15/0/preview.jpg","scene_id":"S2A_tile_20170115_22KHV_0"},{"sat":"S2A","path":"tiles/22/K/HV/2017/1/2/0/","utm_zone":"22","latitude_band":"K","grid_square":"HV","num":"0","acquisition_date":"20170102","browseURL":"https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/22/K/HV/2017/1/2/0/preview.jpg","scene_id":"S2A_tile_20170102_22KHV_0"},{"sat":"S2A","path":"tiles/22/K/HV/2017/1/25/0/","utm_zone":"22","latitude_band":"K","grid_square":"HV","num":"0","acquisition_date":"20170125","browseURL":"https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/22/K/HV/2017/1/25/0/preview.jpg","scene_id":"S2A_tile_20170125_22KHV_0"},{"sat":"S2A","path":"tiles/22/K/HV/2017/1/5/0/","utm_zone":"22","latitude_band":"K","grid_square":"HV","num":"0","acquisition_date":"20170105","browseURL":"https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/22/K/HV/2017/1/5/0/preview.jpg","scene_id":"S2A_tile_20170105_22KHV_0"},{"sat":"S2A","path":"tiles/22/K/HV/2017/2/1/0/","utm_zone":"22","latitude_band":"K","grid_square":"HV","num":"0","acquisition_date":"20170201","browseURL":"https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/22/K/HV/2017/2/1/0/preview.jpg","scene_id":"S2A_tile_20170201_22KHV_0"},{"sat":"S2A","path":"tiles/22/K/HV/2017/2/11/0/","utm_zone":"22","latitude_band":"K","grid_square":"HV","num":"0","acquisition_date":"20170211","browseURL":"https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/22/K/HV/2017/2/11/0/preview.jpg","scene_id":"S2A_tile_20170211_22KHV_0"},{"sat":"S2A","path":"tiles/22/K/HV/2017/2/14/0/","utm_zone":"22","latitude_band":"K","grid_square":"HV","num":"0","acquisition_date":"20170214","browseURL":"https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/22/K/HV/2017/2/14/0/preview.jpg","scene_id":"S2A_tile_20170214_22KHV_0"},{"sat":"S2A","path":"tiles/22/K/HV/2017/2/21/0/","utm_zone":"22","latitude_band":"K","grid_square":"HV","num":"0","acquisition_date":"20170221","browseURL":"https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/22/K/HV/2017/2/21/0/preview.jpg","scene_id":"S2A_tile_20170221_22KHV_0"},{"sat":"S2A","path":"tiles/22/K/HV/2017/2/24/0/","utm_zone":"22","latitude_band":"K","grid_square":"HV","num":"0","acquisition_date":"20170224","browseURL":"https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/22/K/HV/2017/2/24/0/preview.jpg","scene_id":"S2A_tile_20170224_22KHV_0"},{"sat":"S2A","path":"tiles/22/K/HV/2017/3/13/0/","utm_zone":"22","latitude_band":"K","grid_square":"HV","num":"0","acquisition_date":"20170313","browseURL":"https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/22/K/HV/2017/3/13/0/preview.jpg","scene_id":"S2A_tile_20170313_22KHV_0"},{"sat":"S2A","path":"tiles/22/K/HV/2017/3/16/0/","utm_zone":"22","latitude_band":"K","grid_square":"HV","num":"0","acquisition_date":"20170316","browseURL":"https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/22/K/HV/2017/3/16/0/preview.jpg","scene_id":"S2A_tile_20170316_22KHV_0"},{"sat":"S2A","path":"tiles/22/K/HV/2017/3/23/0/","utm_zone":"22","latitude_band":"K","grid_square":"HV","num":"0","acquisition_date":"20170323","browseURL":"https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/22/K/HV/2017/3/23/0/preview.jpg","scene_id":"S2A_tile_20170323_22KHV_0"},{"sat":"S2A","path":"tiles/22/K/HV/2017/3/3/0/","utm_zone":"22","latitude_band":"K","grid_square":"HV","num":"0","acquisition_date":"20170303","browseURL":"https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/22/K/HV/2017/3/3/0/preview.jpg","scene_id":"S2A_tile_20170303_22KHV_0"},{"sat":"S2A","path":"tiles/22/K/HV/2017/4/12/0/","utm_zone":"22","latitude_band":"K","grid_square":"HV","num":"0","acquisition_date":"20170412","browseURL":"https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/22/K/HV/2017/4/12/0/preview.jpg","scene_id":"S2A_tile_20170412_22KHV_0"},{"sat":"S2A","path":"tiles/22/K/HV/2017/4/2/0/","utm_zone":"22","latitude_band":"K","grid_square":"HV","num":"0","acquisition_date":"20170402","browseURL":"https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/22/K/HV/2017/4/2/0/preview.jpg","scene_id":"S2A_tile_20170402_22KHV_0"},{"sat":"S2A","path":"tiles/22/K/HV/2017/4/22/0/","utm_zone":"22","latitude_band":"K","grid_square":"HV","num":"0","acquisition_date":"20170422","browseURL":"https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/22/K/HV/2017/4/22/0/preview.jpg","scene_id":"S2A_tile_20170422_22KHV_0"},{"sat":"S2A","path":"tiles/22/K/HV/2017/4/25/0/","utm_zone":"22","latitude_band":"K","grid_square":"HV","num":"0","acquisition_date":"20170425","browseURL":"https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/22/K/HV/2017/4/25/0/preview.jpg","scene_id":"S2A_tile_20170425_22KHV_0"},{"sat":"S2A","path":"tiles/22/K/HV/2017/4/5/0/","utm_zone":"22","latitude_band":"K","grid_square":"HV","num":"0","acquisition_date":"20170405","browseURL":"https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/22/K/HV/2017/4/5/0/preview.jpg","scene_id":"S2A_tile_20170405_22KHV_0"},{"sat":"S2A","path":"tiles/22/K/HV/2017/5/12/0/","utm_zone":"22","latitude_band":"K","grid_square":"HV","num":"0","acquisition_date":"20170512","browseURL":"https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/22/K/HV/2017/5/12/0/preview.jpg","scene_id":"S2A_tile_20170512_22KHV_0"},{"sat":"S2A","path":"tiles/22/K/HV/2017/5/15/0/","utm_zone":"22","latitude_band":"K","grid_square":"HV","num":"0","acquisition_date":"20170515","browseURL":"https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/22/K/HV/2017/5/15/0/preview.jpg","scene_id":"S2A_tile_20170515_22KHV_0"},{"sat":"S2A","path":"tiles/22/K/HV/2017/5/2/0/","utm_zone":"22","latitude_band":"K","grid_square":"HV","num":"0","acquisition_date":"20170502","browseURL":"https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/22/K/HV/2017/5/2/0/preview.jpg","scene_id":"S2A_tile_20170502_22KHV_0"}]} 2 | -------------------------------------------------------------------------------- /aws_sat_api/search.py: -------------------------------------------------------------------------------- 1 | """Search handlers.""" 2 | 3 | import os 4 | import json 5 | import itertools 6 | from functools import partial 7 | from concurrent import futures 8 | from datetime import datetime, timezone 9 | from typing import Union 10 | 11 | from boto3.session import Session as boto3_session 12 | 13 | from aws_sat_api import utils, aws 14 | 15 | region = os.environ.get('AWS_REGION', 'us-east-1') 16 | max_worker = os.environ.get('MAX_WORKER', 50) 17 | 18 | landsat_bucket = 'landsat-pds' 19 | cbers_bucket = 'cbers-meta-pds' 20 | sentinel_bucket = 'sentinel-s2' 21 | 22 | 23 | def get_s2_info(bucket, scene_path, full=False, s3=None, request_pays=False): 24 | """Return Sentinel metadata.""" 25 | scene_info = scene_path.split('/') 26 | 27 | year = scene_info[4] 28 | month = utils.zeroPad(scene_info[5], 2) 29 | day = utils.zeroPad(scene_info[6], 2) 30 | acquisition_date = f'{year}{month}{day}' 31 | 32 | latitude_band = scene_info[2] 33 | grid_square = scene_info[3] 34 | num = scene_info[7] 35 | 36 | info = { 37 | 'sat': 'S2A', 38 | 'path': scene_path, 39 | 'utm_zone': scene_info[1], 40 | 'latitude_band': latitude_band, 41 | 'grid_square': grid_square, 42 | 'num': num, 43 | 'acquisition_date': acquisition_date, 44 | 'browseURL': f'https://roda.sentinel-hub.com/sentinel-s2-l1c/{scene_path}preview.jpg'} 45 | 46 | utm = utils.zeroPad(info['utm_zone'], 2) 47 | info['scene_id'] = f'S2A_tile_{acquisition_date}_{utm}{latitude_band}{grid_square}_{num}' 48 | 49 | if full: 50 | try: 51 | data = json.loads(aws.get_object(bucket, f'{scene_path}tileInfo.json', s3=s3, request_pays=request_pays)) 52 | sat_name = data['productName'][0:3] 53 | info['sat'] = sat_name 54 | info['geometry'] = data.get('tileGeometry') 55 | info['coverage'] = data.get('dataCoveragePercentage') 56 | info['cloud_coverage'] = data.get('cloudyPixelPercentage') 57 | info['scene_id'] = f'{sat_name}_tile_{acquisition_date}_{utm}{latitude_band}{grid_square}_{num}' 58 | except: 59 | print(f'Could not get info from {scene_path}tileInfo.json') 60 | 61 | return info 62 | 63 | 64 | def get_l8_info(scene_id, full=False, s3=None): 65 | """Return Landsat-8 metadata.""" 66 | info = utils.landsat_parse_scene_id(scene_id) 67 | aws_url = f'https://{landsat_bucket}.s3.amazonaws.com' 68 | scene_key = info["key"] 69 | info['browseURL'] = f'{aws_url}/{scene_key}_thumb_large.jpg' 70 | info['thumbURL'] = f'{aws_url}/{scene_key}_thumb_small.jpg' 71 | 72 | if full: 73 | try: 74 | data = json.loads(aws.get_object(landsat_bucket, f'{scene_key}_MTL.json', s3=s3)) 75 | image_attr = data['L1_METADATA_FILE']['IMAGE_ATTRIBUTES'] 76 | prod_meta = data['L1_METADATA_FILE']['PRODUCT_METADATA'] 77 | 78 | info['sun_azimuth'] = image_attr.get('SUN_AZIMUTH') 79 | info['sun_elevation'] = image_attr.get('SUN_ELEVATION') 80 | info['cloud_coverage'] = image_attr.get('CLOUD_COVER') 81 | info['cloud_coverage_land'] = image_attr.get('CLOUD_COVER_LAND') 82 | info['geometry'] = { 83 | 'type': 'Polygon', 84 | 'coordinates': [[ 85 | [prod_meta['CORNER_UR_LON_PRODUCT'], prod_meta['CORNER_UR_LAT_PRODUCT']], 86 | [prod_meta['CORNER_UL_LON_PRODUCT'], prod_meta['CORNER_UL_LAT_PRODUCT']], 87 | [prod_meta['CORNER_LL_LON_PRODUCT'], prod_meta['CORNER_LL_LAT_PRODUCT']], 88 | [prod_meta['CORNER_LR_LON_PRODUCT'], prod_meta['CORNER_LR_LAT_PRODUCT']], 89 | [prod_meta['CORNER_UR_LON_PRODUCT'], prod_meta['CORNER_UR_LAT_PRODUCT']] 90 | ]]} 91 | except: 92 | print(f'Could not get info from {scene_key}_MTL.json') 93 | 94 | return info 95 | 96 | 97 | def landsat(path, row, full=False): 98 | """Get Landsat scenes.""" 99 | path = utils.zeroPad(path, 3) 100 | row = utils.zeroPad(row, 3) 101 | 102 | levels = ['L8', 'c1/L8'] 103 | prefixes = [f'{l}/{path}/{row}/' for l in levels] 104 | 105 | # WARNING: This is fast but not thread safe 106 | session = boto3_session(region_name=region) 107 | s3 = session.client('s3') 108 | 109 | _ls_worker = partial(aws.list_directory, landsat_bucket, s3=s3) 110 | with futures.ThreadPoolExecutor(max_workers=2) as executor: 111 | results = executor.map(_ls_worker, prefixes) 112 | results = itertools.chain.from_iterable(results) 113 | 114 | scene_ids = [os.path.basename(key.strip('/')) for key in results] 115 | 116 | _info_worker = partial(get_l8_info, full=full, s3=s3) 117 | with futures.ThreadPoolExecutor(max_workers=max_worker) as executor: 118 | results = executor.map(_info_worker, scene_ids) 119 | 120 | return results 121 | 122 | 123 | def cbers(path, row, sensor='MUX'): 124 | """Get CBERS scenes. 125 | 126 | Valid values for sensor are: 'MUX', 'AWFI', 'PAN5M' and 'PAN10M'. 127 | """ 128 | path = utils.zeroPad(path, 3) 129 | row = utils.zeroPad(row, 3) 130 | 131 | prefix = f'CBERS4/{sensor}/{path}/{row}/' 132 | 133 | session = boto3_session(region_name=region) 134 | s3 = session.client('s3') 135 | 136 | results = aws.list_directory(cbers_bucket, prefix, s3=s3) 137 | scene_ids = [os.path.basename(key.strip('/')) for key in results] 138 | results = [] 139 | for scene_id in scene_ids: 140 | info = utils.cbers_parse_scene_id(scene_id) 141 | scene_key = info["key"] 142 | preview_id = '_'.join(scene_id.split('_')[0:-1]) 143 | info['thumbURL'] = f'https://s3.amazonaws.com/{cbers_bucket}/{scene_key}/{preview_id}_small.jpeg' 144 | info['browseURL'] = f'https://s3.amazonaws.com/{cbers_bucket}/{scene_key}/{preview_id}.jpg' 145 | results.append(info) 146 | 147 | return results 148 | 149 | 150 | def sentinel2(utm: Union[str, int], lat: str, grid: str, 151 | full: bool=False, level: str='l1c', 152 | start_date: datetime=None, end_date: datetime=None): 153 | """Get Sentinel 2 scenes. 154 | 155 | The start_date and end_date are optional. 156 | If no date is defined the function will search images between 2015 and now. 157 | 158 | :param utm: Grid zone designator. 159 | :param lat: Latitude band. 160 | :param grid: Grid square. 161 | :param full: Full search. 162 | :param level: Processing level ('l1c' or 'l2a'). 163 | :param start_date: Start date in UTC. 164 | :param end_date: End date in UTC. 165 | """ 166 | if level not in ['l1c', 'l2a']: 167 | raise Exception('Sentinel 2 Level must be "l1c" or "l2a"') 168 | 169 | s2_bucket = f'{sentinel_bucket}-{level}' 170 | request_pays = True 171 | 172 | start_date = start_date or datetime(2015, 1, 1) 173 | end_date = end_date or datetime.now(timezone.utc) 174 | 175 | # Converts the time zone or sets a new tz for naive objects. 176 | start_date = start_date.astimezone(timezone.utc) 177 | end_date = end_date.astimezone(timezone.utc) 178 | 179 | if start_date > end_date: 180 | raise ValueError("Invalid date range (start_date > end_date).") 181 | 182 | if start_date.year < 2015: 183 | raise ValueError(f"Start date out of range {start_date.year} < 2015.") 184 | 185 | years = range(start_date.year, end_date.year + 1) 186 | 187 | utm = str(utm).lstrip('0') 188 | 189 | prefixes = [f'tiles/{utm}/{lat}/{grid}/{y}/' for y in years] 190 | 191 | # WARNING: This is fast but not thread safe 192 | session = boto3_session(region_name=region) 193 | s3 = session.client('s3') 194 | 195 | _ls_worker = partial(aws.list_directory, s2_bucket, s3=s3, request_pays=request_pays) 196 | with futures.ThreadPoolExecutor(max_workers=max_worker) as executor: 197 | results = executor.map(_ls_worker, prefixes) 198 | months_dirs = itertools.chain.from_iterable(results) 199 | 200 | _ls_worker = partial(aws.list_directory, s2_bucket, s3=s3, request_pays=request_pays) 201 | with futures.ThreadPoolExecutor(max_workers=max_worker) as executor: 202 | results = executor.map(_ls_worker, months_dirs) 203 | # print(list(results)) 204 | days_dirs = itertools.chain.from_iterable(results) 205 | 206 | # Now, filter by date intervals. 207 | selected_days = [] 208 | for item in days_dirs: 209 | item_date = datetime(*[int(i) for i in item.split("/")[4:7]], tzinfo=timezone.utc) 210 | if start_date.date() <= item_date.date() <= end_date.date(): 211 | selected_days.append(item) 212 | 213 | _ls_worker = partial(aws.list_directory, s2_bucket, s3=s3, request_pays=request_pays) 214 | with futures.ThreadPoolExecutor(max_workers=max_worker) as executor: 215 | results = executor.map(_ls_worker, selected_days) 216 | version_dirs = itertools.chain.from_iterable(results) 217 | 218 | _info_worker = partial(get_s2_info, s2_bucket, full=full, s3=s3, request_pays=request_pays) 219 | with futures.ThreadPoolExecutor(max_workers=max_worker) as executor: 220 | results = executor.map(_info_worker, version_dirs) 221 | 222 | return results 223 | -------------------------------------------------------------------------------- /tests/fixtures/LC81781192017016LGN00_MTL.json: -------------------------------------------------------------------------------- 1 | { 2 | "L1_METADATA_FILE": { 3 | "IMAGE_ATTRIBUTES": { 4 | "EARTH_SUN_DISTANCE": 0.9837106, 5 | "CLOUD_COVER": 41.92, 6 | "IMAGE_QUALITY_TIRS": 7, 7 | "SUN_AZIMUTH": 94.68629511, 8 | "SUN_ELEVATION": 20.38601689, 9 | "TIRS_SSM_POSITION_STATUS": "ESTIMATED", 10 | "ROLL_ANGLE": -0.0, 11 | "CLOUD_COVER_LAND": 41.92, 12 | "TIRS_SSM_MODEL": "PRELIMINARY", 13 | "IMAGE_QUALITY_OLI": 9 14 | }, 15 | "TIRS_THERMAL_CONSTANTS": { 16 | "K2_CONSTANT_BAND_10": 1321.0789, 17 | "K2_CONSTANT_BAND_11": 1201.1442, 18 | "K1_CONSTANT_BAND_10": 774.8853, 19 | "K1_CONSTANT_BAND_11": 480.8883 20 | }, 21 | "RADIOMETRIC_RESCALING": { 22 | "RADIANCE_MULT_BAND_7": 0.00052959, 23 | "RADIANCE_MULT_BAND_6": 0.0015712, 24 | "RADIANCE_MULT_BAND_5": 0.006318, 25 | "RADIANCE_MULT_BAND_4": 0.010324, 26 | "RADIANCE_MULT_BAND_3": 0.012243, 27 | "RADIANCE_MULT_BAND_2": 0.013287, 28 | "RADIANCE_MULT_BAND_1": 0.012975, 29 | "RADIANCE_MULT_BAND_9": 0.0024692, 30 | "RADIANCE_MULT_BAND_8": 0.011684, 31 | "RADIANCE_ADD_BAND_9": -12.34611, 32 | "RADIANCE_ADD_BAND_8": -58.42181, 33 | "RADIANCE_MULT_BAND_11": 0.0003342, 34 | "RADIANCE_MULT_BAND_10": 0.0003342, 35 | "REFLECTANCE_ADD_BAND_9": -0.1, 36 | "REFLECTANCE_ADD_BAND_8": -0.1, 37 | "RADIANCE_ADD_BAND_1": -64.87512, 38 | "REFLECTANCE_ADD_BAND_6": -0.1, 39 | "RADIANCE_ADD_BAND_3": -61.21736, 40 | "RADIANCE_ADD_BAND_2": -66.43291, 41 | "RADIANCE_ADD_BAND_5": -31.59004, 42 | "RADIANCE_ADD_BAND_4": -51.62192, 43 | "RADIANCE_ADD_BAND_7": -2.64795, 44 | "RADIANCE_ADD_BAND_6": -7.85616, 45 | "REFLECTANCE_MULT_BAND_9": 2e-05, 46 | "REFLECTANCE_MULT_BAND_8": 2e-05, 47 | "REFLECTANCE_MULT_BAND_1": 2e-05, 48 | "REFLECTANCE_MULT_BAND_3": 2e-05, 49 | "REFLECTANCE_MULT_BAND_2": 2e-05, 50 | "REFLECTANCE_MULT_BAND_5": 2e-05, 51 | "REFLECTANCE_MULT_BAND_4": 2e-05, 52 | "REFLECTANCE_MULT_BAND_7": 2e-05, 53 | "REFLECTANCE_MULT_BAND_6": 2e-05, 54 | "REFLECTANCE_ADD_BAND_7": -0.1, 55 | "REFLECTANCE_ADD_BAND_5": -0.1, 56 | "REFLECTANCE_ADD_BAND_4": -0.1, 57 | "RADIANCE_ADD_BAND_11": 0.1, 58 | "RADIANCE_ADD_BAND_10": 0.1, 59 | "REFLECTANCE_ADD_BAND_3": -0.1, 60 | "REFLECTANCE_ADD_BAND_2": -0.1, 61 | "REFLECTANCE_ADD_BAND_1": -0.1 62 | }, 63 | "PRODUCT_METADATA": { 64 | "PANCHROMATIC_LINES": 15881, 65 | "NADIR_OFFNADIR": "NADIR", 66 | "DATA_TYPE": "L1GT", 67 | "CORNER_LR_LAT_PRODUCT": -82.33503, 68 | "SCENE_CENTER_TIME": "09:08:18.1467090Z", 69 | "CORNER_LR_PROJECTION_Y_PRODUCT": 579600.0, 70 | "CORNER_LL_PROJECTION_X_PRODUCT": -839700.0, 71 | "FILE_NAME_BAND_QUALITY": "LC81781192017016LGN00_BQA.TIF", 72 | "CORNER_UR_PROJECTION_Y_PRODUCT": 817800.0, 73 | "CORNER_LL_PROJECTION_Y_PRODUCT": 579600.0, 74 | "TARGET_WRS_ROW": 119, 75 | "BPF_NAME_TIRS": "LT8BPF20170113003504_20170113025501.01", 76 | "THERMAL_LINES": 7941, 77 | "CORNER_LR_PROJECTION_X_PRODUCT": -599700.0, 78 | "FILE_NAME_BAND_3": "LC81781192017016LGN00_B3.TIF", 79 | "CORNER_LL_LAT_PRODUCT": -80.62951, 80 | "FILE_NAME_BAND_1": "LC81781192017016LGN00_B1.TIF", 81 | "REFLECTIVE_LINES": 7941, 82 | "FILE_NAME_BAND_7": "LC81781192017016LGN00_B7.TIF", 83 | "FILE_NAME_BAND_6": "LC81781192017016LGN00_B6.TIF", 84 | "FILE_NAME_BAND_5": "LC81781192017016LGN00_B5.TIF", 85 | "FILE_NAME_BAND_4": "LC81781192017016LGN00_B4.TIF", 86 | "FILE_NAME_BAND_9": "LC81781192017016LGN00_B9.TIF", 87 | "FILE_NAME_BAND_8": "LC81781192017016LGN00_B8.TIF", 88 | "SPACECRAFT_ID": "LANDSAT_8", 89 | "ELEVATION_SOURCE": "RAMP", 90 | "CPF_NAME": "L8CPF20170101_20170331.02", 91 | "CORNER_UR_LON_PRODUCT": -36.25293, 92 | "FILE_NAME_BAND_11": "LC81781192017016LGN00_B11.TIF", 93 | "CORNER_LR_LON_PRODUCT": -45.97646, 94 | "CORNER_UL_PROJECTION_X_PRODUCT": -839700.0, 95 | "PANCHROMATIC_SAMPLES": 16001, 96 | "SENSOR_ID": "OLI_TIRS", 97 | "FILE_NAME_BAND_2": "LC81781192017016LGN00_B2.TIF", 98 | "WRS_PATH": 178, 99 | "OUTPUT_FORMAT": "GEOTIFF", 100 | "FILE_NAME_BAND_10": "LC81781192017016LGN00_B10.TIF", 101 | "WRS_ROW": 119, 102 | "CORNER_UL_PROJECTION_Y_PRODUCT": 817800.0, 103 | "BPF_NAME_OLI": "LO8BPF20170116082611_20170116100504.01", 104 | "TARGET_WRS_PATH": 178, 105 | "CORNER_UL_LON_PRODUCT": -45.75699, 106 | "THERMAL_SAMPLES": 8001, 107 | "METADATA_FILE_NAME": "LC81781192017016LGN00_MTL.txt", 108 | "REFLECTIVE_SAMPLES": 8001, 109 | "CORNER_UR_PROJECTION_X_PRODUCT": -599700.0, 110 | "RLUT_FILE_NAME": "L8RLUT20150303_20431231v11.h5", 111 | "CORNER_UL_LAT_PRODUCT": -79.24253, 112 | "CORNER_UR_LAT_PRODUCT": -80.68613, 113 | "CORNER_LL_LON_PRODUCT": -55.38476, 114 | "DATE_ACQUIRED": "2017-01-16" 115 | }, 116 | "PROJECTION_PARAMETERS": { 117 | "GRID_CELL_SIZE_REFLECTIVE": 30.0, 118 | "TRUE_SCALE_LAT": -71.0, 119 | "MAP_PROJECTION": "PS", 120 | "ORIENTATION": "NORTH_UP", 121 | "ELLIPSOID": "WGS84", 122 | "GRID_CELL_SIZE_THERMAL": 30.0, 123 | "DATUM": "WGS84", 124 | "GRID_CELL_SIZE_PANCHROMATIC": 15.0, 125 | "RESAMPLING_OPTION": "CUBIC_CONVOLUTION", 126 | "FALSE_NORTHING": 0, 127 | "VERTICAL_LON_FROM_POLE": 0.0, 128 | "FALSE_EASTING": 0 129 | }, 130 | "METADATA_FILE_INFO": { 131 | "ORIGIN": "Image courtesy of the U.S. Geological Survey", 132 | "LANDSAT_SCENE_ID": "LC81781192017016LGN00", 133 | "PROCESSING_SOFTWARE_VERSION": "LPGS_2.6.2", 134 | "FILE_DATE": "2017-01-16T13:30:15Z", 135 | "STATION_ID": "LGN", 136 | "REQUEST_ID": "0501701169467_00010" 137 | }, 138 | "MIN_MAX_PIXEL_VALUE": { 139 | "QUANTIZE_CAL_MAX_BAND_5": 65535, 140 | "QUANTIZE_CAL_MAX_BAND_4": 65535, 141 | "QUANTIZE_CAL_MAX_BAND_7": 65535, 142 | "QUANTIZE_CAL_MAX_BAND_6": 65535, 143 | "QUANTIZE_CAL_MAX_BAND_1": 65535, 144 | "QUANTIZE_CAL_MAX_BAND_3": 65535, 145 | "QUANTIZE_CAL_MAX_BAND_2": 65535, 146 | "QUANTIZE_CAL_MAX_BAND_9": 65535, 147 | "QUANTIZE_CAL_MAX_BAND_8": 65535, 148 | "QUANTIZE_CAL_MIN_BAND_9": 1, 149 | "QUANTIZE_CAL_MIN_BAND_8": 1, 150 | "QUANTIZE_CAL_MIN_BAND_7": 1, 151 | "QUANTIZE_CAL_MIN_BAND_6": 1, 152 | "QUANTIZE_CAL_MIN_BAND_5": 1, 153 | "QUANTIZE_CAL_MIN_BAND_4": 1, 154 | "QUANTIZE_CAL_MIN_BAND_3": 1, 155 | "QUANTIZE_CAL_MIN_BAND_2": 1, 156 | "QUANTIZE_CAL_MIN_BAND_1": 1, 157 | "QUANTIZE_CAL_MIN_BAND_11": 1, 158 | "QUANTIZE_CAL_MIN_BAND_10": 1, 159 | "QUANTIZE_CAL_MAX_BAND_11": 65535, 160 | "QUANTIZE_CAL_MAX_BAND_10": 65535 161 | }, 162 | "MIN_MAX_RADIANCE": { 163 | "RADIANCE_MINIMUM_BAND_6": -7.85459, 164 | "RADIANCE_MINIMUM_BAND_7": -2.64742, 165 | "RADIANCE_MINIMUM_BAND_4": -51.6116, 166 | "RADIANCE_MINIMUM_BAND_5": -31.58372, 167 | "RADIANCE_MINIMUM_BAND_2": -66.41962, 168 | "RADIANCE_MINIMUM_BAND_3": -61.20512, 169 | "RADIANCE_MINIMUM_BAND_1": -64.86214, 170 | "RADIANCE_MINIMUM_BAND_8": -58.41013, 171 | "RADIANCE_MINIMUM_BAND_9": -12.34364, 172 | "RADIANCE_MINIMUM_BAND_10": 0.10033, 173 | "RADIANCE_MINIMUM_BAND_11": 0.10033, 174 | "RADIANCE_MAXIMUM_BAND_10": 22.0018, 175 | "RADIANCE_MAXIMUM_BAND_11": 22.0018, 176 | "RADIANCE_MAXIMUM_BAND_8": 707.31287, 177 | "RADIANCE_MAXIMUM_BAND_9": 149.47429, 178 | "RADIANCE_MAXIMUM_BAND_1": 785.44299, 179 | "RADIANCE_MAXIMUM_BAND_2": 804.30322, 180 | "RADIANCE_MAXIMUM_BAND_3": 741.15857, 181 | "RADIANCE_MAXIMUM_BAND_4": 624.98663, 182 | "RADIANCE_MAXIMUM_BAND_5": 382.46066, 183 | "RADIANCE_MAXIMUM_BAND_6": 95.11449, 184 | "RADIANCE_MAXIMUM_BAND_7": 32.05867 185 | }, 186 | "MIN_MAX_REFLECTANCE": { 187 | "REFLECTANCE_MAXIMUM_BAND_8": 1.2107, 188 | "REFLECTANCE_MINIMUM_BAND_8": -0.09998, 189 | "REFLECTANCE_MAXIMUM_BAND_9": 1.2107, 190 | "REFLECTANCE_MINIMUM_BAND_9": -0.09998, 191 | "REFLECTANCE_MINIMUM_BAND_4": -0.09998, 192 | "REFLECTANCE_MINIMUM_BAND_5": -0.09998, 193 | "REFLECTANCE_MINIMUM_BAND_6": -0.09998, 194 | "REFLECTANCE_MINIMUM_BAND_7": -0.09998, 195 | "REFLECTANCE_MINIMUM_BAND_1": -0.09998, 196 | "REFLECTANCE_MINIMUM_BAND_2": -0.09998, 197 | "REFLECTANCE_MINIMUM_BAND_3": -0.09998, 198 | "REFLECTANCE_MAXIMUM_BAND_6": 1.2107, 199 | "REFLECTANCE_MAXIMUM_BAND_7": 1.2107, 200 | "REFLECTANCE_MAXIMUM_BAND_4": 1.2107, 201 | "REFLECTANCE_MAXIMUM_BAND_5": 1.2107, 202 | "REFLECTANCE_MAXIMUM_BAND_2": 1.2107, 203 | "REFLECTANCE_MAXIMUM_BAND_3": 1.2107, 204 | "REFLECTANCE_MAXIMUM_BAND_1": 1.2107 205 | } 206 | } 207 | } -------------------------------------------------------------------------------- /tests/fixtures/LC81782462014232LGN00_MTL.json: -------------------------------------------------------------------------------- 1 | { 2 | "L1_METADATA_FILE": { 3 | "IMAGE_ATTRIBUTES": { 4 | "GROUND_CONTROL_POINTS_VERSION": 4, 5 | "EARTH_SUN_DISTANCE": 1.0118572, 6 | "GEOMETRIC_RMSE_MODEL": 31.126, 7 | "CLOUD_COVER": 38.13, 8 | "IMAGE_QUALITY_TIRS": 9, 9 | "SUN_AZIMUTH": -115.79513548, 10 | "SUN_ELEVATION": 16.11011632, 11 | "TIRS_SSM_POSITION_STATUS": "NOMINAL", 12 | "ROLL_ANGLE": -0.001, 13 | "CLOUD_COVER_LAND": 50.55, 14 | "GROUND_CONTROL_POINTS_MODEL": 12, 15 | "GEOMETRIC_RMSE_MODEL_X": 25.076, 16 | "GEOMETRIC_RMSE_MODEL_Y": 18.439, 17 | "TIRS_SSM_MODEL": "ACTUAL", 18 | "IMAGE_QUALITY_OLI": 9 19 | }, 20 | "TIRS_THERMAL_CONSTANTS": { 21 | "K2_CONSTANT_BAND_10": 1321.0789, 22 | "K2_CONSTANT_BAND_11": 1201.1442, 23 | "K1_CONSTANT_BAND_10": 774.8853, 24 | "K1_CONSTANT_BAND_11": 480.8883 25 | }, 26 | "RADIOMETRIC_RESCALING": { 27 | "RADIANCE_MULT_BAND_7": 0.00050054, 28 | "RADIANCE_MULT_BAND_6": 0.001485, 29 | "RADIANCE_MULT_BAND_5": 0.0059714, 30 | "RADIANCE_MULT_BAND_4": 0.009758, 31 | "RADIANCE_MULT_BAND_3": 0.011572, 32 | "RADIANCE_MULT_BAND_2": 0.012558, 33 | "RADIANCE_MULT_BAND_1": 0.012263, 34 | "RADIANCE_MULT_BAND_9": 0.0023338, 35 | "RADIANCE_MULT_BAND_8": 0.011043, 36 | "RADIANCE_ADD_BAND_9": -11.6688, 37 | "RADIANCE_ADD_BAND_8": -55.21681, 38 | "RADIANCE_MULT_BAND_11": 0.0003342, 39 | "RADIANCE_MULT_BAND_10": 0.0003342, 40 | "REFLECTANCE_ADD_BAND_9": -0.1, 41 | "REFLECTANCE_ADD_BAND_8": -0.1, 42 | "RADIANCE_ADD_BAND_1": -61.31609, 43 | "REFLECTANCE_ADD_BAND_6": -0.1, 44 | "RADIANCE_ADD_BAND_3": -57.85899, 45 | "RADIANCE_ADD_BAND_2": -62.78842, 46 | "RADIANCE_ADD_BAND_5": -29.85702, 47 | "RADIANCE_ADD_BAND_4": -48.78996, 48 | "RADIANCE_ADD_BAND_7": -2.50268, 49 | "RADIANCE_ADD_BAND_6": -7.42517, 50 | "REFLECTANCE_MULT_BAND_9": 2e-05, 51 | "REFLECTANCE_MULT_BAND_8": 2e-05, 52 | "REFLECTANCE_MULT_BAND_1": 2e-05, 53 | "REFLECTANCE_MULT_BAND_3": 2e-05, 54 | "REFLECTANCE_MULT_BAND_2": 2e-05, 55 | "REFLECTANCE_MULT_BAND_5": 2e-05, 56 | "REFLECTANCE_MULT_BAND_4": 2e-05, 57 | "REFLECTANCE_MULT_BAND_7": 2e-05, 58 | "REFLECTANCE_MULT_BAND_6": 2e-05, 59 | "REFLECTANCE_ADD_BAND_7": -0.1, 60 | "REFLECTANCE_ADD_BAND_5": -0.1, 61 | "REFLECTANCE_ADD_BAND_4": -0.1, 62 | "RADIANCE_ADD_BAND_11": 0.1, 63 | "RADIANCE_ADD_BAND_10": 0.1, 64 | "REFLECTANCE_ADD_BAND_3": -0.1, 65 | "REFLECTANCE_ADD_BAND_2": -0.1, 66 | "REFLECTANCE_ADD_BAND_1": -0.1 67 | }, 68 | "PRODUCT_METADATA": { 69 | "PANCHROMATIC_LINES": 12981, 70 | "NADIR_OFFNADIR": "NADIR", 71 | "DATA_TYPE": "L1T", 72 | "CORNER_LR_LAT_PRODUCT": 80.89847, 73 | "SCENE_CENTER_TIME": "09:58:51.7576908Z", 74 | "CORNER_LR_PROJECTION_Y_PRODUCT": 8987400.0, 75 | "CORNER_LL_PROJECTION_X_PRODUCT": 408900.0, 76 | "FILE_NAME_BAND_QUALITY": "LC81782462014232LGN00_BQA.TIF", 77 | "CORNER_UR_PROJECTION_Y_PRODUCT": 9182100.0, 78 | "CORNER_LL_PROJECTION_Y_PRODUCT": 8987400.0, 79 | "TARGET_WRS_ROW": 246, 80 | "BPF_NAME_TIRS": "LT8BPF20140820095048_20140820103544.01", 81 | "THERMAL_LINES": 6491, 82 | "CORNER_LR_PROJECTION_X_PRODUCT": 606300.0, 83 | "FILE_NAME_BAND_3": "LC81782462014232LGN00_B3.TIF", 84 | "CORNER_LL_LAT_PRODUCT": 80.91159, 85 | "FILE_NAME_BAND_1": "LC81782462014232LGN00_B1.TIF", 86 | "REFLECTIVE_LINES": 6491, 87 | "FILE_NAME_BAND_7": "LC81782462014232LGN00_B7.TIF", 88 | "FILE_NAME_BAND_6": "LC81782462014232LGN00_B6.TIF", 89 | "FILE_NAME_BAND_5": "LC81782462014232LGN00_B5.TIF", 90 | "FILE_NAME_BAND_4": "LC81782462014232LGN00_B4.TIF", 91 | "FILE_NAME_BAND_9": "LC81782462014232LGN00_B9.TIF", 92 | "FILE_NAME_BAND_8": "LC81782462014232LGN00_B8.TIF", 93 | "SPACECRAFT_ID": "LANDSAT_8", 94 | "ELEVATION_SOURCE": "GLS2000", 95 | "CPF_NAME": "L8CPF20140701_20140930.03", 96 | "CORNER_UR_LON_PRODUCT": 100.4436, 97 | "FILE_NAME_BAND_11": "LC81782462014232LGN00_B11.TIF", 98 | "CORNER_LR_LON_PRODUCT": 99.02993, 99 | "CORNER_UL_PROJECTION_X_PRODUCT": 408900.0, 100 | "PANCHROMATIC_SAMPLES": 13161, 101 | "SENSOR_ID": "OLI_TIRS", 102 | "FILE_NAME_BAND_2": "LC81782462014232LGN00_B2.TIF", 103 | "WRS_PATH": 178, 104 | "OUTPUT_FORMAT": "GEOTIFF", 105 | "FILE_NAME_BAND_10": "LC81782462014232LGN00_B10.TIF", 106 | "WRS_ROW": 246, 107 | "CORNER_UL_PROJECTION_Y_PRODUCT": 9182100.0, 108 | "BPF_NAME_OLI": "LO8BPF20140820095442_20140820103451.02", 109 | "TARGET_WRS_PATH": 178, 110 | "CORNER_UL_LON_PRODUCT": 86.61133, 111 | "THERMAL_SAMPLES": 6581, 112 | "METADATA_FILE_NAME": "LC81782462014232LGN00_MTL.txt", 113 | "REFLECTIVE_SAMPLES": 6581, 114 | "CORNER_UR_PROJECTION_X_PRODUCT": 606300.0, 115 | "RLUT_FILE_NAME": "L8RLUT20130211_20150302v10.h5", 116 | "CORNER_UL_LAT_PRODUCT": 82.64704, 117 | "CORNER_UR_LAT_PRODUCT": 82.63078, 118 | "CORNER_LL_LON_PRODUCT": 87.8273, 119 | "DATE_ACQUIRED": "2014-08-20" 120 | }, 121 | "PROJECTION_PARAMETERS": { 122 | "UTM_ZONE": 46, 123 | "GRID_CELL_SIZE_REFLECTIVE": 30.0, 124 | "MAP_PROJECTION": "UTM", 125 | "ORIENTATION": "NORTH_UP", 126 | "ELLIPSOID": "WGS84", 127 | "GRID_CELL_SIZE_THERMAL": 30.0, 128 | "DATUM": "WGS84", 129 | "GRID_CELL_SIZE_PANCHROMATIC": 15.0, 130 | "RESAMPLING_OPTION": "CUBIC_CONVOLUTION" 131 | }, 132 | "METADATA_FILE_INFO": { 133 | "ORIGIN": "Image courtesy of the U.S. Geological Survey", 134 | "LANDSAT_SCENE_ID": "LC81782462014232LGN00", 135 | "PROCESSING_SOFTWARE_VERSION": "LPGS_2.6.2", 136 | "FILE_DATE": "2016-05-21T19:33:10Z", 137 | "STATION_ID": "LGN", 138 | "REQUEST_ID": "0501605204617_01404" 139 | }, 140 | "MIN_MAX_PIXEL_VALUE": { 141 | "QUANTIZE_CAL_MAX_BAND_5": 65535, 142 | "QUANTIZE_CAL_MAX_BAND_4": 65535, 143 | "QUANTIZE_CAL_MAX_BAND_7": 65535, 144 | "QUANTIZE_CAL_MAX_BAND_6": 65535, 145 | "QUANTIZE_CAL_MAX_BAND_1": 65535, 146 | "QUANTIZE_CAL_MAX_BAND_3": 65535, 147 | "QUANTIZE_CAL_MAX_BAND_2": 65535, 148 | "QUANTIZE_CAL_MAX_BAND_9": 65535, 149 | "QUANTIZE_CAL_MAX_BAND_8": 65535, 150 | "QUANTIZE_CAL_MIN_BAND_9": 1, 151 | "QUANTIZE_CAL_MIN_BAND_8": 1, 152 | "QUANTIZE_CAL_MIN_BAND_7": 1, 153 | "QUANTIZE_CAL_MIN_BAND_6": 1, 154 | "QUANTIZE_CAL_MIN_BAND_5": 1, 155 | "QUANTIZE_CAL_MIN_BAND_4": 1, 156 | "QUANTIZE_CAL_MIN_BAND_3": 1, 157 | "QUANTIZE_CAL_MIN_BAND_2": 1, 158 | "QUANTIZE_CAL_MIN_BAND_1": 1, 159 | "QUANTIZE_CAL_MIN_BAND_11": 1, 160 | "QUANTIZE_CAL_MIN_BAND_10": 1, 161 | "QUANTIZE_CAL_MAX_BAND_11": 65535, 162 | "QUANTIZE_CAL_MAX_BAND_10": 65535 163 | }, 164 | "MIN_MAX_RADIANCE": { 165 | "RADIANCE_MINIMUM_BAND_6": -7.42369, 166 | "RADIANCE_MINIMUM_BAND_7": -2.50218, 167 | "RADIANCE_MINIMUM_BAND_4": -48.7802, 168 | "RADIANCE_MINIMUM_BAND_5": -29.85105, 169 | "RADIANCE_MINIMUM_BAND_2": -62.77586, 170 | "RADIANCE_MINIMUM_BAND_3": -57.84742, 171 | "RADIANCE_MINIMUM_BAND_1": -61.30382, 172 | "RADIANCE_MINIMUM_BAND_8": -55.20576, 173 | "RADIANCE_MINIMUM_BAND_9": -11.66647, 174 | "RADIANCE_MINIMUM_BAND_10": 0.10033, 175 | "RADIANCE_MINIMUM_BAND_11": 0.10033, 176 | "RADIANCE_MAXIMUM_BAND_10": 22.0018, 177 | "RADIANCE_MAXIMUM_BAND_11": 22.0018, 178 | "RADIANCE_MAXIMUM_BAND_8": 668.50989, 179 | "RADIANCE_MAXIMUM_BAND_9": 141.27419, 180 | "RADIANCE_MAXIMUM_BAND_1": 742.35388, 181 | "RADIANCE_MAXIMUM_BAND_2": 760.17938, 182 | "RADIANCE_MAXIMUM_BAND_3": 700.49884, 183 | "RADIANCE_MAXIMUM_BAND_4": 590.70007, 184 | "RADIANCE_MAXIMUM_BAND_5": 361.47897, 185 | "RADIANCE_MAXIMUM_BAND_6": 89.89654, 186 | "RADIANCE_MAXIMUM_BAND_7": 30.29995 187 | }, 188 | "MIN_MAX_REFLECTANCE": { 189 | "REFLECTANCE_MAXIMUM_BAND_8": 1.2107, 190 | "REFLECTANCE_MINIMUM_BAND_8": -0.09998, 191 | "REFLECTANCE_MAXIMUM_BAND_9": 1.2107, 192 | "REFLECTANCE_MINIMUM_BAND_9": -0.09998, 193 | "REFLECTANCE_MINIMUM_BAND_4": -0.09998, 194 | "REFLECTANCE_MINIMUM_BAND_5": -0.09998, 195 | "REFLECTANCE_MINIMUM_BAND_6": -0.09998, 196 | "REFLECTANCE_MINIMUM_BAND_7": -0.09998, 197 | "REFLECTANCE_MINIMUM_BAND_1": -0.09998, 198 | "REFLECTANCE_MINIMUM_BAND_2": -0.09998, 199 | "REFLECTANCE_MINIMUM_BAND_3": -0.09998, 200 | "REFLECTANCE_MAXIMUM_BAND_6": 1.2107, 201 | "REFLECTANCE_MAXIMUM_BAND_7": 1.2107, 202 | "REFLECTANCE_MAXIMUM_BAND_4": 1.2107, 203 | "REFLECTANCE_MAXIMUM_BAND_5": 1.2107, 204 | "REFLECTANCE_MAXIMUM_BAND_2": 1.2107, 205 | "REFLECTANCE_MAXIMUM_BAND_3": 1.2107, 206 | "REFLECTANCE_MAXIMUM_BAND_1": 1.2107 207 | } 208 | } 209 | } -------------------------------------------------------------------------------- /tests/fixtures/LC08_L1GT_178119_20180103_20180103_01_RT_MTL.json: -------------------------------------------------------------------------------- 1 | { 2 | "L1_METADATA_FILE": { 3 | "IMAGE_ATTRIBUTES": { 4 | "TIRS_STRAY_LIGHT_CORRECTION_SOURCE": "TIRS", 5 | "SATURATION_BAND_8": "N", 6 | "SATURATION_BAND_9": "N", 7 | "TRUNCATION_OLI": "UPPER", 8 | "EARTH_SUN_DISTANCE": 0.9832843, 9 | "CLOUD_COVER": 64.66, 10 | "SATURATION_BAND_3": "N", 11 | "IMAGE_QUALITY_TIRS": 7, 12 | "SATURATION_BAND_1": "N", 13 | "SUN_AZIMUTH": 93.74503077, 14 | "SATURATION_BAND_7": "N", 15 | "SATURATION_BAND_4": "N", 16 | "SATURATION_BAND_5": "N", 17 | "SATURATION_BAND_2": "N", 18 | "SUN_ELEVATION": 22.51092792, 19 | "TIRS_SSM_POSITION_STATUS": "ESTIMATED", 20 | "ROLL_ANGLE": -0.0, 21 | "CLOUD_COVER_LAND": 64.66, 22 | "SATURATION_BAND_6": "N", 23 | "TIRS_SSM_MODEL": "PRELIMINARY", 24 | "IMAGE_QUALITY_OLI": 9 25 | }, 26 | "TIRS_THERMAL_CONSTANTS": { 27 | "K1_CONSTANT_BAND_11": 480.8883, 28 | "K2_CONSTANT_BAND_11": 1201.1442, 29 | "K1_CONSTANT_BAND_10": 774.8853, 30 | "K2_CONSTANT_BAND_10": 1321.0789 31 | }, 32 | "RADIOMETRIC_RESCALING": { 33 | "RADIANCE_MULT_BAND_7": 0.00053005, 34 | "RADIANCE_MULT_BAND_6": 0.0015726, 35 | "RADIANCE_MULT_BAND_5": 0.0063235, 36 | "RADIANCE_MULT_BAND_4": 0.010333, 37 | "RADIANCE_MULT_BAND_3": 0.012254, 38 | "RADIANCE_MULT_BAND_2": 0.013298, 39 | "RADIANCE_MULT_BAND_1": 0.012986, 40 | "RADIANCE_MULT_BAND_9": 0.0024714, 41 | "RADIANCE_MULT_BAND_8": 0.011694, 42 | "RADIANCE_ADD_BAND_9": -12.35681, 43 | "RADIANCE_ADD_BAND_8": -58.47248, 44 | "RADIANCE_MULT_BAND_11": 0.0003342, 45 | "RADIANCE_MULT_BAND_10": 0.0003342, 46 | "REFLECTANCE_ADD_BAND_9": -0.1, 47 | "REFLECTANCE_ADD_BAND_8": -0.1, 48 | "RADIANCE_ADD_BAND_1": -64.93138, 49 | "REFLECTANCE_ADD_BAND_6": -0.1, 50 | "RADIANCE_ADD_BAND_3": -61.27045, 51 | "RADIANCE_ADD_BAND_2": -66.49052, 52 | "RADIANCE_ADD_BAND_5": -31.61744, 53 | "RADIANCE_ADD_BAND_4": -51.66669, 54 | "RADIANCE_ADD_BAND_7": -2.65024, 55 | "RADIANCE_ADD_BAND_6": -7.86297, 56 | "REFLECTANCE_MULT_BAND_9": 2e-05, 57 | "REFLECTANCE_MULT_BAND_8": 2e-05, 58 | "REFLECTANCE_MULT_BAND_1": 2e-05, 59 | "REFLECTANCE_MULT_BAND_3": 2e-05, 60 | "REFLECTANCE_MULT_BAND_2": 2e-05, 61 | "REFLECTANCE_MULT_BAND_5": 2e-05, 62 | "REFLECTANCE_MULT_BAND_4": 2e-05, 63 | "REFLECTANCE_MULT_BAND_7": 2e-05, 64 | "REFLECTANCE_MULT_BAND_6": 2e-05, 65 | "REFLECTANCE_ADD_BAND_7": -0.1, 66 | "REFLECTANCE_ADD_BAND_5": -0.1, 67 | "REFLECTANCE_ADD_BAND_4": -0.1, 68 | "RADIANCE_ADD_BAND_11": 0.1, 69 | "RADIANCE_ADD_BAND_10": 0.1, 70 | "REFLECTANCE_ADD_BAND_3": -0.1, 71 | "REFLECTANCE_ADD_BAND_2": -0.1, 72 | "REFLECTANCE_ADD_BAND_1": -0.1 73 | }, 74 | "PRODUCT_METADATA": { 75 | "BPF_NAME_OLI": "LO8BPF20180103082634_20180103091401.01", 76 | "PANCHROMATIC_LINES": 15841, 77 | "NADIR_OFFNADIR": "NADIR", 78 | "DATA_TYPE": "L1GT", 79 | "CORNER_LR_LAT_PRODUCT": -82.33114, 80 | "SCENE_CENTER_TIME": "09:08:17.3011050Z", 81 | "CORNER_LR_PROJECTION_Y_PRODUCT": 579900.0, 82 | "CORNER_LL_PROJECTION_X_PRODUCT": -840000.0, 83 | "FILE_NAME_BAND_QUALITY": "LC08_L1GT_178119_20180103_20180103_01_RT_BQA.TIF", 84 | "CORNER_UR_PROJECTION_Y_PRODUCT": 817500.0, 85 | "CORNER_LL_PROJECTION_Y_PRODUCT": 579900.0, 86 | "TARGET_WRS_ROW": 119, 87 | "BPF_NAME_TIRS": "LT8BPF20180102115508_20180102141520.01", 88 | "THERMAL_LINES": 7921, 89 | "CORNER_LR_PROJECTION_X_PRODUCT": -600000.0, 90 | "FILE_NAME_BAND_3": "LC08_L1GT_178119_20180103_20180103_01_RT_B3.TIF", 91 | "CORNER_LL_LAT_PRODUCT": -80.6257, 92 | "FILE_NAME_BAND_1": "LC08_L1GT_178119_20180103_20180103_01_RT_B1.TIF", 93 | "REFLECTIVE_LINES": 7921, 94 | "FILE_NAME_BAND_7": "LC08_L1GT_178119_20180103_20180103_01_RT_B7.TIF", 95 | "FILE_NAME_BAND_6": "LC08_L1GT_178119_20180103_20180103_01_RT_B6.TIF", 96 | "FILE_NAME_BAND_5": "LC08_L1GT_178119_20180103_20180103_01_RT_B5.TIF", 97 | "FILE_NAME_BAND_4": "LC08_L1GT_178119_20180103_20180103_01_RT_B4.TIF", 98 | "FILE_NAME_BAND_9": "LC08_L1GT_178119_20180103_20180103_01_RT_B9.TIF", 99 | "FILE_NAME_BAND_8": "LC08_L1GT_178119_20180103_20180103_01_RT_B8.TIF", 100 | "SPACECRAFT_ID": "LANDSAT_8", 101 | "ELEVATION_SOURCE": "RAMP", 102 | "CPF_NAME": "LC08CPF_20180101_20180331_01.01", 103 | "CORNER_UL_PROJECTION_X_PRODUCT": -840000.0, 104 | "FILE_NAME_BAND_11": "LC08_L1GT_178119_20180103_20180103_01_RT_B11.TIF", 105 | "CORNER_LR_LON_PRODUCT": -45.97596, 106 | "CORNER_UR_LON_PRODUCT": -36.27662, 107 | "PANCHROMATIC_SAMPLES": 16001, 108 | "SENSOR_ID": "OLI_TIRS", 109 | "FILE_NAME_BAND_2": "LC08_L1GT_178119_20180103_20180103_01_RT_B2.TIF", 110 | "OUTPUT_FORMAT": "GEOTIFF", 111 | "FILE_NAME_BAND_10": "LC08_L1GT_178119_20180103_20180103_01_RT_B10.TIF", 112 | "WRS_ROW": 119, 113 | "WRS_PATH": 178, 114 | "COLLECTION_CATEGORY": "RT", 115 | "ANGLE_COEFFICIENT_FILE_NAME": "LC08_L1GT_178119_20180103_20180103_01_RT_ANG.txt", 116 | "TARGET_WRS_PATH": 178, 117 | "CORNER_UL_LON_PRODUCT": -45.77772, 118 | "THERMAL_SAMPLES": 8001, 119 | "METADATA_FILE_NAME": "LC08_L1GT_178119_20180103_20180103_01_RT_MTL.txt", 120 | "CORNER_UL_PROJECTION_Y_PRODUCT": 817500.0, 121 | "REFLECTIVE_SAMPLES": 8001, 122 | "CORNER_UR_PROJECTION_X_PRODUCT": -600000.0, 123 | "RLUT_FILE_NAME": "LC08RLUT_20150303_20431231_01_12.h5", 124 | "CORNER_UL_LAT_PRODUCT": -79.24248, 125 | "CORNER_UR_LAT_PRODUCT": -80.68672, 126 | "CORNER_LL_LON_PRODUCT": -55.38046, 127 | "DATE_ACQUIRED": "2018-01-03" 128 | }, 129 | "PROJECTION_PARAMETERS": { 130 | "GRID_CELL_SIZE_REFLECTIVE": 30.0, 131 | "TRUE_SCALE_LAT": -71.0, 132 | "MAP_PROJECTION": "PS", 133 | "ORIENTATION": "NORTH_UP", 134 | "ELLIPSOID": "WGS84", 135 | "GRID_CELL_SIZE_THERMAL": 30.0, 136 | "DATUM": "WGS84", 137 | "GRID_CELL_SIZE_PANCHROMATIC": 15.0, 138 | "RESAMPLING_OPTION": "CUBIC_CONVOLUTION", 139 | "FALSE_NORTHING": 0, 140 | "VERTICAL_LON_FROM_POLE": 0.0, 141 | "FALSE_EASTING": 0 142 | }, 143 | "METADATA_FILE_INFO": { 144 | "ORIGIN": "Image courtesy of the U.S. Geological Survey", 145 | "LANDSAT_PRODUCT_ID": "LC08_L1GT_178119_20180103_20180103_01_RT", 146 | "LANDSAT_SCENE_ID": "LC81781192018003LGN00", 147 | "PROCESSING_SOFTWARE_VERSION": "LPGS_13.0.0", 148 | "FILE_DATE": "2018-01-03T14:18:07Z", 149 | "COLLECTION_NUMBER": 1, 150 | "STATION_ID": "LGN", 151 | "REQUEST_ID": "0501801037219_00011" 152 | }, 153 | "MIN_MAX_PIXEL_VALUE": { 154 | "QUANTIZE_CAL_MAX_BAND_5": 65535, 155 | "QUANTIZE_CAL_MAX_BAND_4": 65535, 156 | "QUANTIZE_CAL_MAX_BAND_7": 65535, 157 | "QUANTIZE_CAL_MAX_BAND_6": 65535, 158 | "QUANTIZE_CAL_MAX_BAND_1": 65535, 159 | "QUANTIZE_CAL_MAX_BAND_3": 65535, 160 | "QUANTIZE_CAL_MAX_BAND_2": 65535, 161 | "QUANTIZE_CAL_MAX_BAND_9": 65535, 162 | "QUANTIZE_CAL_MAX_BAND_8": 65535, 163 | "QUANTIZE_CAL_MIN_BAND_9": 1, 164 | "QUANTIZE_CAL_MIN_BAND_8": 1, 165 | "QUANTIZE_CAL_MIN_BAND_7": 1, 166 | "QUANTIZE_CAL_MIN_BAND_6": 1, 167 | "QUANTIZE_CAL_MIN_BAND_5": 1, 168 | "QUANTIZE_CAL_MIN_BAND_4": 1, 169 | "QUANTIZE_CAL_MIN_BAND_3": 1, 170 | "QUANTIZE_CAL_MIN_BAND_2": 1, 171 | "QUANTIZE_CAL_MIN_BAND_1": 1, 172 | "QUANTIZE_CAL_MIN_BAND_11": 1, 173 | "QUANTIZE_CAL_MIN_BAND_10": 1, 174 | "QUANTIZE_CAL_MAX_BAND_11": 65535, 175 | "QUANTIZE_CAL_MAX_BAND_10": 65535 176 | }, 177 | "MIN_MAX_RADIANCE": { 178 | "RADIANCE_MINIMUM_BAND_6": -7.8614, 179 | "RADIANCE_MINIMUM_BAND_7": -2.64971, 180 | "RADIANCE_MINIMUM_BAND_4": -51.65636, 181 | "RADIANCE_MINIMUM_BAND_5": -31.61112, 182 | "RADIANCE_MINIMUM_BAND_2": -66.47723, 183 | "RADIANCE_MINIMUM_BAND_3": -61.25819, 184 | "RADIANCE_MINIMUM_BAND_1": -64.91839, 185 | "RADIANCE_MINIMUM_BAND_8": -58.46078, 186 | "RADIANCE_MINIMUM_BAND_9": -12.35434, 187 | "RADIANCE_MINIMUM_BAND_10": 0.10033, 188 | "RADIANCE_MINIMUM_BAND_11": 0.10033, 189 | "RADIANCE_MAXIMUM_BAND_10": 22.0018, 190 | "RADIANCE_MAXIMUM_BAND_11": 22.0018, 191 | "RADIANCE_MAXIMUM_BAND_8": 707.92627, 192 | "RADIANCE_MAXIMUM_BAND_9": 149.60393, 193 | "RADIANCE_MAXIMUM_BAND_1": 786.12415, 194 | "RADIANCE_MAXIMUM_BAND_2": 805.00073, 195 | "RADIANCE_MAXIMUM_BAND_3": 741.80127, 196 | "RADIANCE_MAXIMUM_BAND_4": 625.52863, 197 | "RADIANCE_MAXIMUM_BAND_5": 382.79233, 198 | "RADIANCE_MAXIMUM_BAND_6": 95.19697, 199 | "RADIANCE_MAXIMUM_BAND_7": 32.08648 200 | }, 201 | "MIN_MAX_REFLECTANCE": { 202 | "REFLECTANCE_MAXIMUM_BAND_8": 1.2107, 203 | "REFLECTANCE_MINIMUM_BAND_8": -0.09998, 204 | "REFLECTANCE_MAXIMUM_BAND_9": 1.2107, 205 | "REFLECTANCE_MINIMUM_BAND_9": -0.09998, 206 | "REFLECTANCE_MINIMUM_BAND_4": -0.09998, 207 | "REFLECTANCE_MINIMUM_BAND_5": -0.09998, 208 | "REFLECTANCE_MINIMUM_BAND_6": -0.09998, 209 | "REFLECTANCE_MINIMUM_BAND_7": -0.09998, 210 | "REFLECTANCE_MINIMUM_BAND_1": -0.09998, 211 | "REFLECTANCE_MINIMUM_BAND_2": -0.09998, 212 | "REFLECTANCE_MINIMUM_BAND_3": -0.09998, 213 | "REFLECTANCE_MAXIMUM_BAND_6": 1.2107, 214 | "REFLECTANCE_MAXIMUM_BAND_7": 1.2107, 215 | "REFLECTANCE_MAXIMUM_BAND_4": 1.2107, 216 | "REFLECTANCE_MAXIMUM_BAND_5": 1.2107, 217 | "REFLECTANCE_MAXIMUM_BAND_2": 1.2107, 218 | "REFLECTANCE_MAXIMUM_BAND_3": 1.2107, 219 | "REFLECTANCE_MAXIMUM_BAND_1": 1.2107 220 | } 221 | } 222 | } -------------------------------------------------------------------------------- /tests/test_search.py: -------------------------------------------------------------------------------- 1 | """tests aws_sat_api.search""" 2 | 3 | import os 4 | import json 5 | from io import BytesIO 6 | from datetime import date, datetime 7 | 8 | import pytest 9 | from mock import patch 10 | 11 | from aws_sat_api import search 12 | from botocore.exceptions import ClientError 13 | 14 | 15 | @patch('aws_sat_api.aws.get_object') 16 | def test_get_s2_info_valid(get_object): 17 | """Should work as expected 18 | """ 19 | 20 | bucket = 'sentinel-s2-l1c' 21 | scene_path = 'tiles/38/S/NG/2017/10/9/1/' 22 | full = False 23 | s3 = None 24 | request_pays = False 25 | 26 | expected = { 27 | 'acquisition_date': '20171009', 28 | 'browseURL': 'https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/38/S/NG/2017/10/9/1/preview.jpg', 29 | 'grid_square': 'NG', 30 | 'latitude_band': 'S', 31 | 'num': '1', 32 | 'path': 'tiles/38/S/NG/2017/10/9/1/', 33 | 'sat': 'S2A', 34 | 'scene_id': 'S2A_tile_20171009_38SNG_1', 35 | 'utm_zone': '38'} 36 | 37 | assert search.get_s2_info(bucket, scene_path, full, s3, request_pays) == expected 38 | get_object.assert_not_called() 39 | 40 | 41 | @patch('aws_sat_api.aws.get_object') 42 | def test_get_s2_info_validFull(get_object): 43 | """Should work as expected 44 | """ 45 | 46 | path = os.path.join(os.path.dirname(__file__), f'fixtures/tileInfo.json') 47 | with open(path, 'rb') as f: 48 | tileInfo = f.read() 49 | 50 | get_object.return_value = tileInfo 51 | 52 | bucket = 'sentinel-s2-l1c' 53 | scene_path = 'tiles/38/S/NG/2017/10/9/1/' 54 | full = True 55 | s3 = None 56 | request_pays = False 57 | 58 | expected = { 59 | 'acquisition_date': '20171009', 60 | 'browseURL': 'https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/38/S/NG/2017/10/9/1/preview.jpg', 61 | 'cloud_coverage': 5.01, 62 | 'coverage': 36.52, 63 | 'geometry': { 64 | 'coordinates': [[ 65 | [499980.0, 4200000.0], 66 | [609780.0, 4200000.0], 67 | [609780.0, 4090200.0], 68 | [499980.0, 4090200.0], 69 | [499980.0, 4200000.0]]], 70 | 'crs': {'properties': {'name': 'urn:ogc:def:crs:EPSG:8.8.1:32638'}, 'type': 'name'}, 71 | 'type': 'Polygon'}, 72 | 'grid_square': 'NG', 73 | 'latitude_band': 'S', 74 | 'num': '1', 75 | 'path': 'tiles/38/S/NG/2017/10/9/1/', 76 | 'sat': 'S2B', 77 | 'scene_id': 'S2B_tile_20171009_38SNG_1', 78 | 'utm_zone': '38'} 79 | 80 | assert search.get_s2_info(bucket, scene_path, full, s3, request_pays) == expected 81 | get_object.assert_called_once() 82 | 83 | 84 | @patch('aws_sat_api.aws.get_object') 85 | def test_get_s2_info_botoError(get_object): 86 | """Should work as expected 87 | """ 88 | 89 | get_object.side_effect = ClientError( 90 | {'Error': {'Code': 500, 'Message': 'Error'}}, 'get_object') 91 | 92 | bucket = 'sentinel-s2-l1c' 93 | scene_path = 'tiles/38/S/NG/2017/10/9/1/' 94 | full = True 95 | s3 = None 96 | request_pays = False 97 | 98 | expected = { 99 | 'acquisition_date': '20171009', 100 | 'browseURL': 'https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/38/S/NG/2017/10/9/1/preview.jpg', 101 | 'grid_square': 'NG', 102 | 'latitude_band': 'S', 103 | 'num': '1', 104 | 'path': 'tiles/38/S/NG/2017/10/9/1/', 105 | 'sat': 'S2A', 106 | 'scene_id': 'S2A_tile_20171009_38SNG_1', 107 | 'utm_zone': '38'} 108 | 109 | assert search.get_s2_info(bucket, scene_path, full, s3, request_pays) == expected 110 | get_object.assert_called_once() 111 | 112 | 113 | @patch('aws_sat_api.aws.get_object') 114 | def test_get_s2_info_validFullPays(get_object): 115 | """Should work as expected 116 | """ 117 | 118 | path = os.path.join(os.path.dirname(__file__), f'fixtures/tileInfo.json') 119 | with open(path, 'rb') as f: 120 | tileInfo = f.read() 121 | 122 | get_object.return_value = tileInfo 123 | 124 | bucket = 'sentinel-s2-l1c' 125 | scene_path = 'tiles/38/S/NG/2017/10/9/1/' 126 | full = True 127 | s3 = None 128 | request_pays = True 129 | 130 | expected = { 131 | 'acquisition_date': '20171009', 132 | 'browseURL': 'https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/38/S/NG/2017/10/9/1/preview.jpg', 133 | 'cloud_coverage': 5.01, 134 | 'coverage': 36.52, 135 | 'geometry': { 136 | 'coordinates': [[ 137 | [499980.0, 4200000.0], 138 | [609780.0, 4200000.0], 139 | [609780.0, 4090200.0], 140 | [499980.0, 4090200.0], 141 | [499980.0, 4200000.0]]], 142 | 'crs': {'properties': {'name': 'urn:ogc:def:crs:EPSG:8.8.1:32638'}, 'type': 'name'}, 143 | 'type': 'Polygon'}, 144 | 'grid_square': 'NG', 145 | 'latitude_band': 'S', 146 | 'num': '1', 147 | 'path': 'tiles/38/S/NG/2017/10/9/1/', 148 | 'sat': 'S2B', 149 | 'scene_id': 'S2B_tile_20171009_38SNG_1', 150 | 'utm_zone': '38'} 151 | 152 | assert search.get_s2_info(bucket, scene_path, full, s3, request_pays) == expected 153 | get_object.assert_called_once() 154 | assert get_object.call_args[1].get('request_pays') 155 | 156 | 157 | @patch('aws_sat_api.aws.get_object') 158 | def test_get_l8_info_valid(get_object): 159 | """Should work as expected 160 | """ 161 | 162 | scene_id = 'LC81782462014232LGN00' 163 | full = False 164 | s3 = None 165 | 166 | expected = { 167 | 'acquisitionJulianDay': '232', 168 | 'acquisitionYear': '2014', 169 | 'acquisition_date': '20140820', 170 | 'archiveVersion': '00', 171 | 'browseURL': 172 | 'https://landsat-pds.s3.amazonaws.com/L8/178/246/LC81782462014232LGN00/LC81782462014232LGN00_thumb_large.jpg', 173 | 'category': 'pre', 174 | 'groundStationIdentifier': 'LGN', 175 | 'key': 'L8/178/246/LC81782462014232LGN00/LC81782462014232LGN00', 176 | 'path': '178', 177 | 'row': '246', 178 | 'satellite': 'L8', 179 | 'scene_id': 'LC81782462014232LGN00', 180 | 'sensor': 'C', 181 | 'thumbURL': 182 | 'https://landsat-pds.s3.amazonaws.com/L8/178/246/LC81782462014232LGN00/LC81782462014232LGN00_thumb_small.jpg'} 183 | 184 | assert search.get_l8_info(scene_id, full, s3) == expected 185 | get_object.assert_not_called() 186 | 187 | 188 | @patch('aws_sat_api.aws.get_object') 189 | def test_get_l8_info_validFull(get_object): 190 | """Should work as expected 191 | """ 192 | 193 | path = os.path.join(os.path.dirname(__file__), f'fixtures/LC81782462014232LGN00_MTL.json') 194 | with open(path, 'rb') as f: 195 | tileInfo = f.read() 196 | 197 | get_object.return_value = tileInfo 198 | 199 | scene_id = 'LC81782462014232LGN00' 200 | full = True 201 | s3 = None 202 | 203 | expected = { 204 | 'acquisitionJulianDay': '232', 205 | 'acquisitionYear': '2014', 206 | 'acquisition_date': '20140820', 207 | 'archiveVersion': '00', 208 | 'browseURL': 209 | 'https://landsat-pds.s3.amazonaws.com/L8/178/246/LC81782462014232LGN00/LC81782462014232LGN00_thumb_large.jpg', 210 | 'category': 'pre', 211 | 'cloud_coverage': 38.13, 212 | 'cloud_coverage_land': 50.55, 213 | 'geometry': { 214 | 'coordinates': [[ 215 | [100.4436, 82.63078], 216 | [86.61133, 82.64704], 217 | [87.8273, 80.91159], 218 | [99.02993, 80.89847], 219 | [100.4436, 82.63078]]], 220 | 'type': 'Polygon'}, 221 | 'groundStationIdentifier': 'LGN', 222 | 'key': 'L8/178/246/LC81782462014232LGN00/LC81782462014232LGN00', 223 | 'path': '178', 224 | 'row': '246', 225 | 'satellite': 'L8', 226 | 'scene_id': 'LC81782462014232LGN00', 227 | 'sensor': 'C', 228 | 'sun_azimuth': -115.79513548, 229 | 'sun_elevation': 16.11011632, 230 | 'thumbURL': 231 | 'https://landsat-pds.s3.amazonaws.com/L8/178/246/LC81782462014232LGN00/LC81782462014232LGN00_thumb_small.jpg'} 232 | 233 | assert search.get_l8_info(scene_id, full, s3) == expected 234 | get_object.assert_called_once() 235 | 236 | 237 | @patch('aws_sat_api.aws.get_object') 238 | def test_get_l8_info_botoError(get_object): 239 | """Should work as expected 240 | """ 241 | 242 | get_object.side_effect = ClientError( 243 | {'Error': {'Code': 500, 'Message': 'Error'}}, 'get_object') 244 | 245 | scene_id = 'LC81782462014232LGN00' 246 | full = True 247 | s3 = None 248 | 249 | expected = { 250 | 'acquisitionJulianDay': '232', 251 | 'acquisitionYear': '2014', 252 | 'acquisition_date': '20140820', 253 | 'archiveVersion': '00', 254 | 'browseURL': 255 | 'https://landsat-pds.s3.amazonaws.com/L8/178/246/LC81782462014232LGN00/LC81782462014232LGN00_thumb_large.jpg', 256 | 'category': 'pre', 257 | 'groundStationIdentifier': 'LGN', 258 | 'key': 'L8/178/246/LC81782462014232LGN00/LC81782462014232LGN00', 259 | 'path': '178', 260 | 'row': '246', 261 | 'satellite': 'L8', 262 | 'scene_id': 'LC81782462014232LGN00', 263 | 'sensor': 'C', 264 | 'thumbURL': 265 | 'https://landsat-pds.s3.amazonaws.com/L8/178/246/LC81782462014232LGN00/LC81782462014232LGN00_thumb_small.jpg'} 266 | 267 | assert search.get_l8_info(scene_id, full, s3) == expected 268 | get_object.assert_called_once() 269 | 270 | 271 | @patch('aws_sat_api.aws.get_object') 272 | def test_get_l8_info_validC1(get_object): 273 | """Should work as expected 274 | """ 275 | 276 | scene_id = 'LC08_L1GT_178119_20180103_20180103_01_RT' 277 | full = False 278 | s3 = None 279 | 280 | expected = { 281 | 'acquisition_date': '20180103', 282 | 'browseURL': 283 | 'https://landsat-pds.s3.amazonaws.com/c1/L8/178/119/LC08_L1GT_178119_20180103_20180103_01_RT/LC08_L1GT_178119_20180103_20180103_01_RT_thumb_large.jpg', 284 | 'category': 'RT', 285 | 'collection': '01', 286 | 'correction_level': 'L1GT', 287 | 'ingestion_date': '20180103', 288 | 'key': 'c1/L8/178/119/LC08_L1GT_178119_20180103_20180103_01_RT/LC08_L1GT_178119_20180103_20180103_01_RT', 289 | 'path': '178', 290 | 'row': '119', 291 | 'satellite': 'L8', 292 | 'scene_id': 'LC08_L1GT_178119_20180103_20180103_01_RT', 293 | 'sensor': 'C', 294 | 'thumbURL': 295 | 'https://landsat-pds.s3.amazonaws.com/c1/L8/178/119/LC08_L1GT_178119_20180103_20180103_01_RT/LC08_L1GT_178119_20180103_20180103_01_RT_thumb_small.jpg'} 296 | 297 | assert search.get_l8_info(scene_id, full, s3) == expected 298 | get_object.assert_not_called() 299 | 300 | 301 | @patch('aws_sat_api.aws.get_object') 302 | def test_get_l8_info_validFullc1(get_object): 303 | """Should work as expected 304 | """ 305 | 306 | path = os.path.join(os.path.dirname(__file__), f'fixtures/LC08_L1GT_178119_20180103_20180103_01_RT_MTL.json') 307 | with open(path, 'rb') as f: 308 | tileInfo = f.read() 309 | 310 | get_object.return_value = tileInfo 311 | 312 | scene_id = 'LC08_L1GT_178119_20180103_20180103_01_RT' 313 | full = True 314 | s3 = None 315 | 316 | expected = { 317 | 'acquisition_date': '20180103', 318 | 'browseURL': 319 | 'https://landsat-pds.s3.amazonaws.com/c1/L8/178/119/LC08_L1GT_178119_20180103_20180103_01_RT/LC08_L1GT_178119_20180103_20180103_01_RT_thumb_large.jpg', 320 | 'category': 'RT', 321 | 'cloud_coverage': 64.66, 322 | 'cloud_coverage_land': 64.66, 323 | 'collection': '01', 324 | 'correction_level': 'L1GT', 325 | 'geometry': { 326 | 'coordinates': [[ 327 | [-36.27662, -80.68672], 328 | [-45.77772, -79.24248], 329 | [-55.38046, -80.6257], 330 | [-45.97596, -82.33114], 331 | [-36.27662, -80.68672]]], 332 | 'type': 'Polygon'}, 333 | 'ingestion_date': '20180103', 334 | 'key': 'c1/L8/178/119/LC08_L1GT_178119_20180103_20180103_01_RT/LC08_L1GT_178119_20180103_20180103_01_RT', 335 | 'path': '178', 336 | 'row': '119', 337 | 'satellite': 'L8', 338 | 'scene_id': 'LC08_L1GT_178119_20180103_20180103_01_RT', 339 | 'sensor': 'C', 340 | 'sun_azimuth': 93.74503077, 341 | 'sun_elevation': 22.51092792, 342 | 'thumbURL': 343 | 'https://landsat-pds.s3.amazonaws.com/c1/L8/178/119/LC08_L1GT_178119_20180103_20180103_01_RT/LC08_L1GT_178119_20180103_20180103_01_RT_thumb_small.jpg'} 344 | 345 | assert search.get_l8_info(scene_id, full, s3) == expected 346 | get_object.assert_called_once() 347 | 348 | 349 | @patch('aws_sat_api.search.boto3_session') 350 | @patch('aws_sat_api.aws.list_directory') 351 | def test_landsat_valid(list_directory, session): 352 | """Should work as expected 353 | """ 354 | 355 | session.return_value.client.return_value.get_object.return_value = True 356 | 357 | list_directory.side_effect = [ 358 | ['c1/L8/178/119/LC08_L1GT_178119_20180103_20180103_01_RT/'], 359 | ['L8/178/119/LC81781192017016LGN00/']] 360 | 361 | path = '178' 362 | row = '119' 363 | full = False 364 | 365 | assert list(search.landsat(path, row, full)) 366 | session.return_value.client.return_value.get_object.assert_not_called() 367 | assert list_directory.call_count == 2 368 | 369 | 370 | @patch('aws_sat_api.search.boto3_session') 371 | @patch('aws_sat_api.aws.list_directory') 372 | def test_landsat_validFull(list_directory, session): 373 | """Should work as expected 374 | """ 375 | 376 | path = os.path.join(os.path.dirname(__file__), f'fixtures/LC08_L1GT_178119_20180103_20180103_01_RT_MTL.json') 377 | with open(path, 'rb') as f: 378 | c1L8 = {'Body': BytesIO(f.read())} 379 | 380 | path = os.path.join(os.path.dirname(__file__), f'fixtures/LC81781192017016LGN00_MTL.json') 381 | with open(path, 'rb') as f: 382 | L8 = {'Body': BytesIO(f.read())} 383 | 384 | session.return_value.client.return_value.get_object.side_effect = [c1L8, L8] 385 | 386 | list_directory.side_effect = [ 387 | ['c1/L8/178/119/LC08_L1GT_178119_20180103_20180103_01_RT/'], 388 | ['L8/178/119/LC81781192017016LGN00/']] 389 | 390 | path = '178' 391 | row = '119' 392 | full = True 393 | 394 | assert list(search.landsat(path, row, full)) 395 | assert session.return_value.client.return_value.get_object.call_count == 2 396 | assert list_directory.call_count == 2 397 | 398 | 399 | @patch('aws_sat_api.search.boto3_session') 400 | @patch('aws_sat_api.aws.list_directory') 401 | def test_cbers_mux_valid(list_directory, session): 402 | """Should work as expected 403 | """ 404 | 405 | session.return_value.client.return_value = True 406 | 407 | list_directory.return_value = [ 408 | 'CBERS4/MUX/217/063/CBERS_4_MUX_20160416_217_063_L2/'] 409 | 410 | path = '217' 411 | row = '063' 412 | 413 | expected = [{ 414 | 'acquisition_date': '20160416', 415 | 'key': 'CBERS4/MUX/217/063/CBERS_4_MUX_20160416_217_063_L2', 416 | 'path': '217', 417 | 'processing_level': 'L2', 418 | 'row': '063', 419 | 'satellite': 'CBERS', 420 | 'scene_id': 'CBERS_4_MUX_20160416_217_063_L2', 421 | 'sensor': 'MUX', 422 | 'thumbURL': 423 | 'https://s3.amazonaws.com/cbers-meta-pds/CBERS4/MUX/217/063/CBERS_4_MUX_20160416_217_063_L2/CBERS_4_MUX_20160416_217_063_small.jpeg', 424 | 'browseURL': 425 | 'https://s3.amazonaws.com/cbers-meta-pds/CBERS4/MUX/217/063/CBERS_4_MUX_20160416_217_063_L2/CBERS_4_MUX_20160416_217_063.jpg', 426 | 'version': '4'}] 427 | 428 | assert list(search.cbers(path, row)) == expected 429 | 430 | @patch('aws_sat_api.search.boto3_session') 431 | @patch('aws_sat_api.aws.list_directory') 432 | def test_cbers_awfi_valid(list_directory, session): 433 | """Should work as expected 434 | """ 435 | 436 | session.return_value.client.return_value = True 437 | 438 | list_directory.return_value = [ 439 | 'CBERS4/AWFI/123/093/CBERS_4_AWFI_20170411_123_093_L4/'] 440 | 441 | path = '123' 442 | row = '93' 443 | sensor = 'AWFI' 444 | 445 | expected = [{ 446 | 'acquisition_date': '20170411', 447 | 'key': 'CBERS4/AWFI/123/093/CBERS_4_AWFI_20170411_123_093_L4', 448 | 'path': '123', 449 | 'processing_level': 'L4', 450 | 'row': '093', 451 | 'satellite': 'CBERS', 452 | 'scene_id': 'CBERS_4_AWFI_20170411_123_093_L4', 453 | 'sensor': 'AWFI', 454 | 'thumbURL': 455 | 'https://s3.amazonaws.com/cbers-meta-pds/CBERS4/AWFI/123/093/CBERS_4_AWFI_20170411_123_093_L4/CBERS_4_AWFI_20170411_123_093_small.jpeg', 456 | 'browseURL': 457 | 'https://s3.amazonaws.com/cbers-meta-pds/CBERS4/AWFI/123/093/CBERS_4_AWFI_20170411_123_093_L4/CBERS_4_AWFI_20170411_123_093.jpg', 458 | 'version': '4'}] 459 | 460 | assert list(search.cbers(path, row, sensor)) == expected 461 | 462 | 463 | @patch('aws_sat_api.aws.list_directory') 464 | def test_s2_date_filter(list_directory): 465 | start_date = datetime(2017, 1, 1) 466 | end_date = datetime(2017, 5, 15) 467 | 468 | path = os.path.join(os.path.dirname(__file__), f'fixtures/s2_search_2017.json') 469 | with open(path, 'r') as f: 470 | fixt = json.loads(f.read()) 471 | 472 | list_directory.side_effect = [ 473 | fixt["months"], 474 | *fixt["days"], 475 | *fixt["versions"]] 476 | 477 | results_date_filter = list(search.sentinel2(22, "K", "HV", start_date=start_date, end_date=end_date)) 478 | assert len(results_date_filter) == 22 479 | assert results_date_filter == fixt["results"] 480 | 481 | 482 | @patch('aws_sat_api.aws.list_directory') 483 | def test_s2_date_filter_single_day(list_directory): 484 | start_date = datetime(2017, 1, 12) 485 | end_date = datetime(2017, 1, 12) 486 | 487 | path = os.path.join(os.path.dirname(__file__), f'fixtures/s2_search_2017.json') 488 | with open(path, 'r') as f: 489 | fixt = json.loads(f.read()) 490 | 491 | list_directory.side_effect = [ 492 | fixt["months"], 493 | *fixt["days"], 494 | *fixt["versions"]] 495 | 496 | results_date_filter = list(search.sentinel2(22, "K", "HV", start_date=start_date, end_date=end_date)) 497 | assert len(results_date_filter) == 1 498 | assert results_date_filter[0] == fixt["results"][0] 499 | 500 | 501 | def test_s2_date_exceptions(): 502 | """Tests if the expected exceptions are properly raised.""" 503 | with pytest.raises(ValueError, match="Start date out of range"): 504 | search.sentinel2(22, "K", "HV", start_date=datetime(2014, 1, 1)) 505 | 506 | with pytest.raises(ValueError, match="Invalid date range"): 507 | search.sentinel2(22, "K", "HV", start_date=datetime(2017, 5, 1), end_date=datetime(2017, 1, 15)) 508 | --------------------------------------------------------------------------------