├── Procfile ├── runtime.txt ├── .gitignore ├── uwsgi.ini ├── requirements.txt ├── main.py ├── code_editor.js ├── README.md └── checker.py /Procfile: -------------------------------------------------------------------------------- 1 | web: uwsgi uwsgi.ini -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.8.12 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .idea 3 | /credential 4 | /__pycache__ -------------------------------------------------------------------------------- /uwsgi.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | http-socket = :$(PORT) 3 | master = true 4 | die-on-term = true 5 | module = app:app 6 | memory-report = true -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2021.10.8 2 | charset-normalizer==2.0.7 3 | click==8.0.3 4 | colorama==0.4.4 5 | Flask==2.0.2 6 | geojson==2.5.0 7 | geomet==0.3.0 8 | html2text==2020.1.16 9 | idna==3.3 10 | itsdangerous==2.0.1 11 | Jinja2==3.0.2 12 | MarkupSafe==2.0.1 13 | python-dotenv==0.19.1 14 | requests==2.26.0 15 | sentinelsat==1.1.0 16 | six==1.16.0 17 | tqdm==4.62.3 18 | urllib3==1.26.7 19 | Werkzeug==2.0.2 20 | wincertstore==0.2 21 | uwsgi 22 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request 2 | from checker import Checker, get_api 3 | import os 4 | from dotenv import load_dotenv 5 | 6 | load_dotenv() # Load environment variable from .env 7 | 8 | app = Flask( 9 | __name__, 10 | static_folder='.', 11 | root_path='/home/runner' 12 | ) 13 | 14 | 15 | @app.route('/') 16 | def root(): 17 | return "App made by Rodrigo E. Principe (fitoprincipe82@gmail.com)" 18 | 19 | 20 | @app.route('/error') 21 | def error(): 22 | return "An error occurred, sorry =)" 23 | 24 | 25 | @app.route('/error2') 26 | def error2(): 27 | error = request.args.get('error') 28 | return "Ocurrió el siguiente error \n\n {}".format(error) 29 | 30 | 31 | @app.route('/check') 32 | def check(): 33 | coords = request.args.get('coords') 34 | level = request.args.get('level') 35 | ingee = request.args.get('ingee') 36 | start = request.args.get('start') 37 | end = request.args.get('end') 38 | 39 | user = os.environ['HUB_USER'] 40 | password = os.environ['HUB_PASS'] 41 | api = get_api(user, password) 42 | 43 | ch = Checker(coords, start, end, level, ingee, api) 44 | 45 | html = ch.create_html() 46 | 47 | return html 48 | 49 | if __name__ == '__main__': 50 | app.run(host='0.0.0.0', port='3000', debug=True) -------------------------------------------------------------------------------- /code_editor.js: -------------------------------------------------------------------------------- 1 | // Draw a geometry 2 | var endpoint = 'http://127.0.0.1:5000/check' 3 | var start = '2021-09-20' 4 | var end = '2021-09-21' 5 | 6 | var format_footprint = function(geom) { 7 | var newcoords = [] 8 | var coords = geom.bounds().coordinates().getInfo()[0] 9 | for (var i in coords) { 10 | var coord = coords[i] 11 | var lon = coord[0] 12 | var lat = coord[1] 13 | newcoords.push(lon) 14 | newcoords.push(lat) 15 | } 16 | return newcoords.join(' ') 17 | } 18 | 19 | var check_S2_available = function(col, roi, start, end, level) { 20 | var start = ee.Date(start) 21 | var end = ee.Date(end) 22 | var coords = format_footprint(roi) 23 | var start = start.format('yMMdd').getInfo() 24 | var end = end.format('yMMdd').getInfo() 25 | var ingee = col.aggregate_array('PRODUCT_ID').join(' ').getInfo() 26 | 27 | var params = "?coords="+coords+"&start="+start+"&end="+end+"&ingee="+ingee+"&level="+level 28 | var url = endpoint+params 29 | return url 30 | } 31 | 32 | var check_S2TOA_available = function(roi, start, end) { 33 | start = ee.Date(start) 34 | end = ee.Date(end).advance(1, 'day') 35 | roi = roi.bounds() 36 | Map.addLayer(roi) 37 | var col = ee.ImageCollection('COPERNICUS/S2') 38 | .filterBounds(roi) 39 | .filterDate(start, end) 40 | return check_S2_available(col, roi, start, end, 'toa') 41 | } 42 | 43 | var url = check_S2TOA_available(geometry, start, end) 44 | print(ui.Label('CHECK', null, url)) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GEE Sentinel Checker 2 | 3 | Check if Google Earth Engine (GEE) is missing a Sentinel image by 4 | calling the Copernicus Open Hub API. 5 | 6 | This is a Flask (python) application that you can deploy or run locally. 7 | 8 | The query parameters are: 9 | - **coords**: the coordinates of the polygon to check. Must be the coordinates of a simple rectangle. Must be a string as follows: "lat lon lat lon..." 10 | - **start**: the start date (inclusive). Format: yyyyMMdd 11 | - **end**: the end date (exclusive). Format: yyyyMMdd 12 | - **ingee**: the ids list of the images available on GEE taken from "PRODUCT_ID" property of the images. Must be a string as follows: "id1 id2 id3..." 13 | - **level**: the processing level of the images (options: 'toa', 'sr') 14 | 15 | The file `code_editor.js` contains an example on how to use this web app. 16 | 17 | ## Usage 18 | ### Alternative 1 (without python) 19 | Avoid the python part and use the already running endpoint. It is running 20 | on Heroku so it has its limitations. 21 | 22 | #### In the code editor 23 | ``` javascript 24 | var checker = require('users/fitoprincipe/s2checker:main') 25 | 26 | // Local endpoint. Uncomment this line if the server is running locally 27 | //var endpoint = 'http://127.0.0.1:5000/check'; // local 28 | //var endpoint2 = 'https://checkSentinelGee.rodrigoprincipe.repl.co/check'; 29 | var endpoint = null; 30 | 31 | // DRAW A GEOMETRY to get `geometry` variable 32 | var level = 'toa' // or 'sr' 33 | var start = '2021-09-15' 34 | var end = '2021-09-21' 35 | var check = new checker.Checker(geometry, level, start, end, endpoint); 36 | 37 | print(ui.Label('CHECK', null, check.url())) 38 | ``` 39 | https://code.earthengine.google.com/4a5472d6d80990ce58afa385cf5f9adc 40 | 41 | If you hit an error that says: 42 | 43 | > **Application error**: An error occurred in the application 44 | and your page could not be served. If you are the application 45 | owner, check your logs for details. You can do this from the 46 | Heroku CLI with the command 47 | 48 | is because you are asking too much, to solve it decrease the area 49 | of the geometry or the time window. 50 | 51 | ### Alternative 2 (with python) 52 | If you run the server locally you can fetch more data, so you won't 53 | hit the _Application error_. Also, you can modify the code to your needs 54 | and even contribute to this project to make it better for everyone. 55 | 56 | Steps: 57 | 1. Clone this repository 58 | 2. Create a virtual environment (for example: `s2checker`) 59 | 3. Activate the environment 60 | 4. Navigate to the repository folder 61 | 5. Install dependencies: 62 | >pip install -r requirements.txt 63 | 6. Create a new file in the repo folder called `.env` (text file) 64 | 7. Add the following to that file: 65 | > FLASK_ENV=development 66 | > HUB_USER=your_copernicus_hub_user 67 | > HUB_PASS=your_copernicus_hub_password 68 | 8. Run the server: 69 | >flask run 70 | 9. The server is running, now you can uncomment the `endpoint` line 71 | in the [code editor code](#in-the-code-editor) -------------------------------------------------------------------------------- /checker.py: -------------------------------------------------------------------------------- 1 | from sentinelsat import SentinelAPI, geojson_to_wkt 2 | from dotenv import load_dotenv 3 | import os 4 | from requests.auth import HTTPBasicAuth 5 | import requests 6 | import base64 7 | 8 | load_dotenv() # Load environment variable from .env 9 | 10 | coords = '-68.45317820662159 -26.35042608088762 -62.36675242537159 -26.35042608088762 -62.36675242537159 -22.002031348439377 -68.45317820662159 -22.002031348439377 -68.45317820662159 -26.35042608088762' 11 | start = '20210920' 12 | end = '20210922' 13 | ingee = 'S2B_MSIL1C_20210920T143729_N0301_R096_T19JEL_20210920T180332 S2B_MSIL1C_20210920T143729_N0301_R096_T19JEM_20210920T180332 S2B_MSIL1C_20210920T143729_N0301_R096_T19JEN_20210920T180332 S2B_MSIL1C_20210920T143729_N0301_R096_T19JFL_20210920T180332 S2B_MSIL1C_20210920T143729_N0301_R096_T19JFM_20210920T180332 S2B_MSIL1C_20210920T143729_N0301_R096_T19JFN_20210920T180332 S2B_MSIL1C_20210920T143729_N0301_R096_T19KEP_20210920T180332 S2B_MSIL1C_20210920T143729_N0301_R096_T19KEQ_20210920T180332 S2B_MSIL1C_20210920T143729_N0301_R096_T19KER_20210920T180332 S2B_MSIL1C_20210920T143729_N0301_R096_T19KFP_20210920T180332 S2B_MSIL1C_20210920T143729_N0301_R096_T19KFQ_20210920T180332 S2B_MSIL1C_20210920T143729_N0301_R096_T19KFR_20210920T180332 S2B_MSIL1C_20210920T143729_N0301_R096_T19KGQ_20210920T180332 S2B_MSIL1C_20210920T143729_N0301_R096_T19KGR_20210920T180332 S2B_MSIL1C_20210921T141049_N0301_R110_T20JMS_20210921T172924 S2B_MSIL1C_20210921T141049_N0301_R110_T20KNU_20210921T172924' 14 | level = 'toa' 15 | 16 | 17 | MATCH = { 18 | 'identifier': 'PRODUCT_ID', 19 | } 20 | 21 | 22 | def get_api(user, password): 23 | return SentinelAPI(user, password, 'https://apihub.copernicus.eu/apihub') 24 | 25 | 26 | def _date_gee(date): 27 | y = date[0:4] 28 | m = date[4:6] 29 | d = date[6:8] 30 | return '{}-{}-{}'.format(y, m, d) 31 | 32 | 33 | class Checker: 34 | TEMPLATE = """ 35 |
| ESA ID | 38 |Is available in GEE? | 39 |Is online in S2 Hub? | 40 |Code Editor | 41 |Preview | 42 |
|---|