def tileid_from_bbox(self, bbox: geo.LatLonBBox, tile_scale: int = 1) -> List[TileID]: """ Gets the tile_ids given a bounding box. Args: bbox (tuple): (left, upper, right, bottom) mercantile compatible bounding box. tile_scale (int, optional): Essentially how much zoom to add. +ve numbers zoom in, -ve zoom out. 0 treated as 1: no zoom change. Defaults to 1. Returns: List[TileID]: TileIDs covering the box. """ tile_ids = [] mt = mercantile.bounding_tile(*bbox) tm = TileID(z=mt.z, x=mt.x, y=mt.y) if tile_scale < 0: start_z = tm.z end_z = tm.z + tile_scale for _ in range(start_z, end_z, -1): tm = tm.parent() if tm.z == 0: break tile_ids = [tm] elif tile_scale in (0, 1): tile_ids = [tm] else: tile_ids = [tm] start_z = tm.z end_z = tm.z + tile_scale for _ in range(start_z, min(end_z, self.max_zoom)): new_ids = [] for t in tile_ids: new_ids += t.children() tile_ids = new_ids return [TileID(z=m.z, x=m.x, y=m.y) for m in tile_ids]
def load_files(self, *files: List[Path], verbose: bool = False): files = [Path(file) for file in files] if verbose: from tqdm import tqdm files = tqdm(files, desc="RoadEngine Load") for file in files: if not file.exists(): raise FileNotFoundError(f"Could not load {file.absolute()}!") gdf = gpd.read_file(file.absolute()) self.cache = self.cache.append(gdf, ignore_index=True, sort=False) tileset = set() geometry = gdf['geometry'] if verbose: geometry = tqdm(geometry, desc="RoadEngine Quadtree") for geom in geometry: if type(geom) != LineString: continue root = bounding_tile(*geom.bounds) tileset |= {root} self.cached_tiles |= set(simplify(*tileset)) if verbose: logger.info("RoadEngine is building an R-Tree...") self.cache.sindex if verbose: logger.info("RoadEngine R-Tree done!")
def get_sources(self, bounds, resolution): bounds, bounds_crs = bounds zoom = get_zoom(max(resolution)) self._log.info("Resolution: %s; equivalent zoom: %d", resolution, zoom) left, bottom, right, top = warp.transform_bounds( bounds_crs, WGS84_CRS, *bounds) # account for rounding errors when converting between tiles and coords left += 0.000001 bottom += 0.000001 right -= 0.000001 top -= 0.000001 if (self._bounds[0] <= left <= self._bounds[2] or self._bounds[0] <= right <= self._bounds[2]) and ( self._bounds[1] <= bottom <= self._bounds[3] or self._bounds[1] <= top <= self._bounds[3]) and ( self._minzoom <= zoom <= self._maxzoom): tile = mercantile.bounding_tile(left, bottom, right, top) with requests.get( self.endpoint.format(x=tile.x, y=tile.y, z=tile.z)) as rsp: if not rsp: self._log.warn("%s failed: %s", rsp.url, rsp.text) return for source in rsp.json(): yield Source(**source)
def zoom_level_from_geometry(geometry, splits=4): """Generate optimum zoom level for geometry. Notes ----- The obvious solution would be >>> mercantile.bounding_tile(*geometry.get_shape(WGS84_CRS).bounds).z However, if the geometry is split between two or four tiles, the resulting zoom level might be too big. """ # This import is here to avoid cyclic references from telluric.vectors import generate_tile_coordinates # We split the geometry and compute the zoom level for each chunk levels = [] for chunk in generate_tile_coordinates(geometry, (splits, splits)): levels.append( mercantile.bounding_tile(*chunk.get_shape(WGS84_CRS).bounds).z) # We now return the median value using the median_low function, which # always picks the result from the list return median_low(levels)
def tileserver_optimized_raster(src, dest): """ This method converts a raster to a tileserver optimized raster. The method will reproject the raster to align to the xyz system, in resolution and projection It will also create overviews And finally it will arragne the raster in a cog way. You could take the dest file upload it to a web server that supports ranges and user GeoRaster.get_tile on it, You are geranteed that you will get as minimal data as possible """ src_raster = tl.GeoRaster2.open(src) bounding_box = src_raster.footprint().get_shape( tl.constants.WGS84_CRS).bounds tile = mercantile.bounding_tile(*bounding_box) dest_resolution = mercator_upper_zoom_level(src_raster) bounds = tl.GeoVector.from_xyz(tile.x, tile.y, tile.z).get_bounds( tl.constants.WEB_MERCATOR_CRS) create_options = { "tiled": "YES", "blocksize": 256, "compress": "DEFLATE", "photometric": "MINISBLACK" } with TemporaryDirectory() as temp_dir: temp_file = os.path.join(temp_dir, 'temp.tif') warp(src, temp_file, dst_crs=tl.constants.WEB_MERCATOR_CRS, resolution=dest_resolution, dst_bounds=bounds, create_options=create_options) with rasterio.Env(GDAL_TIFF_INTERNAL_MASK=True, GDAL_TIFF_OVR_BLOCKSIZE=256): resampling = rasterio.enums.Resampling.gauss with rasterio.open(temp_file, 'r+') as tmp_raster: factors = _calc_overviews_factors(tmp_raster) tmp_raster.build_overviews(factors, resampling=resampling) tmp_raster.update_tags(ns='rio_overview', resampling=resampling.name) telluric_tags = _get_telluric_tags(src) if telluric_tags: tmp_raster.update_tags(**telluric_tags) rasterio_sh.copy(temp_file, dest, COPY_SRC_OVERVIEWS=True, tiled=True, compress='DEFLATE', photometric='MINISBLACK')
def get_bounding_tile(self, lon_lat_h, C_n_v): """ Calculates the corner points of the image in WGS-84, and returns the WMS tile z/x/y that completely bounds the image :param lon_lat_h: [3,] Position of the camera: Lon, Lat (deg), \ height (m) :param C_n_v: [3x3] np.ndarray that translates a vector in the vehicle frame into the local-level NED frame :return: mercantile.Tile that completely bounds the projected image """ cwgs = self.project_corners(lon_lat_h, C_n_v) b_tile = mercantile.bounding_tile( cwgs.min(0)[0], cwgs.min(0)[1], cwgs.max(0)[0], cwgs.max(0)[1]) return b_tile
def build_tile_dict(geom): # use bounds to find the smallest tile that completely contains our input aoi # not useful for AOIs that cross lat or lon 0 (returns tile [0, 0, 0]) # but helpful for many AOIs # https://github.com/mapbox/mercantile/blob/master/docs/cli.rst#bounding-tile bbox = geom.bounds bounding_tile = mercantile.bounding_tile(*bbox) # divide tiles into within and intersecting lists within_list, intersect_list = process_tile([bounding_tile], geom) # initialize tile: proportion covered dict, starting with within # tiles, all of which have a coverage proportion of 1 tile_dict = dict([(x, 1) for x in within_list]) for t in intersect_list: # do intersection of intersecting tile and original AOI geom intersect_area = get_intersect_area(geom, t) # divide intersect area by area of all z12 tiles in webmerc tile_dict[t] = intersect_area / 9572.547449763457 return tile_dict
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_bounding_tile_pt(): """A point is a valid input""" assert mercantile.bounding_tile(-91.5, 1.0).z == 28
def test_bounding_tile(): assert mercantile.bounding_tile(-92.5, 0.5, -90.5, 1.5) == (31, 63, 7) assert mercantile.bounding_tile(-90.5, 0.5, -89.5, 0.5) == (0, 0, 1) assert mercantile.bounding_tile(-92, 0, -88, 2) == (0, 0, 0)
def test_overflow_bounding_tile(): assert mercantile.bounding_tile( -179.99999999999997, -90.00000000000003, 180.00000000000014, -63.27066048950458) == (0, 0, 0)
def tiles(ctx, zoom, input, bounding_tile, with_bounds, seq, x_json_seq): """Lists Web Mercator tiles at ZOOM level intersecting GeoJSON [west, south, east, north] bounding boxen, features, or collections read from stdin. Output is a JSON [x, y, z [, west, south, east, north -- optional]] array. Example: $ echo "[-105.05, 39.95, -105, 40]" | mercantile tiles 12 Output: [852, 1550, 12] [852, 1551, 12] [853, 1550, 12] [853, 1551, 12] """ verbosity = ctx.obj['verbosity'] logger = logging.getLogger('mercantile') try: src = click.open_file(input).readlines() except IOError: src = [input] src = iter(src) first_line = next(src) # If input is RS-delimited JSON sequence. if first_line.startswith(u'\x1e'): def feature_gen(): buffer = first_line.strip(u'\x1e') for line in src: if line.startswith(u'\x1e'): if buffer: yield json.loads(buffer) buffer = line.strip(u'\x1e') else: buffer += line else: yield json.loads(buffer) else: def feature_gen(): yield json.loads(first_line) for line in src: yield json.loads(line) try: source = feature_gen() # Detect the input format for obj in source: if isinstance(obj, list): bbox = obj if len(bbox) == 2: bbox += bbox if len(bbox) != 4: raise ValueError("Invalid input.") elif isinstance(obj, dict): if 'bbox' in obj: bbox = obj['bbox'] else: box_xs = [] box_ys = [] for feat in obj.get('features', [obj]): lngs, lats = zip(*list(coords(feat))) box_xs.extend([min(lngs), max(lngs)]) box_ys.extend([min(lats), max(lats)]) bbox = min(box_xs), min(box_ys), max(box_xs), max(box_ys) west, south, east, north = bbox if bounding_tile: vals = mercantile.bounding_tile( west, south, east, north, truncate=False) output = json.dumps(vals) if seq: click.echo(u'\x1e') click.echo(output) else: # shrink the bounds a small amount so that # shapes/tiles round trip. epsilon = 1.0e-10 west += epsilon south += epsilon east -= epsilon north -= epsilon for tile in mercantile.tiles( west, south, east, north, [zoom], truncate=False): vals = (tile.x, tile.y, zoom) if with_bounds: vals += mercantile.bounds(tile.x, tile.y, zoom) output = json.dumps(vals) if seq: click.echo(u'\x1e') click.echo(output) sys.exit(0) except Exception: logger.exception("Failed. Exception caught") sys.exit(1)
def bounding_tile(ctx, input, seq): """Print the Web Mercator tile at ZOOM level bounding GeoJSON [west, south, east, north] bounding boxes, features, or collections read from stdin. 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). Example: $ echo "[-105.05, 39.95, -105, 40]" | mercantile bounding-tile Output: [426, 775, 11] """ src = iter(normalize_input(input)) first_line = next(src) # If input is RS-delimited JSON sequence. if first_line.startswith(u'\x1e'): def feature_gen(): buffer = first_line.strip(u'\x1e') for line in src: if line.startswith(u'\x1e'): if buffer: yield json.loads(buffer) buffer = line.strip(u'\x1e') else: buffer += line else: yield json.loads(buffer) else: def feature_gen(): yield json.loads(first_line) for line in src: yield json.loads(line) source = feature_gen() # Detect the input format for obj in source: if isinstance(obj, list): bbox = obj if len(bbox) == 2: bbox += bbox if len(bbox) != 4: raise click.BadParameter( "{0}".format(bbox), param=input, param_hint='input') elif isinstance(obj, dict): if 'bbox' in obj: bbox = obj['bbox'] else: box_xs = [] box_ys = [] for feat in obj.get('features', [obj]): lngs, lats = zip(*list(coords(feat))) box_xs.extend([min(lngs), max(lngs)]) box_ys.extend([min(lats), max(lats)]) bbox = min(box_xs), min(box_ys), max(box_xs), max(box_ys) west, south, east, north = bbox vals = mercantile.bounding_tile( west, south, east, north, truncate=False) output = json.dumps(vals) if seq: click.echo(u'\x1e') click.echo(output)
def bounding_tile(ctx, input, seq): """Print the Web Mercator tile at ZOOM level bounding GeoJSON [west, south, east, north] bounding boxes, features, or collections read from stdin. 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). Example: \b echo "[-105.05, 39.95, -105, 40]" | mercantile bounding-tile [426, 775, 11] """ src = iter(normalize_input(input)) first_line = next(src) # If input is RS-delimited JSON sequence. if first_line.startswith(RS): def feature_gen(): buffer = first_line.strip(RS) for line in src: if line.startswith(RS): if buffer: yield json.loads(buffer) buffer = line.strip(RS) else: buffer += line else: yield json.loads(buffer) else: def feature_gen(): yield json.loads(first_line) for line in src: yield json.loads(line) for obj in feature_gen(): if isinstance(obj, list): bbox = obj if len(bbox) == 2: bbox += bbox elif len(bbox) != 4: raise click.BadParameter("{0}".format(bbox), param=input, param_hint="input") elif isinstance(obj, dict): if "bbox" in obj: bbox = obj["bbox"] else: bbox = mercantile.geojson_bounds(obj) west, south, east, north = bbox vals = mercantile.bounding_tile(west, south, east, north, truncate=False) output = json.dumps(vals) if seq: click.echo(RS) click.echo(output)
def test_bounding_tile(bounds, tile): assert mercantile.bounding_tile(*bounds) == mercantile.Tile(*tile)
def thumbnail_url(rec): bounds = shape(rec['geometry']).bounds tile = mercantile.bounding_tile(*bounds) return tms_template_url(rec).format(z=tile.z, x=tile.x, y=tile.y)
def tiles(ctx, zoom, input, bounding_tile, with_bounds, seq, x_json_seq): """Lists Web Mercator tiles at ZOOM level intersecting GeoJSON [west, south, east, north] bounding boxen, features, or collections read from stdin. Output is a JSON [x, y, z [, west, south, east, north -- optional]] array. Example: $ echo "[-105.05, 39.95, -105, 40]" | mercantile tiles 12 Output: [852, 1550, 12] [852, 1551, 12] [853, 1550, 12] [853, 1551, 12] """ src = iter(normalize_input(input)) first_line = next(src) # If input is RS-delimited JSON sequence. if first_line.startswith(u'\x1e'): def feature_gen(): buffer = first_line.strip(u'\x1e') for line in src: if line.startswith(u'\x1e'): if buffer: yield json.loads(buffer) buffer = line.strip(u'\x1e') else: buffer += line else: yield json.loads(buffer) else: def feature_gen(): yield json.loads(first_line) for line in src: yield json.loads(line) source = feature_gen() # Detect the input format for obj in source: if isinstance(obj, list): bbox = obj if len(bbox) == 2: bbox += bbox if len(bbox) != 4: raise click.BadParameter("{0}".format(bbox), param=input, param_hint='input') elif isinstance(obj, dict): if 'bbox' in obj: bbox = obj['bbox'] else: box_xs = [] box_ys = [] for feat in obj.get('features', [obj]): lngs, lats = zip(*list(coords(feat))) box_xs.extend([min(lngs), max(lngs)]) box_ys.extend([min(lats), max(lats)]) bbox = min(box_xs), min(box_ys), max(box_xs), max(box_ys) west, south, east, north = bbox if bounding_tile: vals = mercantile.bounding_tile(west, south, east, north, truncate=False) output = json.dumps(vals) if seq: click.echo(u'\x1e') click.echo(output) else: epsilon = 1.0e-10 if east != west and north != south: # 2D bbox # shrink the bounds a small amount so that # shapes/tiles round trip. west += epsilon south += epsilon east -= epsilon north -= epsilon for tile in mercantile.tiles(west, south, east, north, [zoom], truncate=False): vals = (tile.x, tile.y, zoom) if with_bounds: vals += mercantile.bounds(tile.x, tile.y, zoom) output = json.dumps(vals) if seq: click.echo(u'\x1e') click.echo(output)
def test_bounding_tile_roundtrip(t): """bounding_tile(bounds(tile)) gives the tile""" val = mercantile.bounding_tile(*mercantile.bounds(t)) assert val.x == t.x assert val.y == t.y assert val.z == t.z
def tiles(ctx, zoom, input, bounding_tile, with_bounds, seq, x_json_seq): """Lists Web Mercator tiles at ZOOM level intersecting GeoJSON [west, south, east, north] bounding boxen, features, or collections read from stdin. Output is a JSON [x, y, z [, west, south, east, north -- optional]] array. Example: $ echo "[-105.05, 39.95, -105, 40]" | mercantile tiles 12 Output: [852, 1550, 12] [852, 1551, 12] [853, 1550, 12] [853, 1551, 12] """ src = iter(normalize_input(input)) first_line = next(src) # If input is RS-delimited JSON sequence. if first_line.startswith(u'\x1e'): def feature_gen(): buffer = first_line.strip(u'\x1e') for line in src: if line.startswith(u'\x1e'): if buffer: yield json.loads(buffer) buffer = line.strip(u'\x1e') else: buffer += line else: yield json.loads(buffer) else: def feature_gen(): yield json.loads(first_line) for line in src: yield json.loads(line) source = feature_gen() # Detect the input format for obj in source: if isinstance(obj, list): bbox = obj if len(bbox) == 2: bbox += bbox if len(bbox) != 4: raise click.BadParameter( "{0}".format(bbox), param=input, param_hint='input') elif isinstance(obj, dict): if 'bbox' in obj: bbox = obj['bbox'] else: box_xs = [] box_ys = [] for feat in obj.get('features', [obj]): lngs, lats = zip(*list(coords(feat))) box_xs.extend([min(lngs), max(lngs)]) box_ys.extend([min(lats), max(lats)]) bbox = min(box_xs), min(box_ys), max(box_xs), max(box_ys) west, south, east, north = bbox if bounding_tile: vals = mercantile.bounding_tile( west, south, east, north, truncate=False) output = json.dumps(vals) if seq: click.echo(u'\x1e') click.echo(output) else: epsilon = 1.0e-10 if east != west and north != south: # 2D bbox # shrink the bounds a small amount so that # shapes/tiles round trip. west += epsilon south += epsilon east -= epsilon north -= epsilon for tile in mercantile.tiles( west, south, east, north, [zoom], truncate=False): vals = (tile.x, tile.y, zoom) if with_bounds: vals += mercantile.bounds(tile.x, tile.y, zoom) output = json.dumps(vals) if seq: click.echo(u'\x1e') click.echo(output)
def test_bounding_tile_truncate(): """Input is truncated""" assert mercantile.bounding_tile(-181.0, 1.0, truncate=True) == mercantile.bounding_tile( -180.0, 1.0)
def test_overflow_bounding_tile(): assert mercantile.bounding_tile(-179.99999999999997, -90.00000000000003, 180.00000000000014, -63.27066048950458) == (0, 0, 0)
def test_bounding_tile_truncate(): """Input is truncated""" assert mercantile.bounding_tile(-181.0, 1.0, truncate=True) \ == mercantile.bounding_tile(-180.0, 1.0)
def test_geo_bounding_tile(self): gr = self.raster_for_test() gv = gr.footprint().reproject({'init': 'epsg:4326'}) bounding_tile = mercantile.bounding_tile(*gv.get_shape(gv.crs).bounds) self.assertEqual(bounding_tile, (2319, 1578, 12))
def provide_legend(idx, field_name): # Extract out special extent parameter that is independent from hash extent = None params = request.args.get("params") if params and params != "{params}": params = json.loads(params) extent = params.get("extent") zoom = params.get("zoom") if (zoom is None) and extent: zoom = mercantile.bounding_tile( max(-180.0, extent["minLon"]), max(-90.0, extent["minLat"]), max(180.0, extent["maxLon"]), max(90.0, extent["maxLat"]), ).z - 1 elif (zoom is None) and not extent: return legend_response("[]", "no zoom") zoom = int(zoom) # Get hash and parameters try: parameter_hash, params = extract_parameters(request) except Exception as e: current_app.logger.exception("Error while extracting parameters") return legend_response("[]", e) cache_dir = Path(current_app.config["CACHE_DIRECTORY"]) params = merge_generated_parameters(params, idx, parameter_hash) # Assign param value to legacy keyword values geopoint_field = params["geopoint_field"] category_type = params["category_type"] category_histogram = params["category_histogram"] category_format = params["category_format"] cmap = params["cmap"] histogram_interval = params.get("generated_params", {}).get( "histogram_interval", None ) field_min = params.get("generated_params", {}).get( "field_min", None ) field_max = params.get("generated_params", {}).get( "field_max", None ) # If not in category mode, just return nothing if params["category_field"] is None: return legend_response("[]", parameter_hash=parameter_hash, params=params) cmap = params["cmap"] category_field = params["category_field"] geopoint_field = params["geopoint_field"] base_s = get_search_base(current_app.config.get("ELASTIC"), params, idx) if extent: legend_bbox = { "top_left": { "lat": min(90.0, extent["maxLat"]), "lon": max(-180.0, extent["minLon"]), }, "bottom_right": { "lat": max(-90.0, extent["minLat"]), "lon": min(180.0, extent["maxLon"]), }, } current_app.logger.info("legend_bbox: %s", legend_bbox) base_s = base_s.filter("geo_bounding_box", **{geopoint_field: legend_bbox}) legend_s = copy.copy(base_s) legend_s = legend_s.params(size=0) legend = {} if histogram_interval is not None and category_histogram in (True, None): # Put in the histogram search legend_s.aggs.bucket( "categories", "histogram", field=category_field, interval=histogram_interval, min_doc_count=1, ) # Perform the execution response = legend_s.execute() # If no categories then return blank list if not hasattr(response.aggregations, "categories"): return legend_response("[]", parameter_hash=parameter_hash, params=params) # Generate the legend list for category in response.aggregations.categories: # Bin the data raw = float(category.key) # Format with pynumeral if provided if category_format: label = "%s-%s" % ( pynumeral.format(raw, category_format), pynumeral.format(raw + histogram_interval, category_format), ) else: label = "%s-%s" % (raw, raw + histogram_interval) legend[label] = category.doc_count elif category_field: tiles_iter = mercantile.tiles( max(-180.0, extent["minLon"]), max(-90.0, extent["minLat"]), min(180.0, extent["maxLon"]), min(90.0, extent["maxLat"]), zoom ) for tile in tiles_iter: #Query the database to get the categories for this tile _, tile_legend = get_tile_categories( base_s, tile.x, tile.y, tile.z, geopoint_field, category_field, int(current_app.config["MAX_LEGEND_ITEMS_PER_TILE"]), ) for k, v in tile_legend.items(): if category_type == "number": try: k = pynumeral.format(to_32bit_float(k), category_format) except ValueError: k = str(k) else: k = str(k) legend[k] = legend.get(k, 0) + v color_key_legend = [] if not legend: return legend_response("[]", parameter_hash=parameter_hash, params=params) else: # Extract other to put it at the end other = legend.pop("Other", None) for k, count in sorted(legend.items(), key=lambda x: x[1], reverse=True): c = create_color_key([k], cmap=cmap, field_min=field_min, field_max=field_max, histogram_interval=histogram_interval).get( str(k), "#000000" ) color_key_legend.append({"key": k, "color": c, "count": count}) # Add Other to the end if other: k = "Other" count = other if not params.get("ellipses"): c = create_color_key([k], cmap=cmap, field_min=field_min, field_max=field_max, histogram_interval=histogram_interval).get( str(k), "#000000" ) else: # In ellipse mode everything gets it's own color # so there is never a color for Other c = None color_key_legend.append({"key": k, "color": c, "count": count}) return legend_response(json.dumps(color_key_legend), parameter_hash=parameter_hash, params=params)
def test_geo_bounding_tile(self): gr = self.read_only_virtual_geo_raster() gv = gr.footprint().reproject(CRS({'init': 'epsg:4326'})) bounding_tile = mercantile.bounding_tile(*gv.get_shape(gv.crs).bounds) self.assertEqual(bounding_tile, (37108, 25248, 16))