Esempio n. 1
0
def coords2png(lat0, lng0, lat1, lng1, out_file):
    # find desirable zoom level
    zoom = 0
    while True:
        tiles_list = list(mercantile.tiles(lng0, lat0, lng1, lat1, [zoom]))
        if len(tiles_list) > MAX_TILES:
            zoom -= 1
            break
        zoom += 1
    print('Zoom level %s' % zoom)
    
    # get tiles list
    tiles_list = list(mercantile.tiles(lng0, lat0, lng1, lat1, [zoom]))
    xs = [tile.x for tile in tiles_list]
    x0 = min(xs)
    x1 = max(xs)
    ys = [tile.y for tile in tiles_list]
    y0 = min(ys)
    y1 = max(ys)
    x_tiles = x1 - x0 + 1
    y_tiles = y1 - y0 + 1
    x_width = x_tiles * TILE_SIDE
    y_width = y_tiles * TILE_SIDE

    # create image
    img = Image.new("RGBA", (x_tiles * TILE_SIDE, y_tiles * TILE_SIDE), (0, 0, 0, 0))
    for x in range(x0, x1 + 1):
        for y in range(y0, y1 + 1):
            print("processing tile %s, %s" % (x, y))
            tile = tile_image(zoom, x, y)
            tile_img = Image.open(io.BytesIO(tile))
            img.paste(tile_img, ((x - x0) * TILE_SIDE, (y - y0) * TILE_SIDE))

    # crop image
    top_left = mercantile.bounds(x0, y0, zoom)
    bottom_right = mercantile.bounds(x1, y1, zoom)
    img_lat1 = top_left.north
    img_lng0 = top_left.west
    img_lat0 = bottom_right.south
    img_lng1 = bottom_right.east
    img_lat_delta = img_lat1 - img_lat0
    img_lng_delta = img_lng1 - img_lng0
    pixel_lat_ratio = float(x_width) / img_lat_delta
    pixel_lng_ratio = float(y_width) / img_lng_delta
    left = int(abs(lng0 - img_lng0) * pixel_lng_ratio)
    right = x_width - int(abs(lng1 - img_lng1) * pixel_lng_ratio)
    top = int(abs(lat1 - img_lat1) * pixel_lat_ratio)
    bottom = y_width - int(abs(lat0 - img_lat0) * pixel_lat_ratio)
    img = img.crop((left, top, right, bottom))
            
    img.save(out_file, 'PNG')
Esempio n. 2
0
def generate_chunk_tasks(image_source, tile_dim):
    tasks = []
    zoom = image_source.zoom
    (min_col, max_col) = (image_source.tile_bounds[0], image_source.tile_bounds[2])
    (min_row, max_row) = (image_source.tile_bounds[1], image_source.tile_bounds[3])

    for tile_col in range(min_col, min(max_col + 1, 2**zoom)):
        for tile_row in range(min_row, min(max_row + 1, 2**zoom)):
            tile_bounds = mercantile.bounds(tile_col, tile_row, zoom)
            (wm_left, wm_bottom, wm_right, wm_top)  = warp.transform_bounds("EPSG:4326",
                                                                           "EPSG:3857",
                                                                            tile_bounds.west,
                                                                            tile_bounds.south,
                                                                            tile_bounds.east,
                                                                            tile_bounds.north)
            affine = transform.from_bounds(wm_left, wm_bottom, wm_right, wm_top, tile_dim, tile_dim)
            target_meta = { 
                "transform": affine[:6],
                "width": tile_dim,
                "height": tile_dim 
            }

            target = os.path.join(image_source.image_folder, "%d/%d/%d.tif" % (zoom, tile_col, tile_row))
            task = ChunkTask(source_uri=image_source.source_uri,
                             target_meta=target_meta,
                             target=target)

            tasks.append(task)

    return tasks
def slice_geoms_to_tiles(grouped_countries):
    tiles = {}
    z = SPLIT_ZOOM
    for x in range(2 ** z):
        for y in range(2 ** z):
            start = datetime.datetime.utcnow()
            twest, tsouth, teast, tnorth = mercantile.bounds(x, y, z)
            bound = shapely.geometry.box(twest, tsouth, teast, tnorth)
            no_country_geom = shapely.geometry.box(twest, tsouth, teast, tnorth)
            parts = []
            for iso, geom, _ in grouped_countries:
                if not bound.intersects(geom):
                    continue
                geom_part = bound.intersection(geom)
                no_country_geom = no_country_geom.difference(geom_part)
                parts.append((iso, geom_part))

            if not no_country_geom.buffer(0).is_empty:
                parts.append(('??', no_country_geom))

            time_spent = (datetime.datetime.utcnow() - start).total_seconds()
            Stat().log('polygons in %s/%s/%s: %s - %s%s', z, x, y,
                       len(parts), '|'.join(iso for iso, _ in parts),
                       '' if time_spent < 1 else (' (%s sec.)' % time_spent))
            tiles['%s/%s/%s' % (z, x, y)] = tuple((iso, geom, geom.bounds)
                                                  for iso, geom in parts)
    return tiles
def triangulate(zoom, output, bounds, tile, tableid):
    if bounds:
        bounds = np.array(bounds).astype(np.float64)
    elif tile:
        epsilon = 1.0e-10
        tile = np.array(tile).astype(np.uint16)
        tBounds = mercantile.bounds(*tile)
        bounds = np.array([
            tBounds.west + epsilon,
            tBounds.south + epsilon,
            tBounds.east - epsilon,
            tBounds.north - epsilon
            ])
    else:
        sys.exit('Error: A bounds or tile must be specified')

    tileMin = mercantile.tile(bounds[0], bounds[3], zoom)
    tileMax = mercantile.tile(bounds[2], bounds[1], zoom)

    pGet = facetParent()

    if tableid:
        gJSON = createDBinit(tileMin, tileMax, zoom, pGet, tableid)
    else:
        gJSON = createFacets(tileMin, tileMax, zoom, pGet)

    if output:
        with open(output, 'w') as oFile:
            for feat in gJSON:
                oFile.write(json.dumps(feat) + '\n')
    else:
        for feat in gJSON:
            click.echo(json.dumps(feat))
def createFacets(tileMin, tileMax, zoom, parentGet):
    for r in range(tileMin.y, tileMax.y + 1):
        for c in range(tileMin.x, tileMax.x + 1):
            quad = tools.quadtree(c, r, zoom)
            boolKey = (r+c) % 2 == 0
            n = parentGet.getParents('n', c, r, zoom)
            s = parentGet.getParents('s', c, r, zoom)
            coords = getCorners(mercantile.bounds(c, r, zoom), boolKey)
            nQT = ''.join(np.dstack((n, quad)).flatten()) + 'n'
            sQT = ''.join(np.dstack((s, quad)).flatten()) + 's'

            yield {
                "type": "Feature",
                "properties": {
                    "qt": nQT
                    },
                "geometry": {
                    "type": "Polygon",
                    "coordinates": [coords[0].tolist()]
                    }
                }
            yield {
                "type": "Feature",
                "properties": {
                    "qt": sQT,
                    },
                "geometry": {
                    "type": "Polygon",
                    "coordinates": [coords[1].tolist()]
                    }
                }
Esempio n. 6
0
def calculate_center(x, y, zoom):
    bounds = mercantile.bounds(x, y, zoom)
    height = bounds.north - bounds.south
    width = bounds.east - bounds.west
    center = (bounds.north + height / 2, bounds.west + width / 2)

    return center
Esempio n. 7
0
def get_image_tile(raster, x, y, z):
    try:
        bound = bounds(x, y, z)

        with rasterio.open(raster) as src:
            x_res, y_res = src.transform[0], src.transform[4]
            p1 = Proj({'init': 'epsg:4326'})
            p2 = Proj(**src.crs)

            # project tile boundaries from lat/lng to source CRS
            tile_ul_proj = transform(p1, p2, bound.west, bound.north)
            tile_lr_proj = transform(p1, p2, bound.east, bound.south)
            # get origin point from the TIF
            tif_ul_proj = (src.bounds.left, src.bounds.top)

            # use the above information to calculate the pixel indices of the window
            top = int((tile_ul_proj[1] - tif_ul_proj[1]) / y_res)
            left = int((tile_ul_proj[0] - tif_ul_proj[0]) / x_res)
            bottom = int((tile_lr_proj[1] - tif_ul_proj[1]) / y_res)
            right = int((tile_lr_proj[0] - tif_ul_proj[0]) / x_res)

            window = ((top, bottom), (left, right))

            # read the first three bands (assumed RGB) of the TIF into an array
            data = np.empty(shape=(3, 256, 256)).astype(src.profile['dtype'])
            for k in (1, 2, 3):
                src.read(k, window=window, out=data[k - 1], boundless=True)

            return Image.fromarray(np.moveaxis(data, 0, -1), mode='RGB')
    except Exception as err:
        raise TileNotFoundError(err)
def process_item(out, min_cache_zoom, cache, link,
                 part_zoom, tiled_countries, all_countries,
                 min_zoom, max_zoom, skip_empty_tiles):
    stat = Stat()
    date = get_date_from_link(link)

    for line in lzma.LZMAFile(get_tile_usage_dump(link)):
        path, count = line.decode().strip().split()
        z, x, y = path.split('/')

        x = int(x)
        y = int(y)
        z = int(z)

        if min_zoom is not None and z < min_zoom:
            continue
        if max_zoom is not None and z > max_zoom:
            continue

        twest, tsouth, teast, tnorth = b = mercantile.bounds(x, y, z)
        country = detect_country_with_cache(
            path, b, x, y, z, part_zoom, tiled_countries, all_countries,
            min_cache_zoom, cache, stat)

        if skip_empty_tiles and country == '??':
            continue

        lat = tnorth + (tnorth - tsouth) / 2
        lon = twest + (teast - twest) / 2

        out.write(('%s,%s,%s,%s,%s,%s,%s,%s\n' % (
            date, z, x, y, count, lat, lon, country)).encode())
    stat.log_stats(date, cache)
Esempio n. 9
0
    async def serve(cls, request):

        # TODO: develop and test some 'request' plugin hooks
        endpoint = request.path.split('.')[-1]
        path_args = request.match_info
        request_hook_response = Configs.plugins.hook('request', endpoint=endpoint, request=request, **path_args)
        # if a request_hook_response is received, return and skip the regular processing:
        if request_hook_response:
            return request_hook_response

        # fetch query parameters from request info
        x = int(request.match_info['x'])
        y = int(request.match_info['y'])
        zoom = int(request.match_info['z'])
        layers = request.match_info['layers']
        try:
            recipe_name = request.match_info['recipe']
        except KeyError:
            recipe_name = 'default_recipe'

        # check that recipe exists
        if recipe_name not in Configs.recipes.keys():
            logger.error('Recipe {0} not found in recipes'.format(recipe_name))
            return aiohttp.errors.HttpBadRequest('Recipe {0} not found in recipes'.format(recipe_name))

        # fetch recipe
        recipe = Configs.recipes[recipe_name]

        # extrapolate the layers
        if layers == 'all':
            layers = list(recipe.layers.keys())
        else:
            layers = layers.split('+')

        # compute bounds and extents
        bounds = mercantile.bounds(x, y, zoom)
        west, south = mercantile.xy(bounds.west, bounds.south)
        east, north = mercantile.xy(bounds.east, bounds.north)

        # process the layers
        layer_data = []
        for layer_name in layers:
            if layer_name not in recipe.layers.keys():
                logger.error('Layer {0} not found in recipe {1}'.format(layer_name, recipe_name))
                return aiohttp.errors.HttpBadRequest('Layer {0} not found in layer config file'.format(layer_name))
            else:
                layer = recipe.layers[layer_name]
                layer_data.append(await cls.query_layer(layer, zoom, west, south, east, north))

        content_type, body = cls.post_process(layer_data)
        response = Response(content_type=content_type, body=body, headers=generic_headers)

        # TODO: develop and test some 'request' plugin hooks
        response_hook_response = Configs.plugins.hook('request', response=response, request=request)
        # if a response_hook_response is received, it overrides the regular response:
        if response_hook_response:
            return response_hook_response
        else:
            return response
Esempio n. 10
0
def test_bbox():
    expected = (-9.140625, 53.12040528310657, -8.7890625, 53.33087298301705)
    bbox = mercantile.bounds(486, 332, 10)
    for a, b in zip(expected, bbox):
        assert round(a-b, 7) == 0
    assert bbox.west == bbox[0]
    assert bbox.south == bbox[1]
    assert bbox.east == bbox[2]
    assert bbox.north == bbox[3]
Esempio n. 11
0
def calculate_wkt(x, y, zoom):
    bounds = mercantile.bounds(x, y, zoom)
    text='POLYGON (('
    text=text+str(bounds.west)+' '+str(bounds.north)+', '
    text=text+str(bounds.east)+' '+str(bounds.north)+', '
    text=text+str(bounds.east)+' '+str(bounds.south)+', '
    text=text+str(bounds.west)+' '+str(bounds.south)
    text=text+'))'
    return text
Esempio n. 12
0
    def mvt(layer, x, y, z):
        def create_vectortile_sql(layer, bounds):
            # Create extent
            west, south = xy(bounds.west, bounds.south)
            east, north = xy(bounds.east, bounds.north)
            extent = "ST_MakeBox2D(ST_MakePoint({west}, {south}), ST_MakePoint({east}, {north}))".format(west=west, south=south, east=east, north=north)

            # e.g. aus_census_2011_shapes.sa1
            geom_table_name = "{schema_name}.{geometry_name}".format(geometry_name=layer["geometry"], schema_name=layer["schema"])
            # e.g. aus_census_2011_shapes.sa1.geom_3857
            geom_column_definition = "{}.geom_3857".format(geom_table_name)

            # Replace the compiled geometry column definition with the zoom-level dependent version
            # e.g. Replace "ST_AsEWKB(aus_census_2011_shapes.sa1.geom_3857)" with "ST_AsMVTGeom(ST_Simplify(aus_census_2011_shapes.sa1.geom_3857, TOLERANCE), EXTENT_OF_TILE)"

            # Zoom 15 is our highest resolution (configured in OpenLayers), so we need to grab
            # unsimplified geometries to allow us to re-use them as the user zooms in.
            if z == 15:
                data_query = layer["_postgis_query"].replace(
                    "ST_AsEWKB({})".format(geom_column_definition),
                    "ST_AsMVTGeom({geom_column_definition}, {extent})".format(geom_column_definition=geom_column_definition, extent=extent)
                )
            else:
                # Bodge bodge
                # Fudge the simplification tolerance so that collections of small and dense geometries (e.g. SA1s in capital cities)
                # don't get dropped too soon
                data_query = layer["_postgis_query"].replace(
                    "ST_AsEWKB({})".format(geom_column_definition),
                    "ST_AsMVTGeom(ST_Simplify({geom_column_definition}, {simplify_tolerance}), {extent})".format(geom_column_definition=geom_column_definition, simplify_tolerance=z_res(z + 2), extent=extent)
                )

            # Drop any geometries that are too small for the user to see in this tile (smaller than about a pixel)
            area_filter = "{geom_table_name}.sqrt_area_geom_3857 >= {area_threshold} AND".format(geom_table_name=geom_table_name, area_threshold=z_res(z + 1.5))

            # FIXME Build this whole query in SQLAlchemy instead
            # We need to begin a WHERE clause if there's no filter on the layer
            if " WHERE " not in layer["_postgis_query"]:
                where_cause = "WHERE"
            else:
                where_cause = "AND"

            return """
                SELECT
                    ST_AsMVT(tile)
                FROM
                    ({data_query}
                    {where_cause}
                        {area_filter}
                        {geom_column_definition} && {extent}
                    ) as tile""".format(data_query=data_query, where_cause=where_cause, area_filter=area_filter, geom_column_definition=geom_column_definition, extent=extent)

        # Wrap EALGIS query in a PostGIS query to produce a vector tile
        mvt_query = create_vectortile_sql(layer, bounds=bounds(x, y, z))
        with ealdb.access_data() as db:
            tile = db.session.execute(mvt_query).fetchone()[0]

        return BytesIO(tile).read()
def calculate_center(x, y, z):
    k = (x, y, z)
    if k not in cache_center:
        bounds = mercantile.bounds(x, y, z)
        height = bounds.north - bounds.south
        width = bounds.east - bounds.west
        center = (bounds.north + height / 2, bounds.west + width / 2)
        cache_center[k] = center
        return center
    return cache_center[k]
Esempio n. 14
0
def test_src_meta_making(expectedMeta):
    bounds = merc.bounds(10, 10, 10)

    src_meta = untiler.make_src_meta(bounds, 4096)

    for k, e in zip(sorted(src_meta), sorted(expectedMeta)):
        assert k == e
        # assert src_meta[k] == expectedMeta[e]

    print("# OK - %s " % (inspect.stack()[0][3]))
Esempio n. 15
0
def tile_to_rect(zoom, x, y, v):
    zoom = int(zoom)
    x = int(x)
    y = int(y)
    box = mercantile.bounds(x, y, zoom)
    return {
        "type": "Feature",
        "properties": {"t": "%s/%s/%s" % (zoom, x, y), "c": v, "k": "#%02X%02X%02X" % get_color(v)},
        "geometry": shapely.geometry.mapping(shapely.geometry.box(*box)),
    }
Esempio n. 16
0
def pyramid_jobs(x, y, z, job_zoom):
    """
    Generate pyramid jobs for a given job_zoom level
    starting at with the parent tile defined by x, y, z.
    """
    if z == job_zoom:
        bounds = mercantile.bounds(x, y, z)
        yield create_pyramid_job(
            x=x, y=y,
            min_zoom=z, max_zoom=14,
            bounds=bounds
        )
        return

    tiles = all_descendant_tiles(x, y, z, job_zoom)
    pyramid_zoom_level_tiles = (t for t in tiles if t.z == job_zoom)

    for tile in pyramid_zoom_level_tiles:
        bounds = mercantile.bounds(tile.x, tile.y, tile.z)
        yield create_pyramid_job(tile.x, tile.y, min_zoom=tile.z,
                                 max_zoom=14, bounds=bounds)
Esempio n. 17
0
def vecttile_mask(tile, save_mask=True):
    bounds = mercantile.bounds(tile.x, tile.y, tile.z)
    ll = mercantile.xy(bounds.west, bounds.south)
    ur = mercantile.xy(bounds.east, bounds.north)
    extent = mapnik.Box2d(*(ll + ur))


    m = mapnik.Map(256, 256)
    m.srs = "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs +over"
    s = mapnik.Style()
    r = mapnik.Rule()

    road_width = 75 # meters
    pixel_size = mercantile.pixel_size(tile.z, bounds.north)
    pixels = int(road_width/pixel_size) + 1
    line_symbolizer = mapnik.LineSymbolizer(mapnik.Color('black'), pixels)
    r.symbols.append(line_symbolizer)
    s.rules.append(r)
    m.append_style('RoadStyle', s)
    layer = mapnik.Layer('Roads from PostGIS')
    layer.srs = "+init=epsg:3857"
    ROADS = """
        (SELECT way
        FROM planet_osm_line
        WHERE roadbiking = TRUE) linestring
    """
    layer.datasource = mapnik.PostGIS(host='localhost', user='******', password='******',
        dbname='osm_uswest', table=ROADS, geometry_field='way')
    layer.styles.append('RoadStyle')
    m.layers.append(layer)




    m.zoom_to_box(extent)

    img = mapnik.Image(m.width, m.height)
    mapnik.render(m, img)
    imgpath = tile_path(os.path.join(tiledir, 'mask'), tile, 'png')
    prep_dirs(imgpath)
    if save_mask:
        img.save(imgpath)

    imgdata = np.frombuffer(img.tostring(), dtype=np.uint8).reshape((256, 256, 4))

    # use alpha channel to get 0->1 scale for mask
    alpha = imgdata[:,:,3] / 255.0  

    # invert for easy multiplication w/ strava alpha channel
    #  1 = no road, keep strava heatmap values)
    #  0 = road, make strava heatmap transparent
    mask = 1 - alpha
    return mask
Esempio n. 18
0
def rounding(z,x,y):
  if z < 16:
    print "Too big"
    raise Exception
  bb = mercantile.bounds(x,y,z)
  bb = [str(f) for f in [bb.west,bb.south,bb.east,bb.north]]
  url = "http://openstreetmap.org/api/0.6/map?bbox=" + ','.join(bb)
  osm_api_response = requests.get(url)
  changeset = task.rounding_changeset(BytesIO(osm_api_response.content))

  filename = "rounding_{0}_{1}_{2}.osm".format(z,x,y)
  return Response(etree.tostring(changeset,pretty_print=True),
    mimetype="text/xml")
Esempio n. 19
0
def changeset(z,x,y):
  if z < 16:
    print "Too big"
    raise Exception
  bb = mercantile.bounds(x,y,z)
  bb = [str(f) for f in [bb.west,bb.south,bb.east,bb.north]]
  url = "http://openstreetmap.org/api/0.6/map?bbox=" + ','.join(bb)
  osm_api_response = requests.get(url)
  changeset = task.changeset(BytesIO(osm_api_response.content),height_db)

  filename = "sfbuildingheight_{0}_{1}_{2}.osm".format(z,x,y)
  return Response(etree.tostring(changeset,pretty_print=True),
    mimetype="text/xml",
    headers={
    'Content-Disposition':'attachment; filename={0}'.format(filename)
  })
Esempio n. 20
0
    def serve(self):
        bounds = mercantile.bounds(self.x, self.y, self.zoom)
        self.west, self.south = mercantile.xy(bounds.west, bounds.south)
        self.east, self.north = mercantile.xy(bounds.east, bounds.north)
        self.layers = []
        if self.namespace not in RECIPES:
            msg = 'Recipe "{}" not found. Available recipes are: {}'
            abort(400, msg.format(self.namespace, list(RECIPES.keys())))
        self.recipe = RECIPES[self.namespace]
        names = self.recipe.layers.keys() if self.ALL else self.names
        for name in names:
            if name not in self.recipe.layers:
                abort(400, u'Layer "{}" not found in recipe {}'.format(
                    name, self.namespace))
            self.process_layer(self.recipe.layers[name])
        self.post_process()

        return self.content, 200, {"Content-Type": self.CONTENT_TYPE}
Esempio n. 21
0
def correct_altitude_mode(context, kmls: type_list_of_kmls):
    """
    Check for KMLs with altitude relative to ground,
    query Mapbox-terrain-rgb and change for relative
    to sea level (absolute)
    """
    for kml in tqdm(kmls, desc="KMLS"):
        with open(kml, "r+") as f:
            txt = f.read()
            if re.search("(?<=altitudeMode>)relative(.+)?(?=\/altitudeMode>)", txt):
                lat = round(float(find_with_re("latitude", txt)), 5)
                lng = round(float(find_with_re("longitude", txt)), 5)
                alt = round(float(find_with_re("altitude", txt)), 5)
                z = 15
                tile = mercantile.tile(lng, lat, z)
                westmost, southmost, eastmost, northmost = mercantile.bounds(tile)
                pixel_column = np.interp(lng, [westmost, eastmost], [0, 256])
                pixel_row = np.interp(lat, [southmost, northmost], [256, 0])
                tile_img = Image.open(
                    requests.get(
                        "https://api.mapbox.com/v4/mapbox.terrain-rgb/10/800/200.pngraw?access_token=pk.eyJ1IjoibWFydGltcGFzc29zIiwiYSI6ImNra3pmN2QxajBiYWUycW55N3E1dG1tcTEifQ.JFKSI85oP7M2gbeUTaUfQQ",
                        stream=True,
                    ).raw
                ).load()

                R, G, B, _ = tile_img[int(pixel_row), int(pixel_column)]
                height = -10000 + ((R * 256 * 256 + G * 256 + B) * 0.1)
                new_height = height + alt
                txt = re.sub(
                    "(?<=<altitudeMode>).+(?=<\/altitudeMode>)", "absolute", txt
                )
                txt = re.sub("(?<=<altitude>).+(?=<\/altitude>)", f"{new_height}", txt)
                txt = re.sub(
                    "(?<=<coordinates>).+(?=<\/coordinates>)",
                    f"{lng},{lat},{new_height}",
                    txt,
                )

                f.seek(0)
                f.write(txt)
                f.truncate()
            else:
                continue
    return kmls
Esempio n. 22
0
def _merge_tiles(tiles, arrays):
    """
    Merge a set of tiles into a single array.

    Parameters
    ---------
    tiles  : list of mercantile.Tile objects
             The tiles to merge.
    arrays : list of numpy arrays
             The corresponding arrays (image pixels) of the tiles. This list
             has the same length and order as the `tiles` argument.

    Returns
    -------
    img : np.ndarray
        Merged arrays.
    extent : tuple
         Bounding box [west, south, east, north] of the returned image
         in long/lat.
    """
    # create (n_tiles x 2) array with column for x and y coordinates
    tile_xys = np.array([(t.x, t.y) for t in tiles])

    # get indices starting at zero
    indices = tile_xys - tile_xys.min(axis=0)

    # the shape of individual tile images
    h, w, d = arrays[0].shape

    # number of rows and columns in the merged tile
    n_x, n_y = (indices + 1).max(axis=0)

    # empty merged tiles array to be filled in
    img = np.zeros((h * n_y, w * n_x, d), dtype=np.uint8)

    for ind, arr in zip(indices, arrays):
        x, y = ind
        img[y * h:(y + 1) * h, x * w:(x + 1) * w, :] = arr

    bounds = np.array([mt.bounds(t) for t in tiles])
    west, south, east, north = (min(bounds[:, 0]), min(bounds[:, 1]),
                                max(bounds[:, 2]), max(bounds[:, 3]))

    return img, (west, south, east, north)
Esempio n. 23
0
    def get_opts(self, count):
        """Return rasterio dataset creation options for the bounding tile."""
        w, s, e, n = list(mercantile.bounds(*self.bounding_tile))

        w, s = mercantile.xy(w, s)
        e, n = mercantile.xy(e, n)

        xcell = ((e - w) / self.tileshape)
        ycell = ((n - s) / self.tileshape)

        return {
            'dtype': np.float32,
            'driver': 'GTiff',
            'height': self.tileshape,
            'width': self.tileshape,
            'count': count,
            'compress': 'lzw',
            'transform': affine.Affine(xcell, 0, w, 0, -ycell, n),
            'crs': 'epsg:3857'}
Esempio n. 24
0
def test_make_tiles_tile_bounds(x, y):
    '''
    Test if children tiles from z10 are created correctly
    '''
    test_bounds = mercantile.bounds(x, y, 10)

    test_bbox = list(mercantile.xy(test_bounds.west, test_bounds.south)) + list(mercantile.xy(test_bounds.east, test_bounds.north))

    test_crs = 'epsg:3857'
    test_minz = 10
    test_maxz = 13

    created_tiles_gen = _make_tiles(test_bbox, test_crs, test_minz, test_maxz)

    assert isinstance(created_tiles_gen, types.GeneratorType)

    created_tiles = list(created_tiles_gen)

    assert len(created_tiles) == 85
Esempio n. 25
0
def test_metadata():
    """Should return correct metadata."""
    with rasterio.open(COG_CMAP) as src_dst:
        meta = reader.metadata(src_dst)
        assert meta["dtype"] == "int8"
        assert meta["colorinterp"] == ["palette"]
        assert not meta.get("scale")
        assert not meta.get("ofsset")
        assert meta.get("colormap")

    with rasterio.open(COG_SCALE) as src_dst:
        meta = reader.metadata(src_dst)
        assert meta["dtype"] == "int16"
        assert meta["colorinterp"] == ["gray"]
        assert meta["scale"] == 0.0001
        assert meta["offset"] == 1000.0
        assert meta["band_descriptions"] == [(1, "Green")]
        assert not meta.get("colormap")
        assert meta["nodata_type"] == "Nodata"

        meta = reader.metadata(src_dst, indexes=1)
        assert meta["colorinterp"] == ["gray"]

        bounds = mercantile.bounds(mercantile.Tile(x=218, y=99, z=8))
        meta = reader.metadata(src_dst, bounds)
        assert meta["colorinterp"] == ["gray"]
        assert meta["bounds"] == bounds

    with rasterio.open(S3_ALPHA_PATH) as src_dst:
        with pytest.warns(AlphaBandWarning):
            meta = reader.metadata(src_dst)
            assert len(meta["band_descriptions"]) == 3
            assert meta["colorinterp"] == ["red", "green", "blue"]
            assert meta["nodata_type"] == "Alpha"

        meta = reader.metadata(src_dst, indexes=(1, 2, 3, 4))
        assert len(meta["band_descriptions"]) == 4
        assert meta["colorinterp"] == ["red", "green", "blue", "alpha"]
        assert meta["nodata_type"] == "Alpha"

    with rasterio.open(S3_MASK_PATH) as src_dst:
        meta = reader.metadata(src_dst)
        assert meta["nodata_type"] == "Mask"
Esempio n. 26
0
    def intersects(self, file, tile):
        ds = gdal.Open(file)
        if ds is None:
            return False
        geo = ds.GetGeoTransform()
        ulx = geo[0]
        uly = geo[3]
        lrx = ulx + ds.RasterXSize * geo[1]
        lry = uly + ds.RasterYSize * geo[5]
        del ds

        f_bb = shapely.geometry.geo.box(min(ulx, lrx), min(uly, lry), max(ulx, lrx), max(uly, lry))
        f_region = Polygon(f_bb)
        x = mercantile.bounds(tile)

        t_bb = shapely.geometry.geo.box(min(x.west, x.east), min(x.north, x.south),
                                        max(x.west, x.east), max(x.north, x.south))
        t_region = Polygon(t_bb)
        return f_region.intersects(t_region)
def salehi():
    with open(sys.argv[1], 'wb') as csvFile:
        fileWriter = csv.writer(csvFile, delimiter=',')
        numberOfRows = int(sys.argv[2])
        sourceProj = Proj(init='epsg:4326')
        targetProj = Proj(init='epsg:3857')
        # x1, y1 = transform(sourceProj, targetProj, 43.0, 24.0)
        # x2, y2 = transform(sourceProj, targetProj, 63.0, 40.0)

        for i in mercantile.tiles(43.85, 25.25, 63.39, 39.87, [15], True):
            bbox = mercantile.bounds(i)
            assert isinstance(bbox, mercantile.LngLatBbox)
            min_x, min_y = bbox.west, bbox.south
            max_x, max_y = bbox.east, bbox.north
            row = (min_x, min_y, max_x, max_y, 256, 256)
            fileWriter.writerow(row)
            numberOfRows = numberOfRows - 1
            if numberOfRows <= 0:
                break
Esempio n. 28
0
    def get_geojson(self):
        """Return geojson of the bounding tile."""
        w, s, e, n = list(mercantile.bounds(*self.bounding_tile))

        return {
            "type": "Feature",
            "properties": {},
            "geometry": {
                "type": "Polygon",
                "coordinates": [
                    [
                        [w, s],
                        [e, s],
                        [e, n],
                        [w, n],
                        [w, s]
                    ]
                ]
            }
        }
Esempio n. 29
0
def tile_bbox(tile, mercator=False):

    if isinstance(tile, mercantile.Tile):
        if mercator:
            return mercantile.xy_bounds(tile)  # EPSG:3857
        else:
            return mercantile.bounds(tile)  # EPSG:4326

    else:
        with open(rasterio_open(tile)) as r:

            if mercator:
                w, s, e, n = r.bounds
                w, s = mercantile.xy(w, s)
                e, n = mercantile.xy(e, n)
                return w, s, e, n  # EPSG:3857
            else:
                return r.bounds  # EPSG:4326

        assert False, "Unable to open tile"
Esempio n. 30
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
                }, ))
Esempio n. 31
0
def generate_ground_points(zoom, num_pts, vert_sigma=10.0):
    """
    This function creates a uniformly sampled grid of points across a single
    Mercantile tile. Uses numpy.random.uniform to pick the 2D locations and
    then assigns a height value based on vert_sigma. Returns 2 Nx3 ndarrays,
    one of lon_lat_height, and the second is the NED locations in the tile.
    Finally we return the Lon, Lat (deg), and HAE of the center point of
    the tile used as the origin of the local level NED frame
    """
    wpafb_lon = -84.049954
    wpafb_lat = 39.8179055
    tile = mercantile.tile(wpafb_lon, wpafb_lat, zoom)
    bounds = mercantile.bounds(tile.x, tile.y, tile.z)
    lons = np.random.uniform(bounds.west, bounds.east, num_pts)
    lats = np.random.uniform(bounds.south, bounds.north, num_pts)
    heights = np.random.standard_normal(num_pts) * vert_sigma
    center = np.array([(bounds.west + bounds.east) / 2,
                       (bounds.north + bounds.south) / 2, 0.0])
    pts_ned = navpy.lla2ned(lats, lons, heights, center[1], center[0], 0.0)
    lon_lat_height = np.vstack((lons, lats, heights)).T
    return lon_lat_height, pts_ned, center
Esempio n. 32
0
def data_tile_meta(prefix, var, tile_pos):
    z, x, y = tile_pos
    tile_meta = mercantile.Tile(x=x, y=y, z=z)
    min_lon, min_lat, max_lon, max_lat = mercantile.bounds(tile_meta)

    data_tile = xr.open_zarr(store=get_store(prefix))[var]
    data_tile = data_tile.sel(longitude=slice(min_lon, max_lon),
                              latitude=slice(max_lat, min_lat))

    return {
        "variable": var,
        "z": z,
        "x": x,
        "y": y,
        "lats": [min_lat, max_lat],
        "lons": [min_lon, max_lon],
        f"{var}Max": float(data_tile.max().compute()),
        f"{var}Min": float(data_tile.min().compute()),
        "units": data_tile.units,
        "description": data_tile.long_name,
    }
Esempio n. 33
0
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]
Esempio n. 34
0
    def createVRT(self, filespec=None, tile=None):
        if filespec is None:
            logging.error("Need to supply a file to process!")
            return

        logging.debug("Creating VRT for %s" % filespec)
        self.drv = gdal.GetDriverByName("VRT")
        base = os.path.splitext(filespec)
        self.vrt = self.drv.Create(base[0] + ".vrt", self.tilesize, self.tilesize, bands=0)
        self.metadata = self.drv.GetMetadata()
        self.vrt.AddBand(gdal.GDT_Byte)
        band = self.vrt.GetRasterBand(1)
        #idx = "%s/%s/%s" % (tile.z, tile.x, tile.y)
        simple = '<SourceFilename relativeToVRT=\"1\">%s</SourceFilename>' % os.path.basename(filespec)
        
        band.SetMetadataItem("SimpleSource", simple)
        bbox = mercantile.bounds(tile)
        # x=0.0, y=0.0, z=0.0, pixel=0.0, line=0.0,
        gcpList = [gdal.GCP(bbox.west,bbox.north,0,0,0),
                   gdal.GCP(bbox.east,bbox.north,0,256,0),
                   gdal.GCP(bbox.east,bbox.south,0,256,256),
                   gdal.GCP(bbox.west,bbox.south,0,0,256)]
        self.vrt.SetGCPs(gcpList, str(''))  # Add the GCPs to the VRT file
        self.makeTileDir(tile)
        tmpfile = base[0] + '-tmp.tif'
        outfile = base[0] + '.tif'
        imgfile = gdal.Open(filespec, gdal.GA_ReadOnly)
        if os.path.exists(filespec):
            # rgbExpand=rgba
            # if "ERSI" not in filespec:
            #     opts = gdal.TranslateOptions(rgbExpand='RGBA', GCPs=gcpList, format='GTiff')
            # else:
            #     opts = gdal.TranslateOptions(GCPs=gcpList, format='GTiff')
            opts = gdal.TranslateOptions(GCPs=gcpList, format='GTiff')

            ds1 = gdal.Translate(tmpfile, imgfile, options=opts)
            #gdal.Warp(outfile, ds1, format='GTiff', xRes=30, yRes=30)
            gdal.Warp(outfile, ds1, format='GTiff', dstSRS='EPSG:4326')
            self.tifs.append(outfile)
            os.remove(tmpfile)
Esempio n. 35
0
def main(args):
    config = load_config(args.config)
    check_classes(config)
    index = [
        i for i in (list(range(len(config["classes"]))))
        if config["classes"][i]["title"] == args.type
    ]
    assert index, "Requested type {} not found among classes title in the config file.".format(
        args.type)

    print("RoboSat.pink - vectorize {} from {}".format(args.type, args.masks))

    with open(args.out, "w", encoding="utf-8") as out:
        first = True
        out.write('{"type":"FeatureCollection","features":[')

        for tile, path in tqdm(list(tiles_from_slippy_map(args.masks)),
                               ascii=True,
                               unit="mask"):
            features = (np.array(Image.open(path).convert("P"),
                                 dtype=np.uint8) == index).astype(np.uint8)
            try:
                C, W, H = features.shape
            except:
                W, H = features.shape
            transform = rasterio.transform.from_bounds(
                (*mercantile.bounds(tile.x, tile.y, tile.z)), W, H)

            for shape, value in rasterio.features.shapes(features,
                                                         transform=transform):
                prop = '"properties":{{"x":{},"y":{},"z":{}}}'.format(
                    int(tile.x), int(tile.y), int(tile.z))
                geom = '"geometry":{{"type": "Polygon", "coordinates":{}}}'.format(
                    json.dumps(shape["coordinates"]))
                out.write('{}{{"type":"Feature",{},{}}}'.format(
                    "," if not first else "", geom, prop))
                first = False

        out.write("]}")
Esempio n. 36
0
def main(args):
    config = load_config(args.config)
    check_classes(config)
    index = [
        i for i in (list(range(len(config["classes"]))))
        if config["classes"][i]["title"] == args.type
    ]
    assert index, "Requested type {} not found among classes title in the config file.".format(
        args.type)
    print("RoboSat.pink - vectorize {} from {}".format(args.type, args.masks))

    out = open(args.out, "w", encoding="utf-8")
    assert out, "Unable to write in output file"

    out.write('{"type":"FeatureCollection","features":[')

    first = True
    for tile, path in tqdm(list(tiles_from_dir(args.masks, xyz_path=True)),
                           ascii=True,
                           unit="mask"):
        mask = (np.array(Image.open(path).convert("P"),
                         dtype=np.uint8) == index).astype(np.uint8)
        try:
            C, W, H = mask.shape
        except:
            W, H = mask.shape
        transform = rasterio.transform.from_bounds(
            (*mercantile.bounds(tile.x, tile.y, tile.z)), W, H)

        for shape, value in rasterio.features.shapes(mask,
                                                     transform=transform,
                                                     mask=mask):
            geom = '"geometry":{{"type": "Polygon", "coordinates":{}}}'.format(
                json.dumps(shape["coordinates"]))
            out.write('{}{{"type":"Feature",{}}}'.format(
                "" if first else ",", geom))
            first = False

    out.write("]}")
Esempio n. 37
0
def comptiles(filedir, comparedir, sampling, filetype):

    # plotdir = '/Users/dnomadb/Documents/pcomp'

    files = os.listdir(filedir)
    cfiles = os.listdir(comparedir)

    if plotdir:
        import matplotlib.pyplot as plot

    for f in files:
        fileinfo = f.split("-")
        if len(fileinfo[-1].split(".")) != 0 and fileinfo[-1].split(".")[-1] == filetype:
            x, y, z = tiledelta.getXYZ(fileinfo)
            bbox = mercantile.bounds(x, y, z)
            with rio.drivers():
                with rio.open(os.path.join(filedir, f), "r") as src:
                    greyimage_before = (
                        src.read(1).astype(np.uint16) + src.read(2).astype(np.uint16) + src.read(3).astype(np.uint16)
                    )
                with rio.open(os.path.join(comparedir, f), "r") as src:
                    greyimage_after = (
                        src.read(1).astype(np.uint16) + src.read(2).astype(np.uint16) + src.read(3).astype(np.uint16)
                    )

                pcplo = tiledelta.compareGreys(greyimage_after, greyimage_before, 10, 20)
                pcplo = pcplo[::sampling, ::sampling]

                if plotdir:
                    fig = plot.figure(figsize=(20, 10))
                    before = fig.add_subplot(131)
                    before.imshow(greyimage_after, cmap="Greys_r")
                    after = fig.add_subplot(132)
                    after.imshow(greyimage_before, cmap="Greys_r")
                    pc2 = fig.add_subplot(133)
                    pc2.imshow(pcplo, cmap="YlGnBu")
                    fig.savefig(os.path.join(plotdir, f))
                else:
                    tiledelta.makeVectors(pcplo, tiledelta.makeAffine(pcplo.shape, bbox))
Esempio n. 38
0
 def get_tiles(change):
     nonlocal url, tile_path
     if dynamic:
         new_tile_path = mkdtemp(prefix='xarray_leaflet_')
         new_url = base_url + '/xarray_leaflet' + new_tile_path + '/{z}/{x}/{y}.png'
         if l in m.layers:
             m.remove_layer(l)
     ((south, west), (north, east)) = change['new']
     tiles = list(mercantile.tiles(west, south, east, north, m.zoom))
     if dynamic:
         da_visible = da.sel(y=slice(north, south), x=slice(west, east))
     else:
         bbox = get_bbox_tiles(tiles)
         da_visible = da.sel(y=slice(bbox.north, bbox.south), x=slice(bbox.west, bbox.east))
     # check if we have visible data
     if 0 not in da_visible.shape:
         da_visible, transform1_args = get_transform(transform1(da_visible, *transform0_args))
     if dynamic:
         tile_path = new_tile_path
         url = new_url
     for tile in tiles:
         path = f'{tile_path}/{int(tile.z)}/{tile.x}/{tile.y}.png'
         # if static map, check if we already have the tile
         # if dynamic map, new tiles are always created
         if dynamic or not os.path.exists(path):
             bbox = mercantile.bounds(tile)
             da_tile = da_visible.sel(y=slice(bbox.north, bbox.south), x=slice(bbox.west, bbox.east))
             # check if we have data for this tile
             if 0 not in da_tile.shape:
                 da_tile = reindex(da_tile, bbox, dx, dy)
                 da_tile, transform2_args = get_transform(transform2(da_tile, *transform1_args))
                 np_tile = get_webmercator(da_tile.values, bbox.west, bbox.north, dx, dy)
                 np_tile, transform3_args = get_transform(transform3(np_tile, *transform2_args))
                 np_tile = colormap(np_tile)
                 write_image(np_tile, path, persist)
     if dynamic:
         l.path = url
         m.add_layer(l)
         l.redraw()
Esempio n. 39
0
def tile_view(request, zoom, x, y):
    """
    Returns an MVT tiles given zoom, x and y in TMS format

    References:
        https://www.mapbox.com/vector-tiles/
        http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-mercator

    """
    bounds = mercantile.bounds(int(x), int(y), int(zoom))
    west, south = mercantile.xy(bounds.west, bounds.south)
    east, north = mercantile.xy(bounds.east, bounds.north)

    pixel = pixel_length(zoom)
    buffer = 4 * pixel
    bbox = Polygon.from_bbox(
        (west - buffer, south - buffer, east + buffer, north + buffer))

    departements = Departement.objects.filter(geom__intersects=bbox)
    departements = departements.annotate(clipped=Intersection('geom', bbox))

    tile = {
        "name":
        "departements",
        "features": [{
            "geometry":
            departement.clipped.simplify(pixel, preserve_topology=True).wkt,
            "properties": {
                "numero": departement.code_dept,
                "nom": departement.nom_dept,
            },
        } for departement in departements],
    }
    vector_tile = mapbox_vector_tile.encode(tile,
                                            quantize_bounds=(west, south, east,
                                                             north))
    return HttpResponse(vector_tile,
                        content_type="application/vnd.mapbox-vector-tile")
def build_source_index(tile, min_zoom, max_zoom):
    source_cache = MemoryAdapter()
    bbox = box(*mercantile.bounds(tile))

    database_url = os.environ.get('DATABASE_URL')

    with psycopg2.connect(database_url) as conn:
        with conn.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur:
            cur.execute(
                """
                SELECT
                    filename, resolution, source, url,
                    min_zoom, max_zoom, priority, approximate_zoom,
                    wkb_geometry
                FROM
                    footprints
                WHERE
                    ST_Intersects(
                        wkb_geometry,
                        ST_GeomFromText(%s, 4326)
                    )
                    AND min_zoom <= %s
                    AND max_zoom >= %s
                    AND enabled = true
                """, (bbox.to_wkt(), min_zoom, max_zoom))

            logger.info("Found %s sources for tile %s, zoom %s-%s",
                        cur.rowcount, tile, min_zoom, max_zoom)

            if not cur.rowcount:
                raise ValueError("No sources found for this tile")

            for row in cur:
                row = dict(row)
                shape = wkb.loads(row.pop('wkb_geometry').decode('hex'))
                source_cache.add_source(shape, row)

    return source_cache
Esempio n. 41
0
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=(',', ':')))
Esempio n. 42
0
def get_tile_tif(tile, imagery, dest_folder):
    """
    Read a GeoTIFF with a window corresponding to a TMS tile

    The TMS tile bounds are converted to the GeoTIFF source CRS. That bounding
    box is converted to a pixel window which is read from the GeoTIFF. For
    remote files which are internally tiled, this will take advantage of HTTP
    GET Range Requests to avoid downloading the entire file. See more info at:
    http://www.cogeo.org/in-depth.html
    """
    bound = bounds(*[int(t) for t in tile.split('-')])
    with rasterio.open(imagery) as src:
        x_res, y_res = src.transform[0], src.transform[4]
        proj_to = Proj(**src.crs)

        # project tile boundaries from lat/lng to source CRS
        tile_ul_proj = proj_to(bound.west, bound.north)
        tile_lr_proj = proj_to(bound.east, bound.south)
        # get origin point from the TIF
        tif_ul_proj = (src.bounds.left, src.bounds.top)

        # use the above information to calculate the pixel indices of the window
        top = int((tile_ul_proj[1] - tif_ul_proj[1]) / y_res)
        left = int((tile_ul_proj[0] - tif_ul_proj[0]) / x_res)
        bottom = int((tile_lr_proj[1] - tif_ul_proj[1]) / y_res)
        right = int((tile_lr_proj[0] - tif_ul_proj[0]) / x_res)

        window = ((top, bottom), (left, right))

        # read the first three bands (assumed RGB) of the TIF into an array
        data = np.empty(shape=(3, 256, 256)).astype(src.profile['dtype'])
        for k in (1, 2, 3):
            src.read(k, window=window, out=data[k - 1], boundless=True)

        # save
        tile_img = op.join(dest_folder, 'tiles', '{}{}'.format(tile, '.jpg'))
        img = Image.fromarray(np.moveaxis(data, 0, -1), mode='RGB')
        img.save(tile_img)
    async def predict_tile(self, session, tile):
        quadkey = get_tile_quadkey(tile)
        bounds = list(mercantile.bounds(tile))
        bbox_str = ','.join(map(str, bounds))
        tile_url = f'{self.tile_url}&bbox={bbox_str}'

        try:
            res = await session.get(tile_url)
            if res.status != 200:
                logging.warning(f'Unable to fetch tile {tile_url}')
                raise Exception(f'Unable to fetch tile {tile_url}')

            data = await res.json()
            return {
                'quadkey': quadkey,
                'centroid': get_tile_center(tile),
                'predictions': {
                    'ml_prediction': data['road_length_km']['total'],
                    'url': tile_url,
                }
            }
        except Exception as e:
            logging.error(str(e))
Esempio n. 44
0
    def get_render_configurations(self, request, **kwargs):
        tile_bounds = list(mercantile.bounds(int(self.kwargs['x']), int(self.kwargs['y']), int(self.kwargs['z'])))
        extent = BBox(tile_bounds, projection=Proj('+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs')).project(
            Proj(
                '+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +a=6378137 +b=6378137 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs'
            )
        )

        base_config = ImageConfiguration(
            extent=extent,
            size=TILE_SIZE,
            image_format='png',
            background_color=TRANSPARENT_BACKGROUND_COLOR
        )

        return base_config, [
            RenderConfiguration(
                variable=x,
                extent=extent,
                size=TILE_SIZE,
                image_format='png'
            ) for x in self.service.variable_set.all()
        ]
def create_cache(part_zoom, countries, splited_countries, min_cache_zoom):
    stat = Stat()

    cache = {}
    if min_cache_zoom:
        stat.log_stats('cache (%s)' % min_cache_zoom, cache)
        return min_cache_zoom, cache

    min_cache_zoom = MIN_ZOOM
    for z in range(MAX_ZOOM + 1):
        for x in range(2 ** z):
            for y in range(2 ** z):
                k = '%s/%s/%s' % (z, x, y)
                b = mercantile.bounds(x, y, z)
                country = detect_country_with_cache(
                    k, b, x, y, z, part_zoom, countries, splited_countries,
                    z, cache, stat)
                if '|' not in country:
                    min_cache_zoom = z
        if min_cache_zoom > 0:
            break
    stat.log_stats('cache (%s)' % min_cache_zoom, cache)
    return min_cache_zoom, cache
Esempio n. 46
0
async def test_cog_read(infile, create_cog_reader):
    async with create_cog_reader(infile) as cog:
        with rasterio.open(infile) as ds:
            _, zoom = get_zooms(ds)
        centroid = Polygon.from_bounds(
            *transform_bounds(cog.epsg, "EPSG:4326", *cog.bounds)).centroid
        tile = mercantile.tile(centroid.x, centroid.y, zoom)

        tile_native_bounds = transform_bounds("EPSG:4326", cog.epsg,
                                              *mercantile.bounds(tile))

        arr = await cog.read(tile_native_bounds, (256, 256))

        with cogeo_reader(infile) as ds:
            rio_tile_arr, rio_tile_mask = ds.tile(tile.x,
                                                  tile.y,
                                                  tile.z,
                                                  tilesize=256,
                                                  resampling_method="bilinear")

        if cog.is_masked:
            tile_arr = np.ma.getdata(arr)
            tile_mask = np.ma.getmask(arr)

            # Make sure image data is the same
            assert pytest.approx(tile_arr - rio_tile_arr,
                                 1) == np.zeros(tile_arr.shape)

            # Make sure mask data is the same
            rio_mask_counts = np.unique(rio_tile_mask, return_counts=True)
            tile_mask_counts = np.unique(tile_mask, return_counts=True)
            assert len(rio_mask_counts[0]) == len(tile_mask_counts[0])
            assert (rio_mask_counts[1][0] *
                    cog.profile["count"] == tile_mask_counts[1][0])

        else:
            assert pytest.approx(arr - rio_tile_arr, 1) == np.zeros(arr.shape)
Esempio n. 47
0
    def writeTile(self, path):
        """Write the image to disk"""
        filespec = "%s/%s/%s/%s/%s.png" % (path, self.z, self.x, self.y, self.y)
        if os.path.exists(filespec) is False:
            tmp = ""
            for i in os.path.dirname(filespec).split('/'):
                tmp += i + '/'
                if os.path.exists(tmp) is False:
                    os.mkdir(tmp)
        file = open(filespec, "wb")
        bytes = self.blob
        logging.debug("Writing %r bytes to %r" % (len(bytes), filespec))
        file.write(bytes)
        file.close()
        suffix = filetype.guess(filespec)
        print('File extension: %s' % suffix.extension)
        if suffix.extension == 'jpg':
            dest = filespec.replace(".png", ".jpg")
            logging.debug("Renaming %r to %r" % (filespec, dest))
            os.rename(filespec, dest)

        foo = mercantile.bounds(self.y, self.x, self.z + 12)
        logging.info("Wrote %r" % filespec)
        return True
Esempio n. 48
0
def get_tile_wms(tile, imagery, folder, imagery_offset, kwargs):
    """
    Read a WMS endpoint with query parameters corresponding to a TMS tile

    Converts the tile boundaries to the spatial/coordinate reference system
    (SRS or CRS) specified by the WMS query parameter.
    """
    # retrieve the necessary parameters from the query string
    query_dict = parse_qs(imagery.lower())
    image_format = query_dict.get('format')[0].split('/')[1]
    wms_version = query_dict.get('version')[0]
    if wms_version == '1.3.0':
        wms_srs = query_dict.get('crs')[0]
    else:
        wms_srs = query_dict.get('srs')[0]

    # find our tile bounding box
    bound = bounds(*[int(t) for t in tile.split('-')])
    p1 = Proj({'init': 'epsg:4326'})
    p2 = Proj({'init': wms_srs})

    # project the tile bounding box from lat/lng to WMS SRS
    tile_ll_proj = transform(p1, p2, bound.west, bound.south)
    tile_ur_proj = transform(p1, p2, bound.east, bound.north)
    if wms_version == '1.3.0':
        bbox = tile_ll_proj[::-1] + tile_ur_proj[::-1]
    else:
        bbox = tile_ll_proj + tile_ur_proj

    # request the image with the transformed bounding box and save
    wms_url = imagery.replace('{bbox}', ','.join([str(b) for b in bbox]))
    r = requests.get(wms_url, auth=kwargs.get('http_auth'))
    tile_img = op.join(folder, '{}.{}'.format(tile, image_format))
    with open(tile_img, 'wb') as w:
        w.write(r.content)
    return tile_img
def pixel_to_location(tile, dx, dy):
    '''Converts a pixel in a tile to a coordinate.

    Args:
      tile: the mercantile tile to calculate the location in.
      dx: the relative x offset in range [0, 1].
      dy: the relative y offset in range [0, 1].

    Returns:
      The coordinate for the pixel in the tile.
    '''

    assert 0 <= dx <= 1, 'x offset is in [0, 1]'
    assert 0 <= dy <= 1, 'y offset is in [0, 1]'

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

    def lerp(a, b, c):
        return a + c * (b - a)

    lon = lerp(west, east, dx)
    lat = lerp(south, north, dy)

    return lon, lat
Esempio n. 50
0
    def create_single_tile(self, tile, in_file, postfix='', interp='lanczos', border=1):
        name = "{}_{}".format(tile.x, tile.y)

        tile_folder = os.path.join(self.out_folder, name)
        os.makedirs(tile_folder, exist_ok=True)

        out_base_name = postfix + '.tif'
        bb = mercantile.bounds(tile)
        out_file = os.path.join(tile_folder, out_base_name)

        if os.path.exists(out_file):
            logger.info('Skipping tile for {}, file exists.'.format(out_file))
        else:

            if not self.intersects(in_file, tile):
                logger.info('Skipping tile for {}, does not intersect.'.format(out_file))
                return None
            else:
                x_res = abs(bb.west - bb.east) / (self.tile_x-2)    # should be fixed
                y_res = abs(bb.north - bb.south) / (self.tile_y-2)  # changes with lat

                # When we select the projection - we will grow by a pixel in all directions to handle partial pixel
                r = ''
                if interp is not None:
                    r = "-r {}".format(interp)

                cmd = 'gdal_translate {} -tr {} {} -projwin {} {} {} {} -a_nodata {} -co COMPRESS=LZW {} {}'.format(
                    r, x_res, y_res,
                    bb.west - x_res * border,
                    bb.north + y_res * border,
                    bb.east + x_res * border,
                    bb.south - y_res * border,
                    self.no_data, in_file, out_file)
                logging.info(cmd)
                os.system(cmd)
        return out_file
Esempio n. 51
0
def main(outfile, xyz, **dump_kw):
    x, y, z = map(int, xyz.split(','))
    minlon, minlat, maxlon, maxlat = (
            round(v, 6) for v in mercantile.bounds(x, y, z))
    
    geom = {
        'type': 'Polygon',
        'coordinates': [[
            [minlon, minlat],
            [minlon, maxlat],
            [maxlon, maxlat],
            [maxlon, minlat],
            [minlon, minlat] ]]}
    feature = {
        'type': 'Feature',
        'id': xyz,
        'geometry': geom,
        'properties': {'title': 'XYZ tile %s' % xyz} }
    collection = {'type': 'FeatureCollection', 'features': [feature]}
    
    with open_output(args.outfile) as sink:
        json.dump(collection, sink, **dump_kw)
    
    return 0
def pixel_to_location(tile, dx, dy):
    """Converts a pixel in a tile to a coordinate.

    Args:
      tile: the mercantile tile to calculate the location in.
      dx: the relative x offset in range [0, 1].
      dy: the relative y offset in range [0, 1].

    Returns:
      The coordinate for the pixel in the tile.
    """

    assert 0 <= dx <= 1, "x offset is in [0, 1]"
    assert 0 <= dy <= 1, "y offset is in [0, 1]"

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

    def lerp(a, b, c):
        return a + c * (b - a)

    lon = lerp(west, east, dx)
    lat = lerp(south, north, dy)

    return lon, lat
def create_cache(part_zoom, countries, splited_countries, min_cache_zoom):
    stat = Stat()

    cache = {}
    if min_cache_zoom:
        stat.log_stats('cache (%s)' % min_cache_zoom, cache)
        return min_cache_zoom, cache

    min_cache_zoom = MIN_ZOOM
    for z in range(MAX_ZOOM + 1):
        for x in range(2**z):
            for y in range(2**z):
                k = '%s/%s/%s' % (z, x, y)
                b = mercantile.bounds(x, y, z)
                country = detect_country_with_cache(k, b, x, y, z, part_zoom,
                                                    countries,
                                                    splited_countries, z,
                                                    cache, stat)
                if '|' not in country:
                    min_cache_zoom = z
        if min_cache_zoom > 0:
            break
    stat.log_stats('cache (%s)' % min_cache_zoom, cache)
    return min_cache_zoom, cache
Esempio n. 54
0
async def get_image(url, available_projections, lon, lat, zoom, session,
                    messages):
    """Download image (tms tile for coordinate lon,lat on level zoom and calculate image hash

    Parameters
    ----------
    url : str
    available_projections : collection
    lon : float
    lat : float
    zoom : int
    session : ClientSession
    messages : list

    Returns
    -------
    ImageHash or None

    """
    tile = list(mercantile.tiles(lon, lat, lon, lat, zooms=zoom))[0]
    bounds = list(mercantile.bounds(tile))

    proj = None
    if "EPSG:4326" in available_projections:
        proj = "EPSG:4326"
    elif "EPSG:3857" in available_projections:
        proj = "EPSG:3857"
    else:
        for proj in sorted(available_projections):
            try:
                CRS.from_string(proj)
            except:
                continue
            break
    if proj is None:
        messages.append("No projection left: {}".format(available_projections))
        return None

    crs_from = CRS.from_string("epsg:4326")
    crs_to = CRS.from_string(proj)
    if not proj == "EPSG:4326":
        transformer = Transformer.from_crs(crs_from, crs_to, always_xy=True)
        bounds = list(transformer.transform(bounds[0], bounds[1])) + list(
            transformer.transform(bounds[2], bounds[3]))

    # WMS < 1.3.0 assumes x,y coordinate ordering.
    # WMS 1.3.0 expects coordinate ordering defined in CRS.
    if crs_to.axis_info[0].direction == "north" and "=1.3.0" in url:
        bbox = ",".join(map(str, [bounds[1], bounds[0], bounds[3], bounds[2]]))
    else:
        bbox = ",".join(map(str, bounds))

    formatted_url = url.format(proj=proj, width=512, height=512, bbox=bbox)
    messages.append("Image URL: {}".format(formatted_url))
    for i in range(3):
        try:
            # Download image
            async with session.request(method="GET",
                                       url=formatted_url,
                                       ssl=False) as response:
                data = await response.read()
                img = Image.open(io.BytesIO(data))
                img_hash = imagehash.average_hash(img)
                messages.append("ImageHash: {}".format(img_hash))
                return img_hash
        except Exception as e:
            messages.append("Could not download image in try {}: {}".format(
                i, str(e)))
        await asyncio.sleep(5)

    return None
Esempio n. 55
0
def bbox_from_import_url(url):
  match = re.search("sfbuildingheight_(\d+)_(\d+)_(\d+).osm",url)
  z = int(match.group(1))
  x = int(match.group(2))
  y = int(match.group(3))
  return mercantile.bounds(x,y,z)
Esempio n. 56
0
        raise ValueError("Could not connect to queue {}".format(queue_name))

    return queue


def create_job(x, y, min_zoom, max_zoom, bounds):
    body = {
        "x": tile.x,
        "y": tile.y,
        "min_zoom": tile.z,
        "max_zoom": 14,
        "bounds": {"west": bounds.west, "south": bounds.south, "east": bounds.east, "north": bounds.north},
    }

    msg = Message()
    msg.set_body(json.dumps(body))
    return msg


if __name__ == "__main__":
    queue = connect_job_queue()
    zoom_level = int(os.getenv("TASK_ZOOM_LEVEL", "8"))

    for tile in tiles_for_switzerland(zoom_level):
        bounds = mercantile.bounds(tile.x, tile.y, tile.z)

        job = create_job(tile.x, tile.y, min_zoom=0, max_zoom=tile.z, bounds=bounds)
        queue.write(job)

        print("{} {} {} {}".format(bounds.west, bounds.south, bounds.east, bounds.north))
Esempio n. 57
0
    def run(self, processes=4):
        '''
        Warp, encode, and tile
        '''

        # get the bounding box + crs of the file to tile
        with rasterio.open(self.inpath) as src:
            bbox = list(src.bounds)
            src_crs = src.crs


        # remove the output filepath if it exists
        if os.path.exists(self.outpath):
            os.unlink(self.outpath)

        # create a connection to the mbtiles file
        conn = sqlite3.connect(self.outpath)
        cur = conn.cursor()

        # create the tiles table
        cur.execute(
            "CREATE TABLE tiles "
            "(zoom_level integer, tile_column integer, "
            "tile_row integer, tile_data blob);")
        # create empty metadata
        cur.execute(
            "CREATE TABLE metadata (name text, value text);")

        conn.commit()

        # populate metadata with required fields
        cur.execute(
            "INSERT INTO metadata "
            "(name, value) "
            "VALUES ('format', ?);",
            (self.image_format, ))

        cur.execute(
            "INSERT INTO metadata "
            "(name, value) "
            "VALUES ('name', '');")
        cur.execute(
            "INSERT INTO metadata "
            "(name, value) "
            "VALUES ('description', '');")
        cur.execute(
            "INSERT INTO metadata "
            "(name, value) "
            "VALUES ('version', '1');")
        cur.execute(
            "INSERT INTO metadata "
            "(name, value) "
            "VALUES ('type', 'baselayer');")

        conn.commit()

        if processes == 1:
            # use mock pool for profiling / debugging
            self.pool = MockTub(_main_worker, (self.inpath, self.run_function, self.global_args))
        else:
            self.pool = Pool(processes, _main_worker, (self.inpath, self.run_function, self.global_args))

        # generator of tiles to make
        if self.bounding_tile is None:
            tiles = _make_tiles(bbox, src_crs, self.min_z, self.max_z)
        else:
            constrained_bbox = list(mercantile.bounds(self.bounding_tile))
            tiles = _make_tiles(constrained_bbox, 'epsg:4326', self.min_z, self.max_z)


        for tile, contents in self.pool.imap_unordered(self.run_function,
                                                       tiles):
            x, y, z = tile

            # mbtiles use inverse y indexing
            tiley = int(math.pow(2, z)) - y - 1

            # insert tile object
            cur.execute(
                "INSERT INTO tiles "
                "(zoom_level, tile_column, tile_row, tile_data) "
                "VALUES (?, ?, ?, ?);",
                (z, x, tiley, buffer(contents)))

            conn.commit()

        conn.close()

        self.pool.close()
        self.pool.join()

        return None
Esempio n. 58
0
def streaming_tile_worker(data):
    size = 2 ** (data['zMax'] - globalArgs['compositezoom']) * globalArgs['tileResolution']
    out_meta = make_src_meta(merc.bounds(data['x'], data['y'], data['z']), size, globalArgs['creation_opts'])
    filename = globalArgs['sceneTemplate'] % (data['z'], data['x'], data['y'])
    subtiler = tile_utils.TileUtils()
    log = 'FILE: %s\n' % filename
    try:
        with rasterio.drivers():
            with rasterio.open(filename, 'w', **out_meta) as dst:
                if data['zMaxCov']: 
                    superTiles = subtiler.get_super_tiles(data['zMaxTiles'], data['zMaxCov'])

                    fillbaseX, fillbaseY = subtiler.get_sub_base_zoom(data['x'], data['y'], data['z'], data['zMaxCov'])

                    ## fill thresh == the number of sub tiles that would need to occur in a fill tile to not fill (eg completely covered)
                    fThresh = 4 ** (data['zMax'] - data['zMaxCov'])

                    fDiff = 2 ** (data['zMax'] - data['zMaxCov'])

                    toFaux, frFaux = affaux(fDiff)

                    if not globalArgs['no_fill']:
                        print('filling')
                        ## Read and write the fill tiles first
                        for t in subtiler.get_fill_super_tiles(superTiles, data['maxCovTiles'], fThresh):
                            z, x, y = t
                            path = globalArgs['readTemplate'] % (z, x, y)
                            log += '%s %s %s\n' % (z, x, y)

                            with rasterio.open(path) as src:
                                imdata = src.read()

                            imdata = make_image_array(imdata, globalArgs['tileResolution'])

                            imdata = upsample(imdata, fDiff, frFaux, toFaux)

                            window = make_window(x, y, fillbaseX, fillbaseY, globalArgs['tileResolution'] * fDiff)
                            dst.write(imdata, window=window)


                baseX, baseY = subtiler.get_sub_base_zoom(data['x'], data['y'], data['z'], data['zMax'])

                for t in data['zMaxTiles']:
                    z, x, y = t
                    path = globalArgs['readTemplate'] % (z, x, y)
                    log += '%s %s %s\n' % (z, x, y)

                    with rasterio.open(path) as src:
                        imdata = src.read()

                    imdata = make_image_array(imdata, globalArgs['tileResolution'])

                    window = make_window(x, y, baseX, baseY, globalArgs['tileResolution'])

                    dst.write(imdata, window=window)
            if globalArgs['logdir']:
                with open(os.path.join(globalArgs['logdir'], '%s.log' % os.path.basename(filename)), 'w') as logger:
                    logwriter(logger, log)

            return filename

    except Exception as e:
        click.echo("%s errored" % (path), err=True)
        raise e
Esempio n. 59
0
    def fetch(self, bbox: typing.List, zoom: int = None, *args, **kwargs):
        """
        The function fetching tiles from a Slippy Map provider, composing them into a single image, and cropping it
        to match the given BBOX. Retrieval of each tile is repeated self.max_retries times, waiting self.retry_delay
        seconds between consecutive requests.

        :param bbox: bounding box of the background image, dataset compliant format: [west, east, south, north, CRS]
        :param zoom: zoom with which to retrieve Slippy Map's tiles (by default, it's calculated based on width, height)
        :return: None if the CRS is different from self.tiles_crs, or background Image
        """

        if not self.url:
            logger.error("Thumbnail background requires url to be configured.")
            raise ThumbnailError("Tiled background improperly configured.")

        if bbox[-1].lower() != self.crs.lower():
            # background service is not available the requested CRS CRS
            logger.debug(
                f"Thumbnail background generation skipped. "
                f"Clashing CRSs: requested {bbox[-1]}, supported {self.crs}")
            return

        bbox = [float(coord) for coord in bbox[0:4]]

        # check if BBOX fits within the EPSG:3857 map, if not - return an empty background
        if bbox[2] > self._epsg3857_max_y or bbox[3] < -self._epsg3857_max_y:
            return Image.new("RGB",
                             (self.thumbnail_width, self.thumbnail_height),
                             (250, 250, 250))

        bbox4326 = self.bbox3857to4326(*bbox)

        # change bbox from dataset (left, right, bottom, top) to mercantile (left, bottom, right, top)
        self._mercantile_bbox = [
            bbox4326[0], bbox4326[2], bbox4326[1], bbox4326[3]
        ]

        # calculate zoom level
        if zoom is None:
            zoom = self.calculate_zoom()
        else:
            zoom = int(zoom)

        top_left_tile = mercantile.tile(bbox4326[0], bbox4326[3], zoom)
        bottom_right_tile = mercantile.tile(bbox4326[1], bbox4326[2], zoom)

        # rescaling factors - indicators of how west and east BBOX boundaries are offset in respect to the world's map;
        # east and west boundaries may exceed the maximum coordinate of the world in EPSG:3857. In such case additinal
        # number of tiles need to be fetched to compose the image and the boundary tiles' coordinates need to be
        # rescaled to ensure the proper image cropping.
        epsg3857_world_width = 2 * self._epsg3857_max_x

        west_rescaling_factor = 0
        if abs(bbox[0]) > self._epsg3857_max_x:
            west_rescaling_factor = ceil(
                (abs(bbox[0]) - self._epsg3857_max_x) /
                epsg3857_world_width) * copysign(1, bbox[0])

        east_rescaling_factor = 0
        if abs(bbox[1]) > self._epsg3857_max_x:
            east_rescaling_factor = ceil(
                (abs(bbox[1]) - self._epsg3857_max_x) /
                epsg3857_world_width) * copysign(1, bbox[1])

        map_row_tiles = 2**zoom - 1  # number of tiles in the Map's row for a certain zoom level

        map_worlds = int(east_rescaling_factor -
                         west_rescaling_factor)  # number maps in an image
        worlds_between = map_worlds - 1  # number of full maps in an image
        if top_left_tile.x > bottom_right_tile.x or bbox[1] - bbox[
                0] > epsg3857_world_width or map_worlds > 0:
            # BBOX crosses Slippy Map's border
            if worlds_between > 0:
                tiles_rows = (list(range(top_left_tile.x, map_row_tiles + 1)) +
                              worlds_between * list(range(map_row_tiles + 1)) +
                              list(range(bottom_right_tile.x + 1)))
            else:
                tiles_rows = list(range(top_left_tile.x,
                                        map_row_tiles + 1)) + list(
                                            range(bottom_right_tile.x + 1))
        else:
            # BBOx is contained by the Slippy Map
            if worlds_between > 0:
                tiles_rows = list(
                    range(top_left_tile.x, bottom_right_tile.x +
                          1)) + worlds_between * list(range(map_row_tiles + 1))
            else:
                tiles_rows = list(
                    range(top_left_tile.x, bottom_right_tile.x + 1))

        tiles_cols = list(range(top_left_tile.y, bottom_right_tile.y + 1))

        # if latitude boundaries extend world's height - add background's height, and set constant Y offset for tiles
        additional_height = 0
        fixed_top_offset = 0
        fixed_bottom_offset = 0

        north_extension3857 = max(0, bbox[3] - self._epsg3857_max_y)
        south_extension3857 = abs(min(0, bbox[2] + self._epsg3857_max_y))
        extension3857 = north_extension3857 + south_extension3857

        if extension3857:
            # get single tile's height in ESPG:3857
            tile_bounds = mercantile.bounds(tiles_rows[0], tiles_cols[0], zoom)
            _, south = self.point4326to3857(getattr(tile_bounds, "west"),
                                            getattr(tile_bounds, "south"))
            _, north = self.point4326to3857(getattr(tile_bounds, "west"),
                                            getattr(tile_bounds, "north"))
            tile_hight3857 = north - south

            additional_height = round(
                self.tile_size * extension3857 /
                tile_hight3857)  # based on linear proportion

            if north_extension3857:
                fixed_top_offset = round(self.tile_size * north_extension3857 /
                                         tile_hight3857)

            if south_extension3857:
                fixed_bottom_offset = round(
                    self.tile_size * south_extension3857 / tile_hight3857)

        background = Image.new(
            "RGB",
            (len(tiles_rows) * self.tile_size,
             len(tiles_cols) * self.tile_size + additional_height),
            (250, 250, 250),
        )

        for offset_x, x in enumerate(tiles_rows):
            for offset_y, y in enumerate(tiles_cols):
                if self.tms:
                    y = (2**zoom) - y - 1
                imgurl = self.url.format(x=x, y=y, z=zoom)

                im = None
                for retries in range(self.max_retries):
                    try:
                        resp, content = http_client.request(imgurl)
                        if resp.status_code > 400:
                            retries = self.max_retries - 1
                            raise Exception(f"{strip_tags(content)}")
                        im = BytesIO(content)
                        Image.open(
                            im).verify()  # verify that it is, in fact an image
                        break
                    except Exception as e:
                        logger.error(
                            f"Thumbnail background fetching from {imgurl} failed {retries} time(s) with: {e}"
                        )
                        if retries + 1 == self.max_retries:
                            raise e
                        time.sleep(self.retry_delay)
                        continue

                if im:
                    image = Image.open(
                        im
                    )  # "re-open" the file (required after running verify method)

                    # add the fetched tile to the background image, placing it under proper coordinates
                    background.paste(
                        image, (offset_x * self.tile_size,
                                offset_y * self.tile_size + fixed_top_offset))

        # get BBOX of the tiles
        top_left_bounds = mercantile.bounds(top_left_tile)
        bottom_right_bounds = mercantile.bounds(bottom_right_tile)

        tiles_bbox3857 = self.bbox4326to3857(
            getattr(top_left_bounds, "west"),
            getattr(bottom_right_bounds, "east"),
            getattr(bottom_right_bounds, "south"),
            getattr(top_left_bounds, "north"),
        )

        # rescale tiles' boundaries - if space covered by the input BBOX extends the width of the world,
        # (e.g. two "worlds" are present on the map), translation between tiles' BBOX and image's pixel requires
        # additional rescaling, for tiles' BBOX coordinates to match input BBOX coordinates
        west_coord = tiles_bbox3857[
            0] + west_rescaling_factor * epsg3857_world_width
        east_coord = tiles_bbox3857[
            1] + east_rescaling_factor * epsg3857_world_width

        # prepare translating function from received BBOX to pixel values of the background image
        src_quad = (0, fixed_top_offset, background.size[0],
                    background.size[1] - fixed_bottom_offset)
        to_src_px = utils.make_bbox_to_pixels_transf(
            [west_coord, tiles_bbox3857[2], east_coord, tiles_bbox3857[3]],
            src_quad)

        # translate received BBOX to pixel values
        minx, miny = to_src_px(bbox[0], bbox[2])
        maxx, maxy = to_src_px(bbox[1], bbox[3])

        # max and min function for Y axis were introduced to mitigate rounding errors
        crop_box = (
            ceil(minx),
            max(ceil(maxy) + fixed_top_offset, 0),
            floor(maxx),
            min(floor(miny) + fixed_top_offset, background.size[1]),
        )

        if not all([
                0 <= crop_x <= background.size[0]
                for crop_x in [crop_box[0], crop_box[2]]
        ]):
            raise ThumbnailError(
                f"Tiled background cropping error. Boundaries outside of the image: {crop_box}"
            )

        # crop background image to the desired bbox and resize it
        background = background.crop(box=crop_box)
        background = background.resize(
            (self.thumbnail_width, self.thumbnail_height))

        if sum(background.convert("L").getextrema()) in (0, 2):
            # either all black or all white
            logger.error("Thumbnail background outside the allowed area.")
            raise ThumbnailError(
                "Thumbnail background outside the allowed area.")
        return background
Esempio n. 60
0
 def _calc_tms_zoom(self, scale):
     for z in range(15,20):
         b = mercantile.bounds(0,0,z)
         if scale > math.sqrt((b.north - b.south)*(b.east - b.west) / (256*256)):
             return z