Beispiel #1
0
def test_sentinel_id_invalid():
    """
    Should raise an error with invalid sceneid
    """

    scene = 'S2A_tile_20170323_17SNC'
    with pytest.raises(InvalidSentinelSceneId):
        utils.sentinel_parse_scene_id(scene)
Beispiel #2
0
def tile(sceneid,
         tile_x,
         tile_y,
         tile_z,
         rgb=('04', '03', '02'),
         tilesize=256):
    """Create mercator tile from Sentinel-2 data.

    Attributes
    ----------

    sceneid : str
        Sentinel-2 sceneid.
    tile_x : int
        Mercator tile X index.
    tile_y : int
        Mercator tile Y index.
    tile_z : int
        Mercator tile ZOOM level.
    rgb : tuple, str, optional (default: ('04', '03', '02'))
        Bands index for the RGB combination.
    tilesize : int, optional (default: 256)
        Output image size.

    Returns
    -------
    data : numpy ndarray
    mask: numpy array
    """

    if not isinstance(rgb, tuple):
        rgb = tuple((rgb, ))

    scene_params = utils.sentinel_parse_scene_id(sceneid)
    sentinel_address = '{}/{}'.format(SENTINEL_BUCKET, scene_params['key'])

    sentinel_preview = '{}/preview.jp2'.format(sentinel_address)
    with rasterio.open(sentinel_preview) as src:
        wgs_bounds = transform_bounds(*[src.crs, 'epsg:4326'] +
                                      list(src.bounds),
                                      densify_pts=21)

    if not utils.tile_exists(wgs_bounds, tile_z, tile_x, tile_y):
        raise TileOutsideBounds('Tile {}/{}/{} is outside image bounds'.format(
            tile_z, tile_x, tile_y))

    mercator_tile = mercantile.Tile(x=tile_x, y=tile_y, z=tile_z)
    tile_bounds = mercantile.xy_bounds(mercator_tile)

    addresses = ['{}/B{}.jp2'.format(sentinel_address, band) for band in rgb]

    _tiler = partial(utils.tile_band_worker,
                     bounds=tile_bounds,
                     tilesize=tilesize,
                     nodata=0)
    with futures.ThreadPoolExecutor(max_workers=3) as executor:
        data, masks = zip(*list(executor.map(_tiler, addresses)))
        mask = np.all(masks, axis=0).astype(np.uint8) * 255

    return np.concatenate(data), mask
Beispiel #3
0
def bounds(sceneid):
    """Retrieve image bounds.

    Attributes
    ----------

    sceneid : str
        Sentinel-2 sceneid.

    Returns
    -------
    out : dict
        dictionary with image bounds.
    """

    scene_params = utils.sentinel_parse_scene_id(sceneid)
    sentinel_address = '{}/{}'.format(SENTINEL_BUCKET, scene_params['key'])

    with rasterio.open('{}/preview.jp2'.format(sentinel_address)) as src:
        wgs_bounds = transform_bounds(*[src.crs, 'epsg:4326'] +
                                      list(src.bounds),
                                      densify_pts=21)

    info = {'sceneid': sceneid}
    info['bounds'] = list(wgs_bounds)

    return info
Beispiel #4
0
def area(
    scene,
    bbox,
    expression,
    expression_range=[-1, 1],
    bbox_crs="epsg:4326",
    out_crs="epsg:3857",
):
    """Area handler."""
    max_img_size = 512

    bands = tuple(set(re.findall(r"b(?P<bands>[0-9]{1,2})", expression)))

    scene_params = sentinel_parse_scene_id(scene)
    sentinel_address = f'{SENTINEL_BUCKET}/{scene_params["key"]}'
    scene_info = sentinel2_get_info(os.path.basename(SENTINEL_BUCKET),
                                    scene_params["key"],
                                    request_pays=True)

    addresses = [f"{sentinel_address}/B{band}.jp2" for band in bands]

    _worker = partial(
        get_area,
        bbox=bbox,
        max_img_size=max_img_size,
        bbox_crs=bbox_crs,
        out_crs=out_crs,
    )
    with futures.ThreadPoolExecutor(max_workers=3) as executor:
        data = np.concatenate(list(executor.map(_worker, addresses)))
        if not np.any(data):
            raise Exception("No valid data in array")
        mask = np.all(data != 0, axis=0).astype(np.uint8) * 255

        ctx = {}
        for bdx, b in enumerate(bands):
            ctx["b{}".format(b)] = data[bdx]
        ratio = np.nan_to_num(ne.evaluate(expression, local_dict=ctx))

    ratio = np.where(
        mask,
        linear_rescale(ratio, in_range=expression_range, out_range=[0, 255]),
        0).astype(np.uint8)

    img = array_to_img(ratio, mask, get_colormap(name="cfastie"))
    ndvi = b64_encode_img(img, "jpeg")

    date = (scene_params["acquisitionYear"] + "-" +
            scene_params["acquisitionMonth"] + "-" +
            scene_params["acquisitionDay"])

    return {
        "ndvi": ndvi,
        "date": date,
        "sat": scene_info["sat"],
        "scene": scene,
        "cloud": scene_info["cloud_coverage"],
    }
Beispiel #5
0
def metadata(sceneid, pmin=2, pmax=98):
    """
    Retrieve image bounds and histogram info.

    Attributes
    ----------
    sceneid : str
        Sentinel-2 sceneid.
    pmin : int, optional, (default: 2)
        Histogram minimum cut.
    pmax : int, optional, (default: 98)
        Histogram maximum cut.

    Returns
    -------
    out : dict
        dictionary with image bounds and bands histogram cuts.

    """
    scene_params = utils.sentinel_parse_scene_id(sceneid)
    sentinel_address = "{}/{}".format(SENTINEL_BUCKET, scene_params["key"])

    with rasterio.open("{}/preview.jp2".format(sentinel_address)) as src:
        wgs_bounds = transform_bounds(*[src.crs, "epsg:4326"] +
                                      list(src.bounds),
                                      densify_pts=21)

    info = {"sceneid": sceneid, "bounds": list(wgs_bounds)}

    bands = [
        "01",
        "02",
        "03",
        "04",
        "05",
        "06",
        "07",
        "08",
        "8A",
        "09",
        "10",
        "11",
        "12",
    ]
    addresses = [
        "{}/preview/B{}.jp2".format(sentinel_address, band) for band in bands
    ]
    _min_max_worker = partial(utils.band_min_max_worker, pmin=pmin, pmax=pmax)
    with futures.ThreadPoolExecutor(max_workers=2) as executor:
        responses = list(executor.map(_min_max_worker, addresses))
        info["rgbMinMax"] = dict(zip(bands, responses))

    return info
Beispiel #6
0
def point(scene, coordinates, expression):
    """Point handler."""
    bands = tuple(set(re.findall(r"b(?P<bands>[0-9]{1,2})", expression)))

    scene_params = sentinel_parse_scene_id(scene)
    sentinel_address = f'{SENTINEL_BUCKET}/{scene_params["key"]}'
    scene_info = sentinel2_get_info(os.path.basename(SENTINEL_BUCKET),
                                    scene_params["key"],
                                    request_pays=True)

    addresses = [f"{sentinel_address}/B{band}.jp2" for band in bands]

    def worker(address):
        """Worker."""
        with rasterio.open(address) as band:
            lon_srs, lat_srs = warp.transform("EPSG:4326", band.crs,
                                              [coordinates[0]],
                                              [coordinates[1]])
            point = list(band.sample([(lon_srs[0], lat_srs[0])]))[0]
        return point[0]

    try:
        with futures.ThreadPoolExecutor(max_workers=3) as executor:
            data = list(executor.map(worker, addresses))

            ctx = {}
            for bdx, b in enumerate(bands):
                ctx["b{}".format(b)] = data[bdx]
            ratio = np.nan_to_num(ne.evaluate(expression, local_dict=ctx))
    except IndexError:
        ratio = 0.0

    date = (scene_params["acquisitionYear"] + "-" +
            scene_params["acquisitionMonth"] + "-" +
            scene_params["acquisitionDay"])

    return {
        "ndvi": ratio,
        "date": date,
        "sat": scene_info["sat"],
        "scene": scene,
        "cloud": scene_info["cloud_coverage"],
    }
Beispiel #7
0
def metadata(sceneid, pmin=2, pmax=98):
    """Retrieve image bounds and histogram info.

    Attributes
    ----------

    sceneid : str
        Sentinel-2 sceneid.
    pmin : int, optional, (default: 2)
        Histogram minimum cut.
    pmax : int, optional, (default: 98)
        Histogram maximum cut.

    Returns
    -------
    out : dict
        dictionary with image bounds and bands histogram cuts.
    """

    scene_params = utils.sentinel_parse_scene_id(sceneid)
    sentinel_address = '{}/{}'.format(SENTINEL_BUCKET, scene_params['key'])

    with rasterio.open('{}/preview.jp2'.format(sentinel_address)) as src:
        wgs_bounds = transform_bounds(*[src.crs, 'epsg:4326'] +
                                      list(src.bounds),
                                      densify_pts=21)

    info = {'sceneid': sceneid, 'bounds': list(wgs_bounds)}

    bands = [
        '01', '02', '03', '04', '05', '06', '07', '08', '8A', '09', '10', '11',
        '12'
    ]
    addresses = [
        '{}/preview/B{}.jp2'.format(sentinel_address, band) for band in bands
    ]
    _min_max_worker = partial(utils.band_min_max_worker, pmin=pmin, pmax=pmax)
    with futures.ThreadPoolExecutor(max_workers=2) as executor:
        responses = list(executor.map(_min_max_worker, addresses))
        info['rgbMinMax'] = dict(zip(bands, responses))

    return info
Beispiel #8
0
def test_sentinel_id_valid_strip():
    """
    Should work as expected (parse sentinel scene id)
    """

    scene = 'S2A_tile_20170323_07SNC_0'
    expected_content = {
        'acquisitionDay': '23',
        'acquisitionMonth': '03',
        'acquisitionYear': '2017',
        'key': 'tiles/7/S/NC/2017/3/23/0',
        'lat': 'NC',
        'num': '0',
        'satellite': 'A',
        'sensor': '2',
        'sq': 'S',
        'utm': '07'
    }

    assert utils.sentinel_parse_scene_id(scene) == expected_content
Beispiel #9
0
def tile(sceneid, tile_x, tile_y, tile_z, rgb=('04', '03', '02'),
         r_bds=(0, 16000), g_bds=(0, 16000), b_bds=(1, 16000), tilesize=256):
    """Create mercator tile from Sentinel-2 data and encodes it in base64.

    Attributes
    ----------

    sceneid : str
        Sentinel-2 sceneid.
    tile_x : int
        Mercator tile X index.
    tile_y : int
        Mercator tile Y index.
    tile_z : int
        Mercator tile ZOOM level.
    rgb : tuple, int, optional (default: ('04', '03', '02'))
        Bands index for the RGB combination.
    r_bds : tuple, int, optional (default: (0, 16000))
        First band (red) DN min and max values (DN * 10,000)
        used for the linear rescaling.
    g_bds : tuple, int, optional (default: (0, 16000))
        Second band (green) DN min and max values (DN * 10,000)
        used for the linear rescaling.
    b_bds : tuple, int, optional (default: (0, 16000))
        Third band (blue) DN min and max values (DN * 10,000)
        used for the linear rescaling.
    tilesize : int, optional (default: 256)
        Output image size.

    Returns
    -------
    out : numpy ndarray (type: uint8)
    """

    scene_params = utils.sentinel_parse_scene_id(sceneid)
    sentinel_address = '{}/{}'.format(SENTINEL_BUCKET, scene_params['key'])

    sentinel_preview = '{}/preview.jp2'.format(sentinel_address)
    with rasterio.open(sentinel_preview) as src:
        wgs_bounds = transform_bounds(
            *[src.crs, 'epsg:4326'] + list(src.bounds), densify_pts=21)

    if not utils.tile_exists(wgs_bounds, tile_z, tile_x, tile_y):
        raise TileOutsideBounds('Tile {}/{}/{} is outside image bounds'.format(
            tile_z, tile_x, tile_y))

    mercator_tile = mercantile.Tile(x=tile_x, y=tile_y, z=tile_z)
    tile_bounds = mercantile.xy_bounds(mercator_tile)

    # define a list of bands Min and Max Values (form input)
    histo_cuts = dict(zip(rgb, [r_bds, g_bds, b_bds]))

    addresses = ['{}/B{}.jp2'.format(sentinel_address, band) for band in rgb]
    _tiler = partial(utils.tile_band_worker,
                     bounds=tile_bounds,
                     tilesize=tilesize)

    with futures.ThreadPoolExecutor(max_workers=3) as executor:
        out = np.stack(executor.map(_tiler, addresses))

        for bdx, band in enumerate(rgb):
            # Rescale Intensity to byte (1->255) with 0 being NoData
            out[bdx] = np.where(
                out[bdx] > 0,
                utils.linear_rescale(out[bdx],
                                     in_range=histo_cuts.get(band),
                                     out_range=[1, 255]), 0)

    return out.astype(np.uint8)