[
  {
    "path": ".gitignore",
    "content": ".DS_Store\npackage.zip\n.serverless/\nnode_modules/\n\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nenv/\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*,cover\n.hypothesis/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n#Ipython Notebook\n.ipynb_checkpoints\n\n.*.swp\n\n.python-version\n"
  },
  {
    "path": "Dockerfile",
    "content": "# Use the official amazonlinux AMI image\nFROM amazonlinux:latest\n\n# Install apt dependencies\nRUN yum install -y \\\n  gcc gcc-c++ freetype-devel yum-utils findutils openssl-devel\n\nRUN yum -y groupinstall development\n\nRUN curl https://www.python.org/ftp/python/3.6.1/Python-3.6.1.tar.xz | tar -xJ \\\n    && cd Python-3.6.1 \\\n    && ./configure --prefix=/usr/local --enable-shared \\\n    && make \\\n    && make install \\\n    && cd .. \\\n    && rm -rf Python-3.6.1\n\nENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH\n\n# Install Python dependencies\nRUN pip3 install rio-tiler==1.0b2 lambda-proxy==0.0.4 aws-sat-api==1.0.0 --no-binary numpy -t /tmp/vendored -U\n\n# Reduce Lambda package size to fit the 250Mb limit\n# Mostly based on https://github.com/jamesandersen/aws-machine-learning-demo\nRUN du -sh /tmp/vendored\n\n# This is the list of available modules on AWS lambda Python 3\n# ['boto3', 'botocore', 'docutils', 'jmespath', 'pip', 'python-dateutil', 's3transfer', 'setuptools', 'six']\nRUN find /tmp/vendored -name \"*-info\" -type d -exec rm -rdf {} +\nRUN rm -rdf /tmp/vendored/boto3/\nRUN rm -rdf /tmp/vendored/botocore/\nRUN rm -rdf /tmp/vendored/docutils/\nRUN rm -rdf /tmp/vendored/dateutil/\nRUN rm -rdf /tmp/vendored/jmespath/\nRUN rm -rdf /tmp/vendored/s3transfer/\nRUN rm -rdf /tmp/vendored/numpy/doc/\n\n# Leave module precompiles for faster Lambda startup\nRUN find /tmp/vendored -type f -name '*.pyc' | while read f; do n=$(echo $f | sed 's/__pycache__\\///' | sed 's/.cpython-36//'); cp $f $n; done;\nRUN find /tmp/vendored -type d -a -name '__pycache__' -print0 | xargs -0 rm -rf\nRUN find /tmp/vendored -type f -a -name '*.py' -print0 | xargs -0 rm -f\n\nRUN du -sh /tmp/vendored\n\nCOPY app /tmp/vendored/app\n\n# Create archive\nRUN cd /tmp/vendored && zip -r9q /tmp/package.zip *\n\n# Cleanup\nRUN rm -rf /tmp/vendored/\n"
  },
  {
    "path": "LICENSE",
    "content": "BSD 3-Clause License\n\nCopyright (c) 2017, Mapbox\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n  list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\n* Neither the name of the copyright holder nor the names of its\n  contributors may be used to endorse or promote products derived from\n  this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "Makefile",
    "content": "\nSHELL = /bin/bash\n\nall: build package\n\nbuild:\n\tdocker build --tag lambda:latest .\n\n#Local Test\ntest:\n\tdocker run \\\n\t\t-w /var/task/ \\\n\t\t--name lambda \\\n\t\t--env AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \\\n\t\t--env AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \\\n \t\t--env AWS_REGION=us-west-2 \\\n\t\t--env PYTHONPATH=/var/task \\\n\t\t--env GDAL_CACHEMAX=75% \\\n\t\t--env GDAL_DISABLE_READDIR_ON_OPEN=TRUE \\\n\t\t--env GDAL_TIFF_OVR_BLOCKSIZE=512 \\\n\t\t--env VSI_CACHE=TRUE \\\n\t\t--env VSI_CACHE_SIZE=536870912 \\\n\t\t-itd \\\n\t\tlambda:latest\n\tdocker cp package.zip lambda:/tmp/package.zip\n\tdocker exec -it lambda bash -c 'unzip -q /tmp/package.zip -d /var/task/'\n\tdocker exec -it lambda bash -c 'pip3 install boto3 jmespath python-dateutil -t /var/task'\n\tdocker exec -it lambda python3 -c 'from app.landsat import APP; print(APP({\"path\": \"/landsat/bounds/LC80230312016320LGN00\", \"queryStringParameters\": \"null\", \"pathParameters\": \"null\", \"requestContext\": \"null\", \"httpMethod\": \"GET\"}, None))'\n\tdocker exec -it lambda python3 -c 'from app.landsat import APP; print(APP({\"path\": \"/landsat/metadata/LC80230312016320LGN00\", \"queryStringParameters\": {\"pmin\":\"2\", \"pmax\":\"99.8\"}, \"pathParameters\": \"null\", \"requestContext\": \"null\", \"httpMethod\": \"GET\"}, None))'\n\tdocker exec -it lambda python3 -c 'from app.landsat import APP; print(APP({\"path\": \"/landsat/processing/LC80230312016320LGN00/8/65/94.png\", \"queryStringParameters\": {\"ratio\":\"(b5-b4)/(b5+b4)\"}, \"pathParameters\": \"null\", \"requestContext\": \"null\", \"httpMethod\": \"GET\"}, None))'\n\tdocker exec -it lambda python3 -c 'from app.landsat import APP; print(APP({\"path\": \"/landsat/tiles/LC80230312016320LGN00/8/65/94.png\", \"queryStringParameters\": {\"rgb\":\"11\", \"histo\":\"0,1000\"}, \"pathParameters\": \"null\", \"requestContext\": \"null\", \"httpMethod\": \"GET\"}, None))'\n\tdocker exec -it lambda python3 -c 'from app.landsat import APP; print(APP({\"path\": \"/landsat/tiles/LC80230312016320LGN00/8/65/94.png\", \"queryStringParameters\": {\"rgb\":\"5,3,2\", \"histo\":\"722,5088;859,4861;1164,5204\"}, \"pathParameters\": \"null\", \"requestContext\": \"null\", \"httpMethod\": \"GET\"}, None))'\n\tdocker exec -it lambda python3 -c 'from app.landsat import APP; print(APP({\"path\": \"/landsat/tiles/LC80230312016320LGN00/8/65/94.png\", \"queryStringParameters\": {\"rgb\":\"4,3,2\", \"histo\":\"722,5088;859,4861;1164,5204\", \"pan\":\"true\"}, \"pathParameters\": \"null\", \"requestContext\": \"null\", \"httpMethod\": \"GET\"}, None))'\n\tdocker stop lambda\n\tdocker rm lambda\n\n\npackage:\n\tdocker run \\\n\t\t-w /var/task/ \\\n\t\t--name lambda \\\n\t\t-itd \\\n\t\tlambda:latest\n\tdocker cp lambda:/tmp/package.zip package.zip\n\tdocker stop lambda\n\tdocker rm lambda\n\nshell:\n\tdocker run \\\n\t\t--name lambda  \\\n\t\t--volume $(shell pwd)/:/data \\\n\t\t--env PYTHONPATH=/var/task/vendored \\\n\t\t--env GDAL_CACHEMAX=75% \\\n\t\t--env GDAL_DISABLE_READDIR_ON_OPEN=TRUE \\\n\t\t--env GDAL_TIFF_OVR_BLOCKSIZE=512 \\\n\t\t--env VSI_CACHE=TRUE \\\n\t\t--env VSI_CACHE_SIZE=536870912 \\\n\t\t--rm \\\n\t\t-it \\\n\t\tlambda:latest /bin/bash\n\ndeploy:\n\tsls deploy\n\nclean:\n\tdocker stop lambda\n\tdocker rm lambda\n"
  },
  {
    "path": "README.md",
    "content": "# landsat-tiler\n\n#### AWS Lambda + Landsat AWS PDS = landsat-tiler\n\n### Description\n\nCreate a highly customizable `serverless` tile server for Amazon's Landsat Public Dataset.\nThis project is based on [rio-tiler](https://github.com/mapbox/rio-tiler) python library.\n\n![landsat-tiler-small](https://cloud.githubusercontent.com/assets/10407788/22255896/ec49f448-e226-11e6-8798-82794174eafe.gif)\n\n\n#### Landsat data on AWS\n\nSince 2015 Landsat 8 data is hosted on AWS and can be freely accessed. This dataset is growing over 700 scenes a day and have archive up to 2013.\n\n> AWS has made Landsat 8 data freely available on Amazon S3 so that anyone can use our on-demand computing resources to perform analysis and create new products without needing to worry about the cost of storing Landsat data or the time required to download it.\n\nmore info: https://aws.amazon.com/public-datasets/landsat/\n\nSomething important about AWS Landsat-pds is that each Landsat scene has its individual bands stored as [cloud optimized GeoTIFF](https://trac.osgeo.org/gdal/wiki/CloudOptimizedGeoTIFF). While this is a critical point to work with the data, it also means that to create an RGB image and visualize it, you have to go through a lot of manual steps.\n\n#### Lambda function\n\nAWS Lambda is a service that lets you run functions in Node, Python, or Java in response to different triggers like API calls, file creation, database edits, etc.\nIn addition to only have to provide code, an other crucial point of AWS Lambda it that you only pay for the execution of the function, you don't have to pay for a 24/24h running server. It's called **serverless** cause you only need to care about the code you provide.\n\n---\n\n# Installation\n\n##### Requirement\n  - AWS Account\n  - Docker\n  - node + npm\n\n\n#### Create the package\n\nCreating a python lambda package with some C (or Cython) libraries like Rasterio/GDAL has never been an easy task because you have to compile and build it on the same infrastructure where it's going to be used (Amazon linux AMI). Until recently, to create your package you had to launch an EC2 instance using the official Amazon Linux AMI and create your package on it (see [perrygeo blog](http://www.perrygeo.com/running-python-with-compiled-code-on-aws-lambda.html) or [Remotepixel blog](https://remotepixel.ca/blog/landsat8-ndvi-20160212.html)).\n\nBut this was before, Late 2016, the AWS team released the Amazon Linux image on docker, so it's now possible to use it `locally` to compile C libraries and create complex lambda package ([see Dockerfile](https://github.com/mapbox/landsat-tiler/blob/master/Dockerfile)).\n\nNote: to stay under AWS lambda package sizes limits (100Mb zipped file / 250Mb unzipped archive) we need to use some [`tricks`](https://github.com/mapbox/landsat-tiler/blob/e4eebb512f51c55d95607daa483a14d2091fa0a1/Dockerfile#L30).\n- use Rasterio wheels which is a complete rasterio distribution that support GeoTIFF, OpenJPEG formats.\n- remove every packages that are already available natively in AWS Lambda (boto3, botocore ...)\n- keep only precompiled python code (`.pyc`) so it lighter and it loads faster\n\n```bash\n# Build Amazon linux AMI docker container + Install Python modules + create package\ngit clone https://github.com/mapbox/landsat-tiler.git\ncd landsat-tiler/\nmake all\n```\n\n#### Deploy to AWS\nOne of the easiest way to **Build** and **Deploy** a Lambda function is to use [Serverless](https://serverless.com) toolkit. We took care of the `building` part with docker so we will just ask **Serverless** to *only* upload our package file to AWS S3, to setup AWS Lambda and AWS API Gateway.\n\n```bash\n#configure serverless (https://serverless.com/framework/docs/providers/aws/guide/credentials/)\nnpm install\nsls deploy\n```\n\n<img width=\"500\" alt=\"sls deploy\" src=\"https://cloud.githubusercontent.com/assets/10407788/22188728/d9ffec44-e0e5-11e6-9a77-569a791ccaf2.png\">\n\n:tada: You should be all set there.\n\n---\n# Use it: Landsat-viewer\n\n#### lambda-tiler + Mapbox GL + Satellite API\n\nThe `viewer/` directory contains a UI example to use with your new Lambda Landsat tiler endpoint. It combine the power of mapbox-gl and the nice developmentseed [sat-api](https://github.com/sat-utils/sat-api) to create a simple and fast **Landsat-viewer**.\n\nTo be able to run it, edit those [two lines](https://github.com/mapbox/landsat-tiler/blob/master/viewer/js/app.js#L3-L4) in `viewer/js/app.js`\n```js\n// viewer/js/app.js\n3  mapboxgl.accessToken = '{YOUR-MAPBOX-TOKEN}';\n4  const landsat_tiler_url = \"{YOUR-API-GATEWAY-URL}\";\n```\n\n## Workflow\n\n1. One AWS λ call to get min/max percent cut value for all the bands and bounds\n\n  *Path:* **/landsat/metdata/{landsat scene id}**\n\n  *Inputs:*\n\n  - sceneid: Landsat product id (or scene id for scene < 1st May 2017)\n\n  *Options:*\n\n  - pmin: Histogram cut minimum value in percent (default: 2)  \n  - pmax: Histogram cut maximum value in percent (default: 98)  \n\n  *Output:* (dict)\n\n  - bounds: (minX, minY, maxX, maxY) (list)\n  - sceneid: scene id (string)\n  - rgbMinMax: Min/Max DN values for the linear rescaling (dict)\n\n  *Example:* `<api-gateway-url>/landsat/metadata/LC08_L1TP_016037_20170813_20170814_01_RT?pmin=5&pmax=95`\n\n2. Parallel AWS λ calls (one per mercator tile) to retrieve corresponding Landsat data\n\n  *Path:* **/landsat/tiles/{landsat scene id}/{z}/{x}/{y}.{ext}**\n\n  *Inputs:*\n\n  - sceneid: Landsat product id (or scene id for scene < 1st May 2017)\n  - x: Mercator tile X index\n  - y: Mercator tile Y index\n  - z: Mercator tile ZOOM level\n  - ext: Image format to return (\"jpg\" or \"png\")\n\n  *Options:*\n\n  - rgb: Bands index for the RGB combination (default: (4, 3, 2))\n  - histo: DN min and max values (default: (0, 16000))\n  - tile: Output image size (default: 256)\n  - pan: If True, apply pan-sharpening(default: False)\n\n  *Output:*\n\n  - base64 encoded image PNG or JPEG (string)\n\n  *Example:*\n  - `<api-gateway-url>/landsat/tile/LC08_L1TP_016037_20170813_20170814_01_RT/8/71/102.png`\n  - `<api-gateway-url>/landsat/tile/LC08_L1TP_016037_20170813_20170814_01_RT/8/71/102.png?rgb=5,4,3&histo=100,3000-130,270-500,4500&tile=1024&pan=true`\n\n\n---\n#### Live Demo: https://viewer.remotepixel.ca\n\n#### Infos & links\n- [rio-tiler](https://github.com/mapbox/rio-tiler) rasterio plugin that process Landsat data hosted on AWS S3.\n- [Introducing the AWS Lambda Tiler](https://hi.stamen.com/stamen-aws-lambda-tiler-blog-post-76fc1138a145)\n- Humanitarian OpenStreetMap Team [oam-dynamic-tiler](https://github.com/hotosm/oam-dynamic-tiler)\n- [Linux Amazon AMI container](http://docs.aws.amazon.com/AmazonECR/latest/userguide/amazon_linux_container_image.html)\n"
  },
  {
    "path": "app/__init__.py",
    "content": "# app\n\n__version__ = '2.0.0'\n"
  },
  {
    "path": "app/landsat.py",
    "content": "\"\"\"app.landsat: handle request for Landsat-tiler\"\"\"\n\nimport re\nimport json\n\nimport numpy as np\n\nfrom rio_tiler import landsat8\nfrom rio_tiler.utils import array_to_img, linear_rescale, get_colormap, expression, b64_encode_img\n\nfrom aws_sat_api.search import landsat as landsat_search\n\nfrom lambda_proxy.proxy import API\n\nAPP = API(app_name=\"landsat-tiler\")\n\n\nclass LandsatTilerError(Exception):\n    \"\"\"Base exception class\"\"\"\n\n\n@APP.route('/landsat/search', methods=['GET'], cors=True)\ndef search():\n    \"\"\"Handle search requests\n    \"\"\"\n    query_args = APP.current_request.query_params\n    query_args = query_args if isinstance(query_args, dict) else {}\n\n    path = query_args['path']\n    row = query_args['row']\n    full = query_args.get('full', True)\n\n    data = list(landsat_search(path, row, full))\n    info = {\n        'request': {'path': path, 'row': row, 'full': full},\n        'meta': {'found': len(data)},\n        'results': data}\n\n    return ('OK', 'application/json', json.dumps(info))\n\n\n@APP.route('/landsat/bounds/<scene>', methods=['GET'], cors=True)\ndef bounds(scene):\n    \"\"\"Handle bounds requests\n    \"\"\"\n    info = landsat8.bounds(scene)\n    return ('OK', 'application/json', json.dumps(info))\n\n\n@APP.route('/landsat/metadata/<scene>', methods=['GET'], cors=True)\ndef metadata(scene):\n    \"\"\"Handle metadata requests\n    \"\"\"\n    query_args = APP.current_request.query_params\n    query_args = query_args if isinstance(query_args, dict) else {}\n\n    pmin = query_args.get('pmin', 2)\n    pmin = float(pmin) if isinstance(pmin, str) else pmin\n\n    pmax = query_args.get('pmax', 98)\n    pmax = float(pmax) if isinstance(pmax, str) else pmax\n\n    info = landsat8.metadata(scene, pmin, pmax)\n    return ('OK', 'application/json', json.dumps(info))\n\n\n@APP.route('/landsat/tiles/<scene>/<int:z>/<int:x>/<int:y>.<ext>', methods=['GET'], cors=True)\ndef tile(scene, tile_z, tile_x, tile_y, tileformat):\n    \"\"\"Handle tile requests\n    \"\"\"\n    if tileformat == 'jpg':\n        tileformat = 'jpeg'\n\n    query_args = APP.current_request.query_params\n    query_args = query_args if isinstance(query_args, dict) else {}\n\n    bands = query_args.get('rgb', '4,3,2')\n    bands = tuple(re.findall(r'\\d+', bands))\n\n    histoCut = query_args.get('histo', ';'.join(['0,16000'] * len(bands)))\n    histoCut = re.findall(r'\\d+,\\d+', histoCut)\n    histoCut = list(map(lambda x: list(map(int, x.split(','))), histoCut))\n\n    if len(bands) != len(histoCut):\n        raise LandsatTilerError('The number of bands doesn\\'t match the number of histogramm values')\n\n    tilesize = query_args.get('tile', 256)\n    tilesize = int(tilesize) if isinstance(tilesize, str) else tilesize\n\n    pan = True if query_args.get('pan') else False\n    tile, mask = landsat8.tile(scene, tile_x, tile_y, tile_z, bands, pan=pan, tilesize=tilesize)\n\n    rtile = np.zeros((len(bands), tilesize, tilesize), dtype=np.uint8)\n    for bdx in range(len(bands)):\n        rtile[bdx] = np.where(mask, linear_rescale(tile[bdx], in_range=histoCut[bdx], out_range=[0, 255]), 0)\n    img = array_to_img(rtile, mask=mask)\n    str_img = b64_encode_img(img, tileformat)\n    return ('OK', f'image/{tileformat}', str_img)\n\n\n@APP.route('/landsat/processing/<scene>/<int:z>/<int:x>/<int:y>.<ext>', methods=['GET'], cors=True)\ndef ratio(scene, tile_z, tile_x, tile_y, tileformat):\n    \"\"\"Handle processing requests\n    \"\"\"\n    if tileformat == 'jpg':\n        tileformat = 'jpeg'\n\n    query_args = APP.current_request.query_params\n    query_args = query_args if isinstance(query_args, dict) else {}\n\n    ratio_value = query_args['ratio']\n    APP.log.debug(f'{ratio_value}')\n\n    range_value = query_args.get('range', [-1, 1])\n\n    tilesize = query_args.get('tile', 256)\n    tilesize = int(tilesize) if isinstance(tilesize, str) else tilesize\n\n    tile, mask = expression(scene, tile_x, tile_y, tile_z, ratio_value, tilesize=tilesize)\n    if len(tile.shape) == 2:\n        tile = np.expand_dims(tile, axis=0)\n\n    rtile = np.where(mask, linear_rescale(tile, in_range=range_value, out_range=[0, 255]), 0).astype(np.uint8)\n    img = array_to_img(rtile, color_map=get_colormap(name='cfastie'), mask=mask)\n    str_img = b64_encode_img(img, tileformat)\n    return ('OK', f'image/{tileformat}', str_img)\n\n\n@APP.route('/favicon.ico', methods=['GET'], cors=True)\ndef favicon():\n    \"\"\"favicon\n    \"\"\"\n    return('NOK', 'text/plain', '')\n"
  },
  {
    "path": "package.json",
    "content": "{\n    \"description\": \"Create a highly customizable `serverless` tile server for Amazon's Landsat Public Dataset\",\n    \"devDependencies\": {\n        \"serverless-apigw-binary\": \"^0.4.1\",\n        \"serverless\": \"^1.23.0\"\n    },\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"git://github.com/mapbox/landsat-tiler.git\"\n    },\n    \"author\": \"Vincent Sarago\"\n}\n"
  },
  {
    "path": "serverless.yml",
    "content": "service: landsat-tiler\n\nprovider:\n  name: aws\n  runtime: python3.6\n  stage: production\n\n  region: us-west-2\n\n  iamRoleStatements:\n  -  Effect: \"Allow\"\n     Action:\n       - \"s3:GetObject\"\n     Resource:\n       - \"arn:aws:s3:::landsat-pds/*\"\n\n  environment:\n    GDAL_CACHEMAX: 75%\n    GDAL_TIFF_OVR_BLOCKSIZE: 512\n    VSI_CACHE: TRUE\n    VSI_CACHE_SIZE: 536870912\n    GDAL_DISABLE_READDIR_ON_OPEN: true\n    CPL_VSIL_CURL_ALLOWED_EXTENSIONS: \".TIF,.ovr\"\n\n  #Optional Bucket where you store your lambda package\n  # deploymentBucket: {YOUR-BUCKET}\n\ncustom:\n  apigwBinary:\n    types:\n      - '*/*'\n\nplugins:\n  - serverless-apigw-binary\n\npackage:\n  artifact: package.zip\n\nfunctions:\n  landsat-tiler:\n    handler: app.landsat.APP\n    memorySize: 1536\n    timeout: 20\n    events:\n      - http:\n          path: landsat/{proxy+}\n          method: get\n          cors: true\n"
  },
  {
    "path": "viewer/css/style.css",
    "content": "body {\n  position: fixed;\n\twidth: 100%;\n\theight: 100%;\n\tcolor: #000;\n\tbackground-color: #FFF;\n  letter-spacing: 0;\n}\n\n.content {\n    height:100%;\n    width: 100%;\n    font-size: 0;\n}\n\n.row, .col {\n    padding: 0;\n    margin: 0;\n}\n\n.center {\n    padding: 20px;\n    margin: auto;\n}\n\n.main-container {\n    height:100%;\n    width: 100%;\n    font-size: 0;\n    padding: 0;\n    margin: 0;\n    display: inline-block;\n}\n\n.map {\n    display: inline-block;\n    height: 100%;\n    width: 100%;\n    padding: 0;\n    margin: 0;\n    -webkit-touch-callout: none;\n      -webkit-user-select: none;\n         -moz-user-select: none;\n          -ms-user-select: none;\n              user-select: none;\n}\n\n.right-panel {\n    position: relative;\n    display: none;\n    vertical-align: top;\n    width: 350px;\n    z-index: 1;\n    color: #FFF;\n    background-color: rgba(111, 111, 112, 0.53);\n    height: 100%;\n    margin: 0;\n    padding: 0;\n}\n\n.right-panel.in {\n  float: right;\n  display: inherit;\n}\n\n.right-panel.in ~ .map {\n    float: left;\n    width: calc(100% - 350px);\n    margin: 0;\n    padding: 0;\n    vertical-align: top;\n}\n\n.right-panel .close-button {\n  display: none;\n  position: absolute;\n  top: 0;\n  left: -36px;\n  width: 36px;\n  height: 36px;\n  line-height: 36px;\n  text-align: center;\n  vertical-align: middle;\n  z-index: 1000;\n  color: #000;\n  background-color: rgba(255, 255, 255, 0.5);\n}\n\n.right-panel .close-button:hover {\n  color: #FFF;\n  background-color: rgba(0, 0, 0, 0.5);\n  cursor: pointer;\n}\n\n.right-panel.in .close-button {\n  display: block;\n}\n\n.right-panel-content {\n  position: relative;\n  display: flex;\n  flex-flow: column;\n  height: 100%;\n}\n\n.right-panel-content .list-img {\n    flex: 0 1 auto;\n    -webkit-flex: 0 1 auto;\n    overflow-y: auto;\n    height: 400px;\n    z-index: 2;\n    overflow-y: scroll;\n}\n\n.right-panel-content .img-display-options {\n    flex: 0 1 auto;\n    padding: 15px 10px;\n    overflow-y: scroll;\n}\n\n.switch-container {\n  display: block;\n  font-size: 18px;\n  line-height: 18px;\n  padding: 4px;\n}\n\n.switch-container * {\n  display: inline-block;\n}\n\n.img-display-options .toggle-group {\n  display: block;\n}\n\n.img-display-options .toggle-group * {\n  display: inline-block;\n}\n\n.img-display-options .toggle {\n  font-size: 10px;\n  color: #000;\n}\n\n.img-display-options span {\n  font-size: 15px;\n  color: #000;\n  display: block;\n}\n\n.img-display-options .inputHisto * {\n  display: inline-block;\n  font-size: 12px;\n  color: #000;\n}\n\n.list-img .list-element {\n  position: relative;\n  color: #FFF;\n  background-color: #404040;\n  cursor: pointer;\n}\n\n.list-img .list-element:hover {\n  -o-box-shadow: inset 0 0 10px #000;\n  -webkit-box-shadow: inset 0 0 10px #000;\n  -moz-box-shadow: inset 0 0 10px #000;\n  -ms-box-shadow: inset 0 0 10px #000;\n}\n\n.list-img .list-element .block-info {\n    font-weight: 100;\n    display: inline-block;\n    padding: 5px;\n    vertical-align: middle;\n}\n\n.list-img .list-element .block-info .scene-info {\n    display: inline-block;\n    padding: 3px;\n    font-size: 12px;\n}\n\n\n.list-img .list-element .block-info img {\n  width: 40px\n}\n\n.list-img .list-element .block-info img:before {\n\tcontent: '';\n\tdisplay: block;\n\tpadding-top: 40px;\n}\n\n.lazyload {\n    opacity: 0;\n}\n\n.lazyloading {\n    opacity: 1;\n    transition: opacity 300ms;\n    background: #000 url(/img/spinner3.gif) no-repeat center;\n}\n\n.lazyloaded {\n    background: none;\n    opacity: 1;\n    transition: opacity 300ms;\n}\n\n/*    */\n\n.landsat-info {\n  position: absolute;\n  left: 0;\n  bottom: 0;\n  padding: 3px;\n  font-size: 14px;\n  color: #fff;\n  background-color: rgba(0, 0, 0, 0.77);\n  z-index: 10;\n}\n.landsat-info span {\n  display: block;\n}\n\n.l8id:before {\n    content: 'Scene-id: '\n}\n\n.l8date:before {\n    content: 'Date: '\n}\n\n.l8rgb:before {\n    content: 'RGB: '\n}\n\n.loading-map {\n    position: absolute;\n    width: 100%;\n    height: 100%;\n    color: #FFF;\n    background-color: #000;\n    font-size: 18px;\n    text-align: center;\n    z-index: 100;\n    opacity: 1;\n}\n\n.loading-map.off{\n    opacity: 0;\n    -o-transition: all 1.5s ease;\n    -webkit-transition: all 1.5s ease;\n    -moz-transition: all 1.5s ease;\n    -ms-transition: all 1.5s ease;\n    transition: all ease 1.5s;\n    visibility:hidden;\n}\n\n.loading-map .middle-center{\n    position: absolute;\n    top: 50%;\n    left: 50%;\n    transform: translate(-50%, -50%);\n}\n\n.loading-map .middle-center * {\n    display: block;\n    padding: 5px;\n}\n.loading-map .middle-center i{\n    font-size: 24px;\n}\n\n.spin {\n    position: absolute;\n    top: 50%;\n    left: 50%;\n    transform: translate(-50%, -50%);\n    width: 100%;\n    font-size: 14px;\n    text-align: center;\n}\n\n.metaloader,\n.errorMessage {\n  position: absolute;\n  bottom: 20px;\n  left: 20px;\n  text-align: center;\n  z-index: 100;\n  color: #fff;\n  z-index: 10;\n}\n\n.errorMessage {\n  color: #ff0000;\n}\n\n@media(max-width: 767px){\n\n    .mapboxgl-ctrl-attrib {\n        font-size: 10px;\n    }\n\n    .map {\n        display: block;\n        width: 100%;\n        float: none;\n    }\n\n    .right-panel.in ~ .map {\n        height: calc(100% - 200px);\n        width: 100%;\n    }\n\n    .right-panel.in {\n        display: block;\n        position: absolute;\n        width: 100%;\n        height: 200px;\n        margin: 0;\n        padding: 0;\n        overflow-y: auto;\n        float: none;\n        bottom: 0;\n        left: 0;\n    }\n}\n"
  },
  {
    "path": "viewer/index.html",
    "content": "<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n\t<meta charset=\"utf-8\">\r\n\t<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\r\n\r\n\t<title>Landsat viewer</title>\r\n\t<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />\r\n\t<script src='https://api.mapbox.com/mapbox-gl-js/v0.42.2/mapbox-gl.js'></script>\r\n\t<link href='https://api.mapbox.com/mapbox-gl-js/v0.42.2/mapbox-gl.css' rel='stylesheet' />\r\n\r\n\t<link href=\"https://api.mapbox.com/mapbox-assembly/v0.19.0/assembly.min.css\" rel=\"stylesheet\">\r\n\t<script async defer src=\"https://api.mapbox.com/mapbox-assembly/v0.19.0/assembly.js\"></script>\r\n\r\n  <link href=\"css/style.css\" rel=\"stylesheet\">\r\n</head>\r\n\r\n<body>\r\n\t<div class=\"content\">\r\n\t\t<div class=\"main-container\">\r\n\r\n\t\t\t<div class=\"right-panel\">\r\n\r\n\t\t\t\t<div class=\"right-panel-content\">\r\n\r\n\t\t\t\t\t<div class=\"list-img\"></div>\r\n\r\n\t\t\t\t\t<div class=\"img-display-options\">\r\n\t\t\t\t\t\t<span>RGB Combination </span>\r\n\r\n\t\t\t\t\t\t<div class='toggle-group mr18'>\r\n\t\t\t\t\t\t\t<label class='toggle-container'>\r\n\t\t\t\t\t\t\t\t<input data=\"6,5,4\" name='toggle' type='radio' />\r\n\t\t\t\t\t\t\t\t<div class='btn btn--stroke btn--darken50 toggle'>vegetation analysis</div>\r\n\t\t\t\t\t\t\t</label>\r\n\t\t\t\t\t\t\t<label class='toggle-container'>\r\n\t\t\t\t\t\t\t\t<input data=\"7,6,4\" name='toggle' type='radio' />\r\n\t\t\t\t\t\t\t\t<div class='btn btn--stroke btn--darken50 toggle'>urban</div>\r\n\t\t\t\t\t\t\t</label>\r\n\t\t\t\t\t\t\t<label class='toggle-container'>\r\n\t\t\t\t\t\t\t\t<input data=\"4,3,2\" checked name='toggle' type='radio'/>\r\n\t\t\t\t\t\t\t\t<div class='btn btn--stroke btn--darken50 toggle'>natural color</div>\r\n\t\t\t\t\t\t\t</label>\r\n\t\t\t\t\t\t\t<label class='toggle-container'>\r\n\t\t\t\t\t\t\t\t<input data=\"5,4,3\" name='toggle' type='radio' />\r\n\t\t\t\t\t\t\t\t<div class='btn btn--stroke btn--darken50 toggle'>false color</div>\r\n\t\t\t\t\t\t\t</label>\r\n\t\t\t\t\t\t\t<label class='toggle-container'>\r\n\t\t\t\t\t\t\t\t<input data=\"5,6,4\" name='toggle' type='radio' />\r\n\t\t\t\t\t\t\t\t<div class='btn btn--stroke btn--darken50 toggle'>land/water</div>\r\n\t\t\t\t\t\t\t</label>\r\n\t\t\t\t\t\t\t<label class='toggle-container'>\r\n\t\t\t\t\t\t\t\t<input data=\"7,6,5\" name='toggle' type='radio' />\r\n\t\t\t\t\t\t\t\t<div class='btn btn--stroke btn--darken50 toggle'>atmospheric penetration</div>\r\n\t\t\t\t\t\t\t</label>\r\n\t\t\t\t\t\t\t<label class='toggle-container'>\r\n\t\t\t\t\t\t\t\t<input data=\"6,5,2\" name='toggle' type='radio' />\r\n\t\t\t\t\t\t\t\t<div class='btn btn--stroke btn--darken50 toggle'>agriculture</div>\r\n\t\t\t\t\t\t\t</label>\r\n\t\t\t\t\t\t\t<label class='toggle-container'>\r\n\t\t\t\t\t\t\t\t<input data=\"5,6,2\" name='toggle' type='radio' />\r\n\t\t\t\t\t\t\t\t<div class='btn btn--stroke btn--darken50 toggle'>healthy veg</div>\r\n\t\t\t\t\t\t\t</label>\r\n\t\t\t\t\t\t\t<label class='toggle-container'>\r\n\t\t\t\t\t\t\t\t<input data=\"7,5,2\" name='toggle' type='radio' />\r\n\t\t\t\t\t\t\t\t<div class='btn btn--stroke btn--darken50 toggle'>forest burn</div>\r\n\t\t\t\t\t\t\t</label>\r\n\t\t\t\t\t\t\t<label class='toggle-container'>\r\n\t\t\t\t\t\t\t\t<input data=\"7,5,4\" name='toggle' type='radio' />\r\n\t\t\t\t\t\t\t\t<div class='btn btn--stroke btn--darken50 toggle'>shortwave ir</div>\r\n\t\t\t\t\t\t\t</label>\r\n\t\t\t\t\t\t\t<label class='toggle-container'>\r\n\t\t\t\t\t\t\t\t<input data=\"5,7,1\" name='toggle' type='radio' />\r\n\t\t\t\t\t\t\t\t<div class='btn btn--stroke btn--darken50 toggle'>false2</div>\r\n\t\t\t\t\t\t\t</label>\r\n\t\t\t\t\t\t\t<label class='toggle-container'>\r\n\t\t\t\t\t\t\t\t<input data=\"7,5,3\" name='toggle' type='radio' />\r\n\t\t\t\t\t\t\t\t<div class='btn btn--stroke btn--darken50 toggle'>natural w/ atmo</div>\r\n\t\t\t\t\t\t\t</label>\r\n\t\t\t\t\t\t</div>\r\n\r\n\t\t\t\t\t\t<span>Histogramm cut </span>\r\n\r\n\t\t\t\t\t\t<div class=\"inputHisto\">\r\n\t\t\t\t\t\t\t<input id=\"minCount\" class='input col--2' value='5' />\r\n\t\t\t\t\t\t\t<span class=\"col--2 center\"> - </span>\r\n\t\t\t\t\t\t\t<input id=\"maxCount\" class='input col--2' value='95' />\r\n\t\t\t\t\t\t\t<span class=\"col--2 pl6\"> %</span>\r\n\r\n\t\t\t\t\t\t\t<button class='btn' onclick=\"updateMetadata()\">Apply</button>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t<button id='btn-clear' class='btn'><span>Clear</span></button>\r\n\r\n\t\t\t\t\t</div>\r\n\r\n\t\t\t\t\t<span class=\"spin none\">\r\n\t\t\t\t\t\t<div class=\"round animation-spin animation--infinite animation--speed-1\">\r\n\t\t\t\t\t\t\t<svg class='icon icon--l inline-block'><use xlink:href='#icon-satellite'/></svg>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</span>\r\n\r\n\t\t\t\t</div>\r\n\r\n\t\t\t</div>\r\n\r\n\t\t\t<div id=\"map\" class=\"map\">\r\n\r\n\t\t\t\t<div class=\"landsat-info none\">\r\n\t\t\t\t\t<span class=\"l8id\"></span>\r\n\t\t\t\t\t<span class=\"l8date\"></span>\r\n\t\t\t\t\t<span class=\"l8rgb\"></span>\r\n\t\t\t\t</div>\r\n\r\n\t\t\t\t<div class=\"loading-map\">\r\n\t\t\t\t\t<div class=\"middle-center\">\r\n\t\t\t\t\t\t<span>Loading</span>\r\n\t\t\t\t\t\t<div class=\"round animation-spin animation--infinite animation--speed-1\">\r\n\t\t\t\t\t\t\t<svg class='icon icon--l inline-block'><use xlink:href='#icon-satellite'/></svg>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\r\n\t\t\t\t<span class=\"metaloader none\">\r\n\t\t\t\t\t<div class=\"round animation-spin animation--infinite animation--speed-1\">\r\n\t\t\t\t\t\t<svg class='icon icon--l inline-block'><use xlink:href='#icon-satellite'/></svg>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</span>\r\n\r\n\t\t\t\t<span class=\"errorMessage none\">\r\n\t\t\t\t\t\t<svg class='icon icon--l'><use xlink:href='#icon-alert'/></svg>\r\n\t\t\t\t\t\t<span >Error</span>\r\n\t\t\t\t</span>\r\n\r\n\t\t\t</div>\r\n\r\n\t\t</div>\r\n\t</div>\r\n\r\n  <script src=\"https://code.jquery.com/jquery-3.2.1.min.js\"></script>\r\n\t<script src='https://npmcdn.com/@turf/turf@3.5.1/turf.min.js'></script>\r\n\t<script src=\"js/app.js\" charset=\"utf-8\"></script>\r\n\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "viewer/js/app.js",
    "content": "'use strict';\n\nmapboxgl.accessToken = '{YOUR-MAPBOX-TOKEN}';\nconst landsat_tiler_url = '{YOUR-API-GATEWAY-ENDPOINT}'; //e.g https://xxxxxxxxxx.execute-api.xxxxxxx.amazonaws.com/production\nconst sat_api = 'https://api.developmentseed.org/satellites/?search=';\n\nlet scope = {};\n\nconst sortScenes = (a, b) => {\n    return Date.parse(b.date) - Date.parse(a.date);\n};\n\n\nconst parseSceneid_c1 = (sceneid) => {\n\n    const sceneid_info = sceneid.split('_');\n\n    return {\n        satellite: sceneid_info[0].slice(0,1) + sceneid_info[0].slice(3),\n        sensor:  sceneid_info[0].slice(1,2),\n        correction_level: sceneid_info[1],\n        path: sceneid_info[2].slice(0,3),\n        row: sceneid_info[2].slice(3),\n        acquisition_date: sceneid_info[3],\n        ingestion_date: sceneid_info[4],\n        collection: sceneid_info[5],\n        category: sceneid_info[6]\n    };\n};\n\n\nconst parseSceneid_pre = (sceneid) => {\n\n    return {\n        sensor:  sceneid.slice(1,2),\n        satellite: sceneid.slice(2,3),\n        path: sceneid.slice(3,6),\n        row: sceneid.slice(6,9),\n        acquisitionYear: sceneid.slice(9,13),\n        acquisitionJulianDay: sceneid.slice(13,16),\n        groundStationIdentifier: sceneid.slice(16,19),\n        archiveVersion: sceneid.slice(19,21)\n    };\n};\n\n\nconst buildQueryAndRequestL8 = (features) => {\n    $('.list-img').scrollTop(0);\n    $('.list-img').empty();\n    $('.errorMessage').addClass('none');\n    $('.landsat-info').addClass('none');\n\n    if (map.getSource('landsat-tiles')) map.removeSource('landsat-tiles');\n    if (map.getLayer('landsat-tiles')) map.removeLayer('landsat-tiles');\n\n    const prStr = [].concat.apply([], features.map(function(e){\n        return '(path:' + e.properties.PATH.toString() + '+AND+row:' + e.properties.ROW.toString() + ')';\n    })).join('+OR+');\n\n    const query = `${sat_api}satellite_name:landsat-8+AND+(${prStr})&limit=2000`;\n    const results = [];\n\n    $.getJSON(query, (data) => {\n        if (data.meta.found !== 0) {\n\n            for (let i = 0; i < data.results.length; i += 1) {\n                let scene = {};\n                scene.path = data.results[i].path.toString();\n                scene.row = data.results[i].row.toString();\n                scene.grid = data.results[i].path + '/' + data.results[i].row;\n                scene.date = data.results[i].date;\n                scene.cloud = data.results[i].cloud_coverage;\n                scene.browseURL = data.results[i].browseURL.replace('http://', 'https://');\n                scene.thumbURL = scene.browseURL.replace('browse/', 'browse/thumbnails/')\n                scene.sceneID = data.results[i].scene_id;\n                scene.awsID = (Date.parse(scene.date) < Date.parse('2017-05-01')) ?  data.results[i].scene_id.replace(/LGN0[0-9]/, 'LGN00') : data.results[i].LANDSAT_PRODUCT_ID;\n                results.push(scene);\n            }\n\n            results.sort(sortScenes);\n\n          for (let i = 0; i < results.length; i += 1) {\n\n              $('.list-img').append(\n                  '<div class=\"list-element\" onclick=\"initScene(\\'' + results[i].awsID + '\\',\\'' + results[i].date + '\\')\">' +\n                        '<div class=\"block-info\">' +\n                            '<img \"class=\"img-item lazy lazyload\" src=\"' + results[i].thumbURL + '\">' +\n                        '</div>' +\n                        '<div class=\"block-info\">' +\n                            '<span class=\"scene-info\">' + results[i].sceneID + '</span>' +\n                            '<span class=\"scene-info\"><svg class=\"icon inline-block\"><use xlink:href=\"#icon-clock\"/></svg> ' + results[i].date + '</span>' +\n                        '</div>' +\n                    '</div>');\n          }\n\n      } else {\n          $('.errorMessage').removeClass('none');\n      }\n    })\n    .always(() => {\n        $('.spin').addClass('none');\n    })\n    .fail(() => {\n        $('.errorMessage').removeClass('none');\n    });\n}\n\nconst initScene = (sceneID, sceneDate) => {\n    $('.metaloader').removeClass('none');\n    $('.errorMessage').addClass('none');\n\n    let min = $(\"#minCount\").val();\n    let max = $(\"#maxCount\").val();\n    const query = `${landsat_tiler_url}/landsat/metadata/${sceneID}?'pmim=${min}&pmax=${max}`;\n\n    $.getJSON(query, (data) => {\n        scope.imgMetadata = data;\n        updateRasterTile();\n        $('.landsat-info').removeClass('none');\n        $('.landsat-info .l8id').text(sceneID);\n        $('.landsat-info .l8date').text(sceneDate);\n    })\n        .fail(() => {\n            if (map.getSource('landsat-tiles')) map.removeSource('landsat-tiles');\n            if (map.getLayer('landsat-tiles')) map.removeLayer('landsat-tiles');\n            $('.landsat-info span').text('');\n            $('.landsat-info').addClass('none');\n            $('.errorMessage').removeClass('none');\n        })\n        .always(() => {\n            $('.metaloader').addClass('none');\n        });\n};\n\n\nconst updateRasterTile = () => {\n    if (map.getSource('landsat-tiles')) map.removeSource('landsat-tiles');\n    if (map.getLayer('landsat-tiles')) map.removeLayer('landsat-tiles');\n\n    let meta = scope.imgMetadata;\n\n    let rgb = $(\".img-display-options .toggle-group input:checked\").attr(\"data\");\n    const bands = rgb.split(',');\n\n    // NOTE: Calling 512x512px tiles is a bit longer but gives a\n    // better quality image and reduce the number of tiles requested\n\n    // HACK: Trade-off between quality and speed. Setting source.tileSize to 512 and telling landsat-tiler\n    // to get 256x256px reduces the number of lambda calls (but they are faster)\n    // and reduce the quality because MapboxGl will oversample the tile.\n\n    const tileURL = `${landsat_tiler_url}/landsat/tiles/${meta.sceneid}/{z}/{x}/{y}.png?` +\n        `rgb=${rgb}` +\n        '&tile=256' +\n        `&histo=${meta.rgbMinMax[bands[0]]}-${meta.rgbMinMax[bands[1]]}-${meta.rgbMinMax[bands[2]]}`;\n\n    const attrib = '<a href=\"https://landsat.usgs.gov/landsat-8\"> &copy; USGS/NASA Landsat</a>';\n\n    $('.landsat-info .l8rgb').text(rgb);\n\n    map.addSource('landsat-tiles', {\n        type: 'raster',\n        tiles: [tileURL],\n        attribution : attrib,\n        bounds: scope.imgMetadata.bounds,\n        minzoom: 7,\n        maxzoom: 14,\n        tileSize: 256\n    });\n\n    map.addLayer({\n        'id': 'landsat-tiles',\n        'type': 'raster',\n        'source': 'landsat-tiles'\n    });\n};\n\n\nconst updateMetadata = () => {\n    if (!map.getSource('landsat-tiles')) return;\n    initScene(scope.imgMetadata.sceneid, scope.imgMetadata.date);\n}\n\n\n$(\".img-display-options .toggle-group\").change(() => {\n    if (map.getSource('landsat-tiles')) updateRasterTile();\n});\n\ndocument.getElementById(\"btn-clear\").onclick = () => {\n  if (map.getLayer('landsat-tiles')) map.removeLayer('landsat-tiles');\n  if (map.getSource('landsat-tiles')) map.removeSource('landsat-tiles');\n  map.setFilter(\"L8_Highlighted\", [\"in\", \"PATH\", \"\"]);\n  map.setFilter(\"L8_Selected\", [\"in\", \"PATH\", \"\"]);\n\n  $('.list-img').scrollLeft(0);\n  $('.list-img').empty();\n\n  $(\".metaloader\").addClass('off');\n  $('.errorMessage').addClass('none');\n  $(\".landsat-info span\").text('');\n  $(\".landsat-info\").addClass('none');\n\n  scope = {};\n\n  $(\"#minCount\").val(5);\n  $(\"#maxCount\").val(95);\n\n  $(\".img-display-options .toggle-group input\").prop('checked', false);\n  $(\".img-display-options .toggle-group input[data='4,3,2']\").prop('checked', true);\n\n  $('.map').removeClass('in');\n  $('.right-panel').removeClass('in');\n  map.resize();\n};\n\n////////////////////////////////////////////////////////////////////////////////\n\nvar map = new mapboxgl.Map({\n    container: 'map',\n    style: 'mapbox://styles/vincentsarago/ciy1m6t8y005a2rr09jhfplg3',\n    center: [-70.50, 40],\n    zoom: 3,\n    attributionControl: true,\n    minZoom: 3,\n    maxZoom: 14\n});\n\nmap.addControl(new mapboxgl.NavigationControl(), 'top-right');\n\nmap.on('mousemove', (e) => {\n    const features = map.queryRenderedFeatures(e.point, {layers: ['landsat8-pathrow']});\n\n    let pr = ['in', 'PATH', ''];\n\n    if (features.length !== 0) {\n        pr =  [].concat.apply([], ['any', features.map(e => {\n            return ['all', ['==', 'PATH', e.properties.PATH], ['==', 'ROW', e.properties.ROW]];\n        })]);\n    }\n    map.setFilter('L8_Highlighted', pr);\n});\n\nmap.on('click', (e) => {\n    $('.right-panel').addClass('in');\n    $('.spin').removeClass('none');\n    const features = map.queryRenderedFeatures(e.point, {layers: ['landsat8-pathrow']});\n\n    if (features.length !== 0) {\n        $('.map').addClass('in');\n        $('.list-img').removeClass('none');\n        map.resize();\n\n        const pr =  [].concat.apply([], ['any', features.map(e => {\n            return ['all', ['==', 'PATH', e.properties.PATH], ['==', 'ROW', e.properties.ROW]];\n        })]);\n\n        map.setFilter('L8_Selected', pr);\n\n        buildQueryAndRequestL8(features);\n\n        const geojson = {\n          'type': 'FeatureCollection',\n          'features': features\n        };\n\n        const extent = turf.bbox(geojson);\n        const llb = mapboxgl.LngLatBounds.convert([[extent[0], extent[1]], [extent[2], extent[3]]]);\n        map.fitBounds(llb, {padding: 50});\n\n    } else {\n        $('.spin').addClass('none');\n        map.setFilter('L8_Selected', ['in', 'PATH', '']);\n    }\n});\n\nmap.on('load', () => {\n    map.addSource('landsat', {\n        'type': 'vector',\n        'url': 'mapbox://vincentsarago.8ib6ynrs'\n    });\n\n    map.addLayer({\n        'id': 'L8_Highlighted',\n        'type': 'fill',\n        'source': 'landsat',\n        'source-layer': 'Landsat8_Desc_filtr2',\n        'paint': {\n            'fill-outline-color': '#1386af',\n            'fill-color': '#0f6d8e',\n            'fill-opacity': 0.3\n        },\n        'filter': ['in', 'PATH', '']\n    });\n\n    map.addLayer({\n        'id': 'L8_Selected',\n        'type': 'line',\n        'source': 'landsat',\n        'source-layer': 'Landsat8_Desc_filtr2',\n        'paint': {\n            'line-color': '#000',\n            'line-width': 1\n        },\n        'filter': ['in', 'PATH', '']\n    });\n\n    $('.loading-map').addClass('off');\n});\n"
  }
]