示例#1
0
def test_find_extrema_cross_antimeridian():
    """Extrema should be calculated correctly"""
    features = [
        {
            "geometry": {
                "coordinates": [
                    [-190.01, 49.15],
                    [-101.95, -8.41],
                    [-43.24, -32.84],
                    [37.62, -25.17],
                    [71.72, -7.01],
                    [190.01, 48.69],
                ],
                "type":
                "LineString",
            },
            "properties": {},
            "type": "Feature",
        },
        {
            "geometry": {
                "coordinates": [[-98.09, 61.44], [-46.76, 61.1]],
                "type": "LineString",
            },
            "properties": {},
            "type": "Feature",
        },
        {
            "geometry": {
                "coordinates": [[-6.33, 59.89], [59.06, 59.89]],
                "type": "LineString",
            },
            "properties": {},
            "type": "Feature",
        },
    ]
    bounds = find_extrema(features)

    assert bounds == (
        -190.0099999999,
        -32.8399999999,
        190.0099999999,
        61.439999999899996,
    )
示例#2
0
def test_find_extrema_clipped_northsouth():
    """Extrema should be calculated correctly"""
    features = [
        {
            "geometry": {
                "coordinates": [
                    [-190.01, 90],
                    [-101.95, -8.41],
                    [-43.24, -32.84],
                    [37.62, -25.17],
                    [71.72, -7.01],
                    [190.01, -90],
                ],
                "type":
                "LineString",
            },
            "properties": {},
            "type": "Feature",
        },
        {
            "geometry": {
                "coordinates": [[-98.09, 61.44], [-46.76, 61.1]],
                "type": "LineString",
            },
            "properties": {},
            "type": "Feature",
        },
        {
            "geometry": {
                "coordinates": [[-6.33, 59.89], [59.06, 59.89]],
                "type": "LineString",
            },
            "properties": {},
            "type": "Feature",
        },
    ]
    bounds = find_extrema(features)

    assert bounds == (
        -190.0099999999,
        -85.0511287798066,
        190.0099999999,
        85.0511287798066,
    )
示例#3
0
def stac_to_mosaicJSON(
    query: Dict,
    minzoom: int = 7,
    maxzoom: int = 12,
    optimized_selection: bool = True,
    maximum_items_per_tile: int = 20,
    stac_collection_limit: int = 500,
    seasons: Tuple = ["spring", "summer", "autumn", "winter"],
    stac_url: str = os.environ.get("SATAPI_URL",
                                   "https://sat-api.developmentseed.org"),
) -> Dict:
    """
    Create a mosaicJSON from a stac request.

    Attributes
    ----------
    query : str
        sat-api query.
    minzoom : int, optional, (default: 7)
        Mosaic Min Zoom.
    maxzoom : int, optional (default: 12)
        Mosaic Max Zoom.
    optimized_selection : bool, optional (default: true)
        Limit one Path-Row scene per quadkey.
    maximum_items_per_tile : int, optional (default: 20)
        Limit number of scene per quadkey. Use 0 to use all items.
    stac_collection_limit : int, optional (default: None)
        Limits the number of items returned by sat-api
    stac_url : str, optional (default: from ENV)

    Returns
    -------
    out : dict
        MosaicJSON definition.

    """
    if stac_collection_limit:
        query.update(limit=stac_collection_limit)

    logger.debug(json.dumps(query))

    def fetch_sat_api(query):
        headers = {
            "Content-Type": "application/json",
            "Accept-Encoding": "gzip",
            "Accept": "application/geo+json",
        }

        url = f"{stac_url}/stac/search"
        data = requests.post(url, headers=headers, json=query).json()
        error = data.get("message", "")
        if error:
            raise Exception(f"SAT-API failed and returned: {error}")

        meta = data.get("meta", {})
        if not meta.get("found"):
            return []

        logger.debug(json.dumps(meta))

        features = data["features"]
        if data["links"]:
            curr_page = int(meta["page"])
            query["page"] = curr_page + 1
            query["limit"] = meta["limit"]

            features = list(itertools.chain(features, fetch_sat_api(query)))

        return features

    features = fetch_sat_api(query)
    if not features:
        raise Exception(f"No asset found for query '{json.dumps(query)}'")

    logger.debug(f"Found: {len(features)} scenes")

    features = list(
        filter(
            lambda x: _get_season(x["properties"]["datetime"],
                                  max(x["bbox"][1], x["bbox"][3])) in seasons,
            features,
        ))

    if optimized_selection:
        dataset = []
        prs = []
        for item in features:
            pr = item["properties"]["eo:column"] + "-" + item["properties"][
                "eo:row"]
            if pr not in prs:
                prs.append(pr)
                dataset.append(item)
    else:
        dataset = features

    if query.get("bbox"):
        bounds = query["bbox"]
    else:
        bounds = burntiles.find_extrema(dataset)

    for i in range(len(dataset)):
        dataset[i]["geometry"] = shape(dataset[i]["geometry"])

    tiles = burntiles.burn([bbox_to_geojson(bounds)], minzoom)
    tiles = list(set(["{2}-{0}-{1}".format(*tile.tolist()) for tile in tiles]))

    logger.debug(f"Number tiles: {len(tiles)}")

    mosaic_definition = dict(
        mosaicjson="0.0.1",
        minzoom=minzoom,
        maxzoom=maxzoom,
        bounds=bounds,
        center=[(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2,
                minzoom],
        tiles={},
    )

    for tile in tiles:
        z, x, y = list(map(int, tile.split("-")))
        tile = mercantile.Tile(x=x, y=y, z=z)
        quadkey = mercantile.quadkey(*tile)
        geometry = box(*mercantile.bounds(tile))
        intersect_dataset = list(
            filter(lambda x: geometry.intersects(x["geometry"]), dataset))
        if len(intersect_dataset):
            # We limit the item per quadkey to 20
            if maximum_items_per_tile:
                intersect_dataset = intersect_dataset[0:maximum_items_per_tile]

            mosaic_definition["tiles"][quadkey] = [
                scene["properties"]["landsat:product_id"]
                for scene in intersect_dataset
            ]

    return mosaic_definition
示例#4
0
def create_mosaic(
    dataset_list: Tuple,
    minzoom: int = None,
    maxzoom: int = None,
    max_threads: int = 20,
    minimum_tile_cover: float = None,
    tile_cover_sort: bool = False,
    version: str = "0.0.2",
    quiet: bool = True,
) -> Dict:
    """
    Create mosaic definition content.

    Attributes
    ----------
        dataset_list : tuple or list, required
            Dataset urls.
        minzoom: int, optional
            Force mosaic min-zoom.
        maxzoom: int, optional
            Force mosaic max-zoom.
        minimum_tile_cover: float, optional (default: 0)
            Filter files with low tile intersection coverage.
        tile_cover_sort: bool, optional (default: None)
            Sort intersecting files by coverage.
        max_threads : int
            Max threads to use (default: 20).
        version: str, optional
            mosaicJSON definition version
        quiet: bool, optional (default: True)
            Mask processing steps.

    Returns
    -------
        mosaic_definition : dict
            Mosaic definition.

    """
    if version not in ["0.0.1", "0.0.2"]:
        raise Exception(f"Invalid mosaicJSON's version: {version}")

    if not quiet:
        click.echo("Get files footprint", err=True)

    results = get_footprints(dataset_list,
                             max_threads=max_threads,
                             quiet=quiet)

    if minzoom is None:
        minzoom = list(set([feat["properties"]["minzoom"]
                            for feat in results]))
        if len(minzoom) > 1:
            warnings.warn("Multiple MinZoom, Assets different minzoom values",
                          UserWarning)

        minzoom = max(minzoom)

    if maxzoom is None:
        maxzoom = list(set([feat["properties"]["maxzoom"]
                            for feat in results]))
        if len(maxzoom) > 1:
            warnings.warn(
                "Multiple MaxZoom, Assets have multiple resolution values",
                UserWarning)

        maxzoom = max(maxzoom)

    quadkey_zoom = minzoom

    datatype = list(set([feat["properties"]["datatype"] for feat in results]))
    if len(datatype) > 1:
        raise Exception("Dataset should have the same data type")

    if not quiet:
        click.echo(f"Get quadkey list for zoom: {quadkey_zoom}", err=True)

    tiles = burntiles.burn(results, quadkey_zoom)
    tiles = ["{2}-{0}-{1}".format(*tile.tolist()) for tile in tiles]

    bounds = burntiles.find_extrema(results)
    mosaic_definition = dict(
        mosaicjson=version,
        minzoom=minzoom,
        maxzoom=maxzoom,
        bounds=bounds,
        center=[(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2,
                minzoom],
        tiles={},
        version="1.0.0",
    )

    if version == "0.0.2":
        mosaic_definition.update(dict(quadkey_zoom=quadkey_zoom))

    if not quiet:
        click.echo(f"Feed Quadkey index", err=True)

    dataset_geoms = polygons(
        [feat["geometry"]["coordinates"][0] for feat in results])
    dataset = [{
        "path": f["properties"]["path"],
        "geometry": geom
    } for (f, geom) in zip(results, dataset_geoms)]

    for parent in tiles:
        z, x, y = list(map(int, parent.split("-")))
        parent = mercantile.Tile(x=x, y=y, z=z)
        quad = mercantile.quadkey(*parent)
        tile_geometry = polygons(
            mercantile.feature(parent)["geometry"]["coordinates"][0])
        fdataset = [
            dataset[idx] for idx in numpy.nonzero(
                intersects(tile_geometry, dataset_geoms))[0]
        ]
        if minimum_tile_cover is not None or tile_cover_sort:
            fdataset = _filter_and_sort(
                tile_geometry,
                fdataset,
                minimum_cover=minimum_tile_cover,
                sort_cover=tile_cover_sort,
            )
        if len(fdataset):
            mosaic_definition["tiles"][quad] = [f["path"] for f in fdataset]

    return mosaic_definition