Creating a Tile Server From Sentinel Data

Now that we have ingested data, we can use it using a tile server. We will be using the catalog that was created in Ingesting a Sentinel Image.

Note: This guide will focus on converting the raster into a Pillow, RGB image so that it can be used by the tile server. The tile server process itself is discussed in more detail in Greyscale Tile Server Code Breakdown.

The Code

Because we are working with a RGB, a multiband image, we will need to correct the colors for each tile in order for it to displayed correctly.

import io
import numpy as np
import rasterio

from flask import Flask, make_response
from PIL import Image

from geopyspark.geopycontext import GeoPyContext
from geopyspark.geotrellis.catalog import read_value
from geopyspark.geotrellis.constants import SPATIAL


# normalize the data so that it falls in the range of 0 - 255
def make_image(arr):
    adjusted = ((arr - whole_min) * 255) / (whole_max - whole_min)
    return Image.fromarray(adjusted.astype('uint8')).resize((256, 256), Image.NEAREST).convert('L')


app = Flask(__name__)

@app.route("/<int:zoom>/<int:x>/<int:y>.png")
def tile(x, y, zoom):
    # fetch tile

    tile = read_value(geopysc, SPATIAL, uri, layer_name, zoom, x, y)
    arr = tile['data']

    bands = arr.shape[0]
    arrs = [np.array(arr[x, :, :]).reshape(256, 256) for x in range(bands)]

    # display tile
    images = [make_image(arr) for arr in arrs]
    image = Image.merge('RGB', images)

    bio = io.BytesIO()
    image.save(bio, 'PNG')
    response = make_response(bio.getvalue())
    response.headers['Content-Type'] = 'image/png'
    response.headers['Content-Disposition'] = 'filename=%d.png' % 0

    return response


if __name__ == "__main__":
    uri = "file:///tmp/sentinel-catalog"
    layer_name = "sentinel-example"

    geopysc = GeoPyContext(appName="s3-flask", master="local[*]")

    with open('/tmp/sentinel_stats.txt', 'r') as f:
        lines = f.readlines()
        whole_max = int(lines[0])
        whole_min = int(lines[1])

    app.run()

Running the Code

Running the tile server is done the same way as in Greyscale Tile Server Running the Code. The only difference being the resulting image, of course.

../_images/sentinel_image.png

You’ll need to scroll over Corsica, and you should see something that matches the above image. If you do, then the server works!

Breaking Down the Code

This next section will go over how to prepare the RGB image to be served. For a more of a general overview of to setup a tile server please see Greyscale Tile Server Code Breakdown.

Setup

if __name__ == "__main__":
    uri = "file:///tmp/sentinel-catalog"
    layer_name = "sentinel-example"

    geopysc = GeoPyContext(appName="s3-flask", master="local[*]")

    with open('/tmp/sentinel_stats.txt', 'r') as f:
        lines = f.readlines()
        whole_max = int(lines[0])
        whole_min = int(lines[1])

    app.run()

In additon to setting up uri and layer_name, we will also read in the max and min values that we saved earlier. These will be used when we normalize a tile.

Preparing the Tile

# normalize the data so that it falls in the range of 0 - 255
def make_image(arr):
    adjusted = ((arr - whole_min) * 255) / (whole_max - whole_min)
    return Image.fromarray(adjusted.astype('uint8')).resize((256, 256), Image.NEAREST).convert('L')

app = Flask(__name__)

@app.route("/<int:zoom>/<int:x>/<int:y>.png")
def tile(x, y, zoom):
    # fetch tile

    tile = read_value(geopysc, SPATIAL, uri, layer_name, zoom, x, y)
    arr = tile['data']

    bands = arr.shape[0]
    arrs = [np.array(arr[x, :, :]).reshape(256, 256) for x in range(bands)]

    # display tile
    images = [make_image(arr) for arr in arrs]
    image = Image.merge('RGB', images)

Tiles that contain multibands need some work done before they can be served. The make_image method takes each band and normalizes it between a range of 0 and 255. We need to do this because Pillow expects the data types of arrays to be uint8. This is why we need the whole_max and the whole_min values; as we needed to know the full range of the original values before normalization. Information that would be otherwise impossible to get at this point.

Once normalized, the band is then converted to a greyscale image. This is done for each band in the tile, and once complete, we can then make a RGB png file. After this step, the remaining process is no different than if you were working with a singleband tile.

Any details that we not discussed in this document can be found in Greyscale Tile Server Code Breakdown.