def test_quadkey_failure(recwarn):
    """expect a deprecation warning"""
    warnings.simplefilter("always")
    with pytest.raises(mercantile.QuadKeyError):
        mercantile.quadkey_to_tile("lolwut")
    assert len(recwarn) == 1
    assert recwarn.pop(DeprecationWarning)
Exemple #2
0
def _get_geojson(mosaicid: str = None,
                 url: str = None) -> Tuple[str, str, str]:
    """
    Handle /geojson requests.

    Attributes
    ----------
    url : str, required
        Mosaic definition url.

    Returns
    -------
    status : str
        Status of the request (e.g. OK, NOK).
    MIME type : str
        response body MIME type (e.g. application/json).
    body : str
        String encoded JSON metata

    """
    bucket = os.environ["MOSAIC_DEF_BUCKET"]
    url = f"s3://{bucket}/mosaics/{mosaicid}.json.gz"
    mosaic_definition = get_mosaic_content(url)

    geojson = {
        "type":
        "FeatureCollection",
        "features": [
            mercantile.feature(mercantile.quadkey_to_tile(qk),
                               props=dict(quadkey=qk, files=files))
            for qk, files in mosaic_definition["tiles"].items()
        ],
    }

    return ("OK", "application/json", json.dumps(geojson))
Exemple #3
0
    def create(data):
        """
        Bulk inserts prediction tiles
        :params prediction, data
        :returns None
        """

        for prediction in data['predictions']:
            if prediction.get('quadkey_geom') is not None:
                polygon = prediction.get('quadkey_geom')
                bounds = [
                    polygon['coordinates'][0][0][0],
                    polygon['coordinates'][0][0][1],
                    polygon['coordinates'][0][2][0],
                    polygon['coordinates'][0][2][1]
                ]

                prediction[
                    "quadkey_geom"] = "SRID=4326;POLYGON(({0} {1},{0} {3},{2} {3},{2} {1},{0} {1}))".format(
                        bounds[0], bounds[1], bounds[2], bounds[3])
            else:
                bounds = mercantile.bounds(
                    mercantile.quadkey_to_tile(prediction.get('quadkey')))
                prediction[
                    "quadkey_geom"] = "SRID=4326;POLYGON(({0} {1},{0} {3},{2} {3},{2} {1},{0} {1}))".format(
                        bounds[0], bounds[1], bounds[2], bounds[3])

        connection = db.engine.connect()
        connection.execute(PredictionTile.__table__.insert(),
                           data['predictions'])
Exemple #4
0
def quadkey(ctx, input):
    """Takes a [x, y, z] tile or a quadkey as input and writes a
    quadkey or a [x, y, z] tile to stdout, respectively.

    $ echo "[486, 332, 10]" | mercantile quadkey

    Output:

    0313102310

    $ echo "0313102310" | mercantile quadkey

    Output:

    [486, 332, 10]
    """
    src = normalize_input(input)
    try:
        for line in iter_lines(src):
            if line[0] == '[':
                tile = json.loads(line)[:3]
                output = mercantile.quadkey(tile)
            else:
                tile = mercantile.quadkey_to_tile(line)
                output = json.dumps(tile)
            click.echo(output)
    except ValueError:
        raise click.BadParameter("{0}".format(input),
                                 param=input,
                                 param_hint='input')
Exemple #5
0
def quadkey(ctx, input):
    """Takes a [x, y, z] tile or a quadkey as input and writes a
    quadkey or a [x, y, z] tile to stdout, respectively.

    $ echo "[486, 332, 10]" | mercantile quadkey

    Output:

    0313102310

    $ echo "0313102310" | mercantile quadkey

    Output:

    [486, 332, 10]
    """
    src = normalize_input(input)
    try:
        for line in iter_lines(src):
            if line[0] == '[':
                tile = json.loads(line)[:3]
                output = mercantile.quadkey(tile)
            else:
                tile = mercantile.quadkey_to_tile(line)
                output = json.dumps(tile)
            click.echo(output)
    except ValueError:
        raise click.BadParameter(
            "{0}".format(input), param=input, param_hint='input')
Exemple #6
0
def _info(mosaicid: str = None, url: str = None) -> Tuple:
    """Handle /info requests."""
    if not mosaicid and not url:
        return ("NOK", "text/plain", "Missing 'MosaicID or URL' parameter")

    mosaic_path = _create_mosaic_path(mosaicid) if mosaicid else url
    with MosaicBackend(mosaic_path) as mosaic:
        meta = mosaic.metadata
        response = {
            "bounds": meta["bounds"],
            "center": meta["center"],
            "maxzoom": meta["maxzoom"],
            "minzoom": meta["minzoom"],
            "name": mosaicid or url,
        }

        if not mosaic_path.startswith("dynamodb://"):
            mosaic_quadkeys = set(mosaic._quadkeys)
            tile = mercantile.quadkey_to_tile(random.sample(mosaic_quadkeys, 1)[0])
            assets = mosaic.tile(*tile)
            with rasterio.open(assets[0]) as src_dst:
                layer_names = _get_layer_names(src_dst)
                dtype = src_dst.dtypes[0]

            response["quadkeys"] = list(mosaic_quadkeys)
            response["layers"] = layer_names
            response["dtype"] = dtype
        else:
            warnings.warn(
                "Cannot retrieve 'quadkeys,layers and dtype' from dynamoDB mosaic."
            )

        return ("OK", "application/json", json.dumps(response, separators=(",", ":")))
Exemple #7
0
def quadkey(ctx, input):
    """Takes [x, y, z] tiles or quadkeys as input and writes
    quadkeys or a [x, y, z] tiles to stdout, respectively.

    Input may be a compact newline-delimited sequences of JSON or
    a pretty-printed ASCII RS-delimited sequence of JSON (like
    https://tools.ietf.org/html/rfc8142 and
    https://tools.ietf.org/html/rfc7159).

    Examples:

    \b
    echo "[486, 332, 10]" | mercantile quadkey
    0313102310

    \b
    echo "0313102310" | mercantile quadkey
    [486, 332, 10]

    """
    src = normalize_input(input)
    try:
        for line in iter_lines(src):
            if line[0] == "[":
                tile = json.loads(line)[:3]
                output = mercantile.quadkey(tile)
            else:
                tile = mercantile.quadkey_to_tile(line)
                output = json.dumps(tile)
            click.echo(output)
    except mercantile.QuadKeyError:
        raise click.BadParameter("{0}".format(input),
                                 param=input,
                                 param_hint="input")
Exemple #8
0
def quadkey(ctx, input):
    """Takes [x, y, z] tiles or quadkeys as input and writes
    quadkeys or a [x, y, z] tiles to stdout, respectively.

    Input may be a compact newline-delimited sequences of JSON or
    a pretty-printed ASCII RS-delimited sequence of JSON (like
    https://tools.ietf.org/html/rfc8142 and
    https://tools.ietf.org/html/rfc7159).

    $ echo "[486, 332, 10]" | mercantile quadkey

    Output:

    0313102310

    $ echo "0313102310" | mercantile quadkey

    Output:

    [486, 332, 10]
    """
    src = normalize_input(input)
    try:
        for line in iter_lines(src):
            if line[0] == '[':
                tile = json.loads(line)[:3]
                output = mercantile.quadkey(tile)
            else:
                tile = mercantile.quadkey_to_tile(line)
                output = json.dumps(tile)
            click.echo(output)
    except ValueError:
        raise click.BadParameter(
            "{0}".format(input), param=input, param_hint='input')
 def shapes(qk: str = Query(...)):
     """return info."""
     x, y, z = mercantile.quadkey_to_tile(qk)
     with MosaicBackend(self.src_path) as mosaic:
         assets = mosaic.assets_for_tile(x, y, z)
         features = get_footprints(assets, max_threads=MAX_THREADS)
     return FeatureCollection(features=features)
Exemple #10
0
def quadkeys_to_poly(quadkeys):
    quadkeys = sorted(set(quadkeys))
    tiles = [mercantile.quadkey_to_tile(qk) for qk in quadkeys]
    tiles = mercantile.simplify(tiles)
    quadkeys = [mercantile.quadkey(t) for t in tiles]
    polys = quadkeys_to_polys(quadkeys)
    poly = shapely.ops.unary_union(polys)
    return poly
Exemple #11
0
    def update(
        self,
        features: Sequence[Dict],
        add_first: bool = True,
        quiet: bool = False,
        **kwargs,
    ):
        """Update existing MosaicJSON on backend."""
        logger.debug(f"Updating {self.mosaic_name}...")

        new_mosaic = MosaicJSON.from_features(
            features,
            self.mosaic_def.minzoom,
            self.mosaic_def.maxzoom,
            quadkey_zoom=self.quadkey_zoom,
            quiet=quiet,
            **kwargs,
        )

        bounds = bbox_union(new_mosaic.bounds, self.mosaic_def.bounds)

        self.mosaic_def._increase_version()
        self.mosaic_def.bounds = bounds
        self.mosaic_def.center = (
            (bounds[0] + bounds[2]) / 2,
            (bounds[1] + bounds[3]) / 2,
            self.mosaic_def.minzoom,
        )
        self.bounds = bounds

        items: List[Dict[str, Any]] = []

        # Create Metadata item
        # Note: `parse_float=Decimal` is required because DynamoDB requires all numbers to be
        # in Decimal type (ref: https://blog.ruanbekker.com/blog/2019/02/05/convert-float-to-decimal-data-types-for-boto3-dynamodb-using-python/)
        meta = json.loads(self.mosaic_def.json(exclude={"tiles"}),
                          parse_float=Decimal)
        items.append({
            "quadkey": self._metadata_quadkey,
            "mosaicId": self.mosaic_name,
            **meta
        })

        # Create Tile items
        for quadkey, new_assets in new_mosaic.tiles.items():
            tile = mercantile.quadkey_to_tile(quadkey)
            assets = self.assets_for_tile(*tile)
            assets = [*new_assets, *assets
                      ] if add_first else [*assets, *new_assets]
            items.append({
                "mosaicId": self.mosaic_name,
                "quadkey": quadkey,
                "assets": assets
            })

        self._write_items(items)
 def geojson(self) -> dict:
     """Get Raster metadata."""
     return {
         "type": "FeatureCollection",
         "features": [
             mercantile.feature(
                 mercantile.quadkey_to_tile(qk), props=dict(files=files)
             )
             for qk, files in self.mosaic["tiles"].items()
         ],
     }
        def generate_npz():
            nonlocal req_threshold
            labels_dict ={}
            for row in stream:
                if req_inferences != 'all' and row[3].get(req_inferences) is None:
                    continue

                if req_inferences != 'all' and row[3].get(req_inferences) <= req_threshold:
                    continue
                if row[4]:
                    i_lst = pred.inf_list.split(",")

                    #convert raw predictions into 0 or 1 based on threshold
                    raw_pred = []
                    for num, inference in enumerate(i_lst):
                        raw_pred.append(row[3][inference])
                    if  req_inferences == 'all':
                        req_threshold = request.args.get('threshold', '0.5')
                        req_threshold = float(req_threshold)
                    l = [1 if score >= req_threshold else 0 for score in raw_pred]

                    #convert quadkey to x-y-z
                    t = '-'.join([str(i) for i in mercantile.quadkey_to_tile(row[1])])

                    # special case for binary
                    if (pred.inf_binary) and (len(i_lst) != 2):
                        return err(400, "binary models must have two catagories"), 400
                    if (len(i_lst) == 2) and (pred.inf_binary):
                        if list(row[4].values())[0]: #validated and true, keep original
                            labels_dict.update({t:l})
                        else:
                            if l == [1, 0]:
                                l = [0, 1]
                            else:
                                l = [1, 0]
                            labels_dict.update({t:l})
                    else:
                        # for multi-label
                        for key in list(row[4].keys()):
                            i = i_lst.index(key)
                            if not row[4][key]:
                                if l[i] == 0:
                                    l[i] = 1
                                else:
                                    l[i] = 0
                            labels_dict.update({t:l})
            if not labels_dict:
                raise NoValid

            bytestream = io.BytesIO()
            np.savez(bytestream, **labels_dict)
            return bytestream.getvalue()
Exemple #14
0
def children(quadkeys, levels=1):
    if not type(quadkeys) is list:
        quadkeys = [
            quadkeys,
        ]
    for level in range(levels):
        childrenKeys = []
        for qk in quadkeys:
            childrenKeys.extend([
                mercantile.quadkey(t) \
                    for t in mercantile.children(mercantile.quadkey_to_tile(qk))
                ])
        quadkeys = childrenKeys
    return quadkeys
def optimize_group(group, quadkey):
    """Try to find the minimal number of assets to cover tile
    This optimization implies _both_ that
    - assets will be ordered in the MosaicJSON in order of sort of the entire tile
    - the total number of assets is kept to a minimum
    Computing the absolute minimum of assets to cover the tile may not in
    general be possible in finite time, so this is a naive method that should
    work relatively well for this use case.

    Returns group also sorted with respect to intersection of entire tile.
    """
    tile = mercantile.quadkey_to_tile(quadkey)
    tile_geom = box(*mercantile.xy_bounds(tile))
    final_assets = []

    while True:
        # Find intersection percent
        group['int_pct'] = group.geometry.intersection(
            tile_geom).area / tile_geom.area

        # Remove features with no tile overlap
        group = group.loc[group['int_pct'] > 0]

        if len(group) == 0:
            # There are many ocean/border tiles on the edges of available maps
            # that by definition don't have full coverage
            break

        # Sort by cover of region of tile that is left
        group = group.sort_values('int_pct', ascending=False)

        # Remove top asset and add to final_assets
        top_asset = group.iloc[0]
        group = group.iloc[1:]
        final_assets.append(top_asset)

        # Recompute tile_geom, removing overlap with top_asset
        tile_geom = tile_geom.difference(top_asset.geometry)

        # When total area is covered, stop
        if tile_geom.area - 1e-4 < 0:
            break

        if len(group) == 0:
            # There are many ocean/border tiles on the edges of available maps
            # that by definition don't have full coverage
            break

    return gpd.GeoDataFrame(final_assets)
Exemple #16
0
    async def get_values_for_quadkey(self, session, prediction):

        quadkey = prediction['quadkey']
        tile = mercantile.quadkey_to_tile(prediction['quadkey'])
        osm_building_area = await get_building_area(session, tile,
                                                    self.overpass_url)
        return {
            'quadkey': quadkey,
            'centroid': prediction['centroid'],
            'predictions': {
                **prediction['predictions'],
                **{
                    'osm_building_area': osm_building_area
                }
            }
        }
def subset_mosaic(mosaic, overview_qk, overview_zoom):
    """Create subset of mosaic within a single overview quadkey

    Args:
        - overview_qk: zoom 6 quadkey
    """
    qk_tiles = {
        k: v
        for k, v in mosaic['tiles'].items() if k[:overview_zoom] == overview_qk
    }
    bounds = mercantile.bounds(mercantile.quadkey_to_tile(overview_qk))

    # The new mosaic needs to be the same minzoom, quadkey zoom as
    new_mosaic = deepcopy(mosaic)
    new_mosaic['tiles'] = qk_tiles
    new_mosaic['bounds'] = bounds
    return MosaicJSON(**new_mosaic)
    def update(
        self,
        features: Sequence[Dict],
        add_first: bool = True,
        quiet: bool = False,
        **kwargs,
    ):
        """Update existing MosaicJSON on backend."""
        logger.debug(f"Updating {self.mosaic_name}...")

        new_mosaic = self.mosaic_def.from_features(
            features,
            self.mosaic_def.minzoom,
            self.mosaic_def.maxzoom,
            quadkey_zoom=self.quadkey_zoom,
            quiet=quiet,
            **kwargs,
        )

        fout = os.devnull if quiet else sys.stderr
        with click.progressbar(  # type: ignore
                new_mosaic.tiles.items(),
                file=fout,
                show_percent=True,
                label=f"Updating mosaic {self.table_name}:{self.mosaic_name}",
        ) as items:
            for quadkey, new_assets in items:
                tile = mercantile.quadkey_to_tile(quadkey)
                assets = self.assets_for_tile(*tile)
                assets = [*new_assets, *assets
                          ] if add_first else [*assets, *new_assets]

                # add custom sorting algorithm (e.g based on path name)
                self._update_quadkey(quadkey, assets)

        bounds = bbox_union(new_mosaic.bounds, self.mosaic_def.bounds)

        self.mosaic_def._increase_version()
        self.mosaic_def.bounds = bounds
        self.mosaic_def.center = (
            (bounds[0] + bounds[2]) / 2,
            (bounds[1] + bounds[3]) / 2,
            self.mosaic_def.minzoom,
        )

        self._update_metadata()
Exemple #19
0
def _geojson(mosaicid: str = None, url: str = None) -> Tuple:
    """Handle /geojson requests."""
    if not mosaicid and not url:
        return ("NOK", "text/plain", "Missing 'MosaicID or URL' parameter")

    mosaic_path = _create_mosaic_path(mosaicid) if mosaicid else url
    with MosaicBackend(mosaic_path) as mosaic:
        geojson = {
            "type": "FeatureCollection",
            "features": [
                mercantile.feature(
                    mercantile.quadkey_to_tile(qk), props=dict(files=files)
                )
                for qk, files in mosaic.mosaic_def.tiles.items()
            ],
        }

    return ("OK", "application/json", json.dumps(geojson, separators=(",", ":")))
Exemple #20
0
    def __init__(self, xtile, ytile, zoom, uri=None):
        super(Syncher, self).__init__()
        self.uri = uri or get_uri(xtile, ytile, zoom)
        self.tile = {'x': xtile, 'y': ytile, 'z': zoom}
        tile_bounds = mc.bounds(mc.quadkey_to_tile(mc.quadkey(xtile, ytile, zoom)))
        keys = ('w', 's', 'e', 'n',)
        self.bbox = dict(zip(keys, map(str, (
            tile_bounds.west,
            tile_bounds.south,
            tile_bounds.east,
            tile_bounds.north,
        )))) # minx, miny, maxx, maxy

        self.base_query = {
            'query': [[{"k": "qwertyuiop", "modv": "not", "regv": "."}]],
            'bbox': self.bbox,
            'gtypes': ['node', 'way', 'relation'],
        }
Exemple #21
0
def _geojson(mosaicid: str = None, url: str = None) -> Tuple[str, str, str]:
    """Handle /geojson requests."""
    if mosaicid:
        url = _create_path(mosaicid)
    elif url is None:
        return ("NOK", "text/plain", "Missing 'URL' parameter")

    mosaic_def = fetch_mosaic_definition(url)
    geojson = {
        "type":
        "FeatureCollection",
        "features": [
            mercantile.feature(mercantile.quadkey_to_tile(qk),
                               props=dict(files=files))
            for qk, files in mosaic_def["tiles"].items()
        ],
    }

    return ("OK", "application/json", json.dumps(geojson))
 async def get_values_for_quadkey(self, session, quadkey):
     '''
         Returns consolidated data values for a quadkey
     '''
     filtered_quadkeys = list(
         filter(lambda d: d['quadkey'].startswith(quadkey), self.source_data))
     total_ml_building_area = functools.reduce(
         lambda a, b: int(a + b['predictions']['ml_prediction']),
         filtered_quadkeys,
         0)
     tile = mercantile.quadkey_to_tile(quadkey)
     osm_building_area = await get_building_area(session, tile, self.overpass_url)
     return {
         'quadkey': quadkey,
         'centroid': get_tile_center(tile),
         'predictions': {
             'ml_prediction': total_ml_building_area,
             'osm_building_area': osm_building_area
         }
     }
Exemple #23
0
    def update(
        self,
        features: Sequence[Dict],
        add_first: bool = True,
        quiet: bool = False,
        **kwargs,
    ):
        """Update existing MosaicJSON on backend."""
        new_mosaic = self.mosaic_def.from_features(
            features,
            self.mosaic_def.minzoom,
            self.mosaic_def.maxzoom,
            quadkey_zoom=self.quadkey_zoom,
            quiet=quiet,
            **kwargs,
        )

        for quadkey, new_assets in new_mosaic.tiles.items():
            tile = mercantile.quadkey_to_tile(quadkey)
            assets = self.assets_for_tile(*tile)
            assets = [*new_assets, *assets
                      ] if add_first else [*assets, *new_assets]

            # add custom sorting algorithm (e.g based on path name)
            self.mosaic_def.tiles[quadkey] = assets

        bounds = bbox_union(new_mosaic.bounds, self.mosaic_def.bounds)

        self.mosaic_def._increase_version()
        self.mosaic_def.bounds = bounds
        self.mosaic_def.center = (
            (bounds[0] + bounds[2]) / 2,
            (bounds[1] + bounds[3]) / 2,
            self.mosaic_def.minzoom,
        )

        # We only write if path is set
        if self.path:
            self.write(overwrite=True)

        return
Exemple #24
0
def to_geojson(input, collect):
    """Read MosaicJSON document and create GeoJSON features."""
    features = []
    with MosaicBackend(input) as mosaic:
        for qk, assets in mosaic.mosaic_def.tiles.items():
            tile = mercantile.quadkey_to_tile(qk)

            west, south, east, north = mercantile.bounds(tile)

            geom = {
                "type":
                "Polygon",
                "coordinates": [[
                    [west, south],
                    [west, north],
                    [east, north],
                    [east, south],
                    [west, south],
                ]],
            }
            feature = {
                "type": "Feature",
                "id": str(tile),
                "geometry": geom,
                "properties": {
                    "nb_assets": len(assets),
                    "assets": assets
                },
            }

            if collect:
                features.append(feature)
            else:
                click.echo(json.dumps(feature))

        if collect and features:
            click.echo(
                json.dumps({
                    "type": "FeatureCollection",
                    "features": features
                }, ))
def quadkeys_to_bounds(quadkeys: List[str]):
    """Convert list of quadkeys to bounds

    Args:
        - quadkeys: List of quadkeys
    """
    tile_bounds = [
        mercantile.bounds(mercantile.quadkey_to_tile(qk)) for qk in quadkeys
    ]

    minx = 180
    miny = 90
    maxx = -180
    maxy = -90
    for tb in tile_bounds:
        minx = min(minx, tb[0])
        miny = min(miny, tb[1])
        maxx = max(maxx, tb[2])
        maxy = max(maxy, tb[3])

    return [minx, miny, maxx, maxy]
def create_overview_mosaic(urls, quadkey_zoom, min_zoom, max_zoom):
    """Create mosaic representing overview
    """
    # Input is file object
    urls = [l.strip() for l in urls.readlines()]

    quadkeys = [parse_url(url, quadkey_zoom) for url in urls]

    # Find bounds of quadkeys
    bboxes = [
        mercantile.bounds(mercantile.quadkey_to_tile(qk)) for qk in quadkeys
    ]
    minx = min(bboxes, key=lambda bbox: bbox[0])[0]
    miny = min(bboxes, key=lambda bbox: bbox[1])[1]
    maxx = max(bboxes, key=lambda bbox: bbox[2])[2]
    maxy = max(bboxes, key=lambda bbox: bbox[3])[3]
    bounds = [minx, miny, maxx, maxy]

    # Find center
    center = [(minx + maxx) / 2, (miny + maxy) / 2, min_zoom]

    tiles = {}
    for qk, url in zip(quadkeys, urls):
        tiles[qk] = [url]

    mosaic = {
        "mosaicjson": "0.0.2",
        "minzoom": min_zoom,
        "maxzoom": max_zoom,
        "quadkey_zoom": 6,
        "bounds": bounds,
        "center": center,
        "tiles": tiles
    }

    # Validation
    mosaic = MosaicJSON(**mosaic).dict(exclude_none=True)
    print(json.dumps(mosaic, separators=(',', ':')))
def missing_quadkeys(mosaic: Dict,
                     shp_path: str,
                     bounds: List[float] = None,
                     simplify: bool = True) -> Dict:
    """Find quadkeys over land missing from mosaic

    Args:
        - mosaic: mosaic definition
        - shp_path: path to Natural Earth shapefile of land boundaries
        - bounds: force given bounds
        - simplify: reduce size of the tileset as much as possible by merging leaves into parents

    Returns:
        - GeoJSON FeatureCollection of missing tiles
    """
    bounds = bounds or mosaic['bounds']
    top_tile = mercantile.bounding_tile(*bounds)
    gdf = gpd.read_file(shp_path)
    quadkey_zoom = mosaic.get('quadkey_zoom', mosaic['minzoom'])

    # Remove null island
    # Keep the landmasses that are visible at given zoom
    gdf = gdf[gdf['max_zoom'] <= quadkey_zoom]

    land_tiles = find_child_land_tiles(top_tile, gdf, quadkey_zoom)
    quadkeys = {mercantile.quadkey(tile) for tile in land_tiles}

    mosaic_quadkeys = set(mosaic['tiles'].keys())
    not_in_mosaic = quadkeys.difference(mosaic_quadkeys)
    not_in_mosaic = [mercantile.quadkey_to_tile(qk) for qk in not_in_mosaic]

    if simplify:
        not_in_mosaic = mercantile.simplify(not_in_mosaic)

    features = [mercantile.feature(tile) for tile in not_in_mosaic]

    return {'type': 'FeatureCollection', 'features': features}
def test_empty_quadkey_to_tile():
    qk = ""
    expected = mercantile.Tile(0, 0, 0)
    assert mercantile.quadkey_to_tile(qk) == expected
Exemple #29
0
    Field(
        'modified_on',
        'datetime',
        # required = True, # notnull=True,
        update=now,
        default=now,
        # compute = lambda _=None: now(),
        writable=False,
        readable=True),
    Field("is_active", "boolean", default=True, readable=False,
          writable=False),
    # Field('task_id', "reference scheduler_task", notnull=True, requires=None),
    Field.Virtual(
        'feature',
        lambda row: mc.feature(mc.quadkey_to_tile(
            mc.quadkey(row.tracked_tile.xtile, row.tracked_tile.ytile, row.
                       tracked_tile.zoom)),
                               fid=row.tracked_tile.uri,
                               props={
                                   'created': row.tracked_tile.created_on,
                                   'updated': row.tracked_tile.modified_on
                               })),
    # Field.Virtual('last_update', lambda row: get_last_update(row.tile.uri))
)

db.define_table("queued_tile",
                Field("tile_id", "reference tracked_tile", unique=True))


def track_tiles(lon, lat, maxdist, buffer=4):
    """ """
def create_overview_cogs(
    mosaic_path: str,
    output_profile: Dict,
    prefix: str = "mosaic_ovr",
    max_overview_level: int = 6,
    method: str = "first",
    config: Dict = None,
    threads=1,
    in_memory: bool = True,
) -> None:
    """
    Create Low resolution mosaic image from a mosaicJSON.

    The output will be a web optimized COG with bounds matching the mosaicJSON bounds and
    with its resolution matching the mosaic MinZoom - 1.

    Attributes
    ----------
    mosaic_path : str, required
        Mosaic definition path.
    output_profile : dict, required
    prefix : str
    max_overview_level : int
    method: str, optional
        pixel_selection method name (default is 'first').
    config : dict
        Rasterio Env options.
    threads: int, optional
        maximum number of threads to use (default is 1).
    in_memory: bool, optional
        Force COG creation in memory (default is True).

    """
    pixel_method = PIXSEL_METHODS[method]

    with MosaicBackend(mosaic_path) as mosaic:
        base_zoom = mosaic.metadata["minzoom"] - 1
        mosaic_quadkey_zoom = mosaic.quadkey_zoom
        bounds = mosaic.metadata["bounds"]
        mosaic_quadkeys = set(mosaic._quadkeys)

        # Select a random quakey/asset and get dataset info
        tile = mercantile.quadkey_to_tile(random.sample(mosaic_quadkeys, 1)[0])
        assets = mosaic.assets_for_tile(*tile)
        info = _get_info(assets[0])

        extrema = tile_extrema(bounds, base_zoom)
        tilesize = 256
        resolution = _meters_per_pixel(base_zoom, 0, tilesize=tilesize)

        # Create multiples files if coverage is too big
        extremas = _split_extrema(extrema, max_ovr=max_overview_level)
        for ix, extrema in enumerate(extremas):
            click.echo(f"Part {1 + ix}/{len(extremas)}", err=True)
            output_path = f"{prefix}_{ix}.tif"

            blocks = list(_get_blocks(extrema, tilesize))
            random.shuffle(blocks)

            width = (extrema["x"]["max"] - extrema["x"]["min"]) * tilesize
            height = (extrema["y"]["max"] - extrema["y"]["min"]) * tilesize
            w, n = mercantile.xy(*mercantile.ul(
                extrema["x"]["min"], extrema["y"]["min"], base_zoom))

            params = dict(
                driver="GTiff",
                dtype=info["dtype"],
                count=len(info["band_descriptions"]),
                width=width,
                height=height,
                crs="epsg:3857",
                transform=Affine(resolution, 0, w, 0, -resolution, n),
                nodata=info["nodata_value"],
            )
            params.update(**output_profile)

            config = config or {}
            with rasterio.Env(**config):
                with ExitStack() as ctx:
                    if in_memory:
                        tmpfile = ctx.enter_context(MemoryFile())
                        tmp_dst = ctx.enter_context(tmpfile.open(**params))
                    else:
                        tmpfile = ctx.enter_context(
                            TemporaryRasterFile(output_path))
                        tmp_dst = ctx.enter_context(
                            rasterio.open(tmpfile.name, "w", **params))

                    def _get_tile(wind):
                        idx, window = wind
                        x = extrema["x"]["min"] + idx[1]
                        y = extrema["y"]["min"] + idx[0]
                        t = mercantile.Tile(x, y, base_zoom)

                        kds = set(find_quadkeys(t, mosaic_quadkey_zoom))
                        if not mosaic_quadkeys.intersection(kds):
                            return window, None, None

                        try:
                            (tile, mask), _ = mosaic.tile(
                                t.x,
                                t.y,
                                t.z,
                                tilesize=tilesize,
                                pixel_selection=pixel_method(),
                            )
                        except NoAssetFoundError:
                            return window, None, None

                        return window, tile, mask

                    with futures.ThreadPoolExecutor(
                            max_workers=threads) as executor:
                        future_work = [
                            executor.submit(_get_tile, item) for item in blocks
                        ]
                        with click.progressbar(
                                futures.as_completed(future_work),
                                length=len(future_work),
                                show_percent=True,
                                label="Loading tiles",
                        ) as future:
                            for res in future:
                                pass

                    for f in _filter_futures(future_work):
                        window, tile, mask = f
                        if tile is None:
                            continue

                        tmp_dst.write(tile, window=window)
                        if info["nodata_type"] == "Mask":
                            tmp_dst.write_mask(mask.astype("uint8"),
                                               window=window)

                    min_tile_size = tilesize = min(
                        int(output_profile["blockxsize"]),
                        int(output_profile["blockysize"]),
                    )
                    overview_level = get_maximum_overview_level(
                        tmp_dst.width, tmp_dst.height, minsize=min_tile_size)
                    overviews = [2**j for j in range(1, overview_level + 1)]
                    tmp_dst.build_overviews(overviews)
                    copy(tmp_dst,
                         output_path,
                         copy_src_overviews=True,
                         **params)
Exemple #31
0
def test_quadkey_failure():
    with pytest.raises(ValueError):
        mercantile.quadkey_to_tile('lolwut')
Exemple #32
0
def test_quadkey_to_tile():
    qk = "0313102310"
    expected = mercantile.Tile(486, 332, 10)
    assert mercantile.quadkey_to_tile(qk) == expected
Exemple #33
0
def quadkey_to_poly(quadkey):
    x0, y0, x1, y1 = mercantile.bounds(mercantile.quadkey_to_tile(quadkey))
    poly = shapely.geometry.Polygon([[x0, y0], [x0, y1], [x1, y1], [x1, y0]])
    return poly
Exemple #34
0
def quadkey_to_centroid(quadkey):
    return centroid(*mercantile.bounds(mercantile.quadkey_to_tile(quadkey)))