def test_tiles_truncate(): """Input is truncated""" assert list( mercantile.tiles(-181.0, 0.0, -170.0, 10.0, zooms=[2], truncate=True)) == list( mercantile.tiles(-180.0, 0.0, -170.0, 10.0, zooms=[2]))
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')
def map_interacted(event): if event["type"] == "change" and event["name"] == "bounds": self.ht.value = f"{self.tile_id}, Map zoom: {int(a_map.zoom)}" self.slider.max = int(a_map.zoom) + self._max_zoom_delta m = event["owner"] ((south, west), (north, east)) = m.bounds b_poly = list(m.bounds_polygon) b_poly += [tuple(b_poly[0])] # m += Polyline(locations=b_poly) # Attention in the order of west, south, east, north! tiles = mercantile.tiles(west, south, east, north, zooms=self.level) features = [mercantile.feature(t) for t in tiles] self.gj.data = geojson.FeatureCollection(features=features) # Ipyleaflet buglet(?): This name is updated in the GeoJSON layer, # but not in the LayersControl! self.gj.name = f"Mercator" # level {self.level}" self.gj.on_hover(hover)
def main(): parser = argparse.ArgumentParser() parser.add_argument('path', type=str) parser.add_argument('out', type=str) parser.add_argument('--zoom', type=int, required=True) parser.add_argument('--ext', type=str, default='webp') args = parser.parse_args() os.makedirs(args.out, exist_ok=True) os.makedirs(os.path.join(args.out, str(args.zoom)), exist_ok=True) meta = tiler.bounds(args.path) bounds = meta['bounds'] tiles = list(mercantile.tiles(*bounds + [[args.zoom]])) for x, y, z in tqdm(tiles, desc='Tiling', unit='tile', ascii=True): os.makedirs(os.path.join(args.out, str(z), str(x)), exist_ok=True) data, _ = tiler.tile(args.path, x, y, z) img = reshape_as_image(data) img = Image.fromarray(img, mode='RGB') img.save(os.path.join(args.out, str(z), str(x), str(y) + '.' + args.ext), optimize=True)
def __init__(self): self.lat = 43.03350 # Координаты центра карты на старте. Задал координаты университета self.lon = 131.88928 self.west = 131.8797 self.south = 43.0227 self.east = 131.9089 self.north = 43.0373 self.zoom = 16 # Масштаб карты на старте self.type = "map" # Другие значения "sat", "sat,skl" self.tiles = list(mercantile.tiles(self.west, self.south, self.east, self.north, self.zoom)) self.min_x = min([t.x for t in self.tiles]) self.min_y = min([t.y for t in self.tiles]) self.max_x = max([t.x for t in self.tiles]) self.max_y = max([t.y for t in self.tiles]) self.bounds = { "west": min([mercantile.bounds(t).west for t in self.tiles]), "east": max([mercantile.bounds(t).east for t in self.tiles]), "south": min([mercantile.bounds(t).south for t in self.tiles]), "north": max([mercantile.bounds(t).north for t in self.tiles]), }
def main(): parser = argparse.ArgumentParser() parser.add_argument('min_zoom', type=int, help='The minimum zoom level to include') parser.add_argument('max_zoom', type=int, help='The maximum zoom level to include') parser.add_argument( '--cities_url', default="https://mapzen.com/data/metro-extracts/cities-extractor.json", help='A GeoJSON URL with features to cover with tiles') parser.add_argument('--output_prefix', default="output", help='The path prefix to output coverage data to') args = parser.parse_args() cities_resp = requests.get(args.cities_url) cities_resp.raise_for_status() cities_data = cities_resp.json() for feature in cities_data: name = feature['id'] bbox = feature['bbox'] min_lon, min_lat, max_lon, max_lat = float(bbox['left']), float( bbox['bottom']), float(bbox['right']), float(bbox['top']) count = 0 with open(os.path.join(args.output_prefix, '{}.csv'.format(name)), 'w') as f: for x, y, z in mercantile.tiles( min_lon, min_lat, max_lon, max_lat, range(args.min_zoom, args.max_zoom + 1)): f.write('{}/{}/{}\n'.format(z, x, y)) count += 1 print("Wrote out {} tiles to {}".format(count, f.name))
def _get_image(west, south, east, north, zoom): tiles = list(mercantile.tiles(west, south, east, north, zoom)) tile_size = 256 min_x = min_y = max_x = max_y = None for tile in tiles: min_x = min(min_x, tile.x) if min_x is not None else tile.x min_y = min(min_y, tile.y) if min_y is not None else tile.y max_x = max(max_x, tile.x) if max_x is not None else tile.x max_y = max(max_y, tile.y) if max_y is not None else tile.y out_img = PIL.Image.new('RGB', ((max_x - min_x + 1) * tile_size, (max_y - min_y + 1) * tile_size)) lat_max = mercantile.bounds(max_x, min_y, zoom).north lat_min = mercantile.bounds(min_x, max_y, zoom).south lon_max = mercantile.bounds(max_x, max_y, zoom).east lon_min = mercantile.bounds(min_x, min_y, zoom).west results = [] cnt = 0 for tile in tiles: # print('Tile {}/{}'.format(cnt+1, len(tiles))) result = _download_tile(tile) results.append(result) cnt += 1 for img, tile in results: left = tile.x - min_x top = tile.y - min_y bounds = (left * tile_size, top * tile_size, (left + 1) * tile_size, (top + 1) * tile_size) out_img.paste(img, bounds) return out_img, lat_min, lat_max, lon_min, lon_max
def polyfill(self, input_gdf, zoom_level): check_package('mercantile', is_optional=True) import mercantile if not hasattr(input_gdf, 'geometry'): raise ValueError('This dataframe has no valid geometry.') geometry_name = input_gdf.geometry.name dfs = [] for _, row in input_gdf.iterrows(): input_geometry = row[geometry_name] bounds = input_geometry.bounds tiles = mercantile.tiles(bounds[0], bounds[1], bounds[2], bounds[3], zoom_level) new_rows = [] for tile in tiles: new_row = row.copy() new_geometry = box(*mercantile.bounds(tile)) if new_geometry.intersects(input_geometry): new_row[geometry_name] = new_geometry new_row['quadkey'] = mercantile.quadkey(tile) new_rows.append(new_row) dfs.append(DataFrame(new_rows)) df = concat(dfs).reset_index(drop=True) return GeoDataFrame(df, geometry=geometry_name, crs='epsg:4326')
def _get_tiles(src_path, nb_tiles=5): with rasterio.open(src_path) as src_dst: bounds = warp.transform_bounds( src_dst.crs, "epsg:4326", *src_dst.bounds, densify_pts=21 ) minzoom, maxzoom = mercator.get_zooms(src_dst) while True: zoom = random.randint(maxzoom - 2, maxzoom) tiles = list(mercantile.tiles(*bounds, zoom)) def _f(tile): x, y, z = tile ulx, uly = mercantile.ul(x, y, z) lrx, lry = mercantile.ul(x + 1, y + 1, zoom) return ( (bounds[0] < ulx < bounds[2]) and (bounds[1] < uly < bounds[3]) and (bounds[0] < lrx < bounds[2]) and (bounds[1] < lry < bounds[3]) ) tiles = list(filter(lambda x: _f(x), tiles)) if not tiles: continue if len(tiles) > nb_tiles: return random.sample(tiles, nb_tiles) else: return tiles
def howmany(w, s, e, n, zoom, verbose=True, ll=False): """ Number of tiles required for a given bounding box and a zoom level ... Arguments --------- w : float West edge longitude s : float South edge latitude e : float East edge longitude n : float Noth edge latitude zoom : int Level of detail verbose : Boolean [Optional. Default=True] If True, print short message with number of tiles and zoom. ll : Boolean [Optional. Default: False] If True, `w`, `s`, `e`, `n` are assumed to be lon/lat as opposed to Spherical Mercator. """ if not ll: # Convert w, s, e, n into lon/lat w, s = _sm2ll(w, s) e, n = _sm2ll(e, n) if zoom == "auto": zoom = _calculate_zoom(w, s, e, n) tiles = len(list(mt.tiles(w, s, e, n, [zoom]))) if verbose: print("Using zoom level %i, this will download %i tiles" % (zoom, tiles)) return tiles
def get_tile_list(geom, zoom=17): """Generate the Tile List for The Tasking List Parameters ---------- geom: shapely geometry of area. zoom : int Zoom Level for Tiles One or more zoom levels. Yields ------ list of tiles that intersect with """ west, south, east, north = geom.bounds tiles = mercantile.tiles(west, south, east, north, zooms=zoom) tile_list = [] for tile in tiles: tile_geom = geometry.shape(mercantile.feature(tile)['geometry']) if tile_geom.intersects(geom): tile_list.append(tile) return tile_list
def add_terrain(): twin = load_config() bbox = twin["bbox"] package = Package(".") dl = gsi.Downloader(CACHE_DIR) # TODO: Fix basemap_zoom = 18 tiles = mercantile.tiles(*bbox, basemap_zoom) # left, bottom, right, top tiles = list(tiles) spinner = Halo(text="", spinner="bouncingBar") spinner.start( "Installing {}".format(typer.style("terrain", fg=typer.colors.GREEN, bold=True)) ) filenames = dl.download_dem5a(tiles) merged = utils.geojson_merge(filenames) # print(merged.head()) merged.to_file(package.assets.joinpath("merged_dem5a.geojson"), driver="GeoJSON") extract_meshed_level(merged, outfile=package.assets.joinpath("levelmap.json")) spinner.succeed() spinner.stop()
def main(args): if not args.zoom and args.type in ["geojson", "bbox"]: sys.exit("Zoom parameter is required") cover = [] if args.type == "geojson": with open(args.input) as f: features = json.load(f) for feature in tqdm(features["features"], ascii=True, unit="feature"): cover.extend(map(tuple, burntiles.burn([feature], args.zoom).tolist())) cover = list(set(cover)) # tiles can overlap for multiple features; unique tile ids elif args.type == "bbox": west, south, east, north = map(float, args.input.split(",")) cover = tiles(west, south, east, north, args.zoom) elif args.type == "dir": cover = [tile for tile, _ in tiles_from_slippy_map(args.input)] if os.path.dirname(args.out) and not os.path.isdir(os.path.dirname(args.out)): os.makedirs(os.path.dirname(args.out), exist_ok=True) with open(args.out, "w") as fp: csv.writer(fp).writerows(cover)
def pickle_task_distributor(pickle_filepath, bucket_name, output_picklepath, zoom=3): """ tile_task_distributor(pickle_filepath, data_type, bucket_name, output_picklepath, zoom=3) ----------------------------------------------------------------------- Inputs: picke_data_path: (str) - the path of the .pickle data file bucket_name: (str) the AWS bucket name output_picklepath: (str) - the location where subsetted data are saved zoom: (int) - the zoom level to generate data 'tiles' ----------------------------------------------------------------------- Output: Invokes process_pickle.py lambda function on AWS with fully specified event ----------------------------------------------------------------------- Author: Michael Christensen Date Modified: 10/08/2018 """ pickle_data = s3.get_object(Bucket=bucket_name, Key=pickle_filepath) body_string = pickle_data['Body'].read() data = pickle.loads(body_string) lat = data['lat'] lon = data['lon'] tiles = mercantile.tiles(west=lon.min(), south=lat.min(), east=lon.max(), north=lat.max(), zooms=zoom) x, y, z = zip(*[t for t in tiles]) #break into groups of 50 group_size = 50 tile_break_points = (list(range(0, len(x), group_size))) tile_break_points.append(len(x)) # check that the event is valid format for break_indx in range(len(tile_break_points) - 1): # build payload for initiation of lambda function payload = {} payload['pickle_filepath'] = pickle_filepath payload['bucket_name'] = bucket_name payload['output_picklepath'] = output_picklepath payload['xyz_info'] = { 'start_indx': tile_break_points[break_indx], 'end_indx': tile_break_points[break_indx + 1] } payload['zoom'] = zoom # invoke process_tiles with appropriate payload try: response = lam.invoke(FunctionName='process_pickle', InvocationType='Event', Payload=json.dumps(payload)) except Exception as e: raise e
def _tile_coords(self, bounds): """ convert mercator bbox to tile index limits """ tfm = partial(pyproj.transform, pyproj.Proj(init="epsg:3857"), pyproj.Proj(init="epsg:4326")) bounds = ops.transform(tfm, box(*bounds)).bounds # because tiles have a common corner, the tiles that cover a # given tile includes the adjacent neighbors. # https://github.com/mapbox/mercantile/issues/84#issuecomment-413113791 west, south, east, north = bounds 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 params = [west, south, east, north, [self.zoom_level]] tile_coords = [(tile.x, tile.y) for tile in mercantile.tiles(*params)] xtiles, ytiles = zip(*tile_coords) minx = min(xtiles) miny = min(ytiles) maxx = max(xtiles) maxy = max(ytiles) return minx, miny, maxx, maxy
def test_tiles_roundtrip(t): """tiles(bounds(tile)) gives the tile""" res = list(mercantile.tiles(*mercantile.bounds(t), zooms=[t.z])) assert len(res) == 1 val = res.pop() assert val.x == t.x assert val.y == t.y assert val.z == t.z
def get_intersected_tiles(self): zoom_range = range(app_settings.MIN_TILE_ZOOM, app_settings.MAX_TILE_ZOOM + 1) try: return [(tile.x, tile.y, tile.z) for tile in tiles(*self.get_bounding_box(), zoom_range)] except ValueError: # TODO find why a ValueError is raised with some Point() geometries return []
def test_tiles_single_zoom(): bounds = (-105, 39.99, -104.99, 40) tiles = list(mercantile.tiles(*bounds, zooms=14)) expect = [ mercantile.Tile(x=3413, y=6202, z=14), mercantile.Tile(x=3413, y=6203, z=14), ] assert sorted(tiles) == sorted(expect)
def bbox_to_quadkeys(bbox: list, zoom: int): """ Find all quadkeys in a bbox """ tiles = mercantile.tiles(bbox[0], bbox[1], bbox[2], bbox[3], int(zoom)) quadkeys = [] for tile in tiles: quadkeys.append(mercantile.quadkey(tile)) return quadkeys
def create_tiles_gdf(bounds: List[float], quadkey_zoom: int) -> gpd.GeoDataFrame: """Create GeoDataFrame of all tiles within bounds at quadkey_zoom """ features = [ mercantile.feature(tile, props={'quadkey': mercantile.quadkey(tile)}) for tile in mercantile.tiles(*bounds, quadkey_zoom) ] return gpd.GeoDataFrame.from_features(features, crs='EPSG:4326')
def write_tile_data(): filename = args.output_filename with open(filename, "w", newline="") as data_file: writer = csv.writer(data_file) for tile in mercantile.tiles(args.minx, args.miny, args.maxx, args.maxy, range(args.minlevel, args.maxlevel)): bounds = mercantile.bounds(tile) writer.writerow(bounds)
def handle(self, *args, **kwargs): tiles = [] for service in Service.objects.all(): extent = service.full_extent.project(WGS84) tiles += [ (service.name, t) for t in mercantile.tiles(extent.xmin, extent.ymin, extent.xmax, extent.ymax, ZOOM_LEVELS, truncate=True) ] asyncio.get_event_loop().run_until_complete(self.fetch_tiles(tiles))
def worker(path): raster = rasterio_open(path) w, s, e, n = transform_bounds(raster.crs, "EPSG:4326", *raster.bounds) transform, _, _ = calculate_default_transform(raster.crs, "EPSG:3857", raster.width, raster.height, w, s, e, n) tiles = [mercantile.Tile(x=x, y=y, z=z) for x, y, z in mercantile.tiles(w, s, e, n, args.zoom)] tiled = [] for tile in tiles: try: w, s, e, n = mercantile.xy_bounds(tile) # inspired by rio-tiler, cf: https://github.com/mapbox/rio-tiler/pull/45 warp_vrt = WarpedVRT( raster, crs="epsg:3857", resampling=Resampling.bilinear, add_alpha=False, transform=from_bounds(w, s, e, n, args.ts, args.ts), width=math.ceil((e - w) / transform.a), height=math.ceil((s - n) / transform.e), ) data = warp_vrt.read( out_shape=(len(raster.indexes), args.ts, args.ts), window=warp_vrt.window(w, s, e, n) ) image = np.moveaxis(data, 0, 2) # C,H,W -> H,W,C except: sys.exit("Error: Unable to tile {} from raster {}.".format(str(tile), raster)) tile_key = (str(tile.x), str(tile.y), str(tile.z)) if not args.label and len(tiles_map[tile_key]) == 1 and is_border(image): progress.update() continue if len(tiles_map[tile_key]) > 1: out = os.path.join(splits_path, str(tiles_map[tile_key].index(path))) else: out = args.out x, y, z = map(int, tile) if not args.label: ret = tile_image_to_file(out, mercantile.Tile(x=x, y=y, z=z), image) if args.label: ret = tile_label_to_file(out, mercantile.Tile(x=x, y=y, z=z), palette, image) if not ret: sys.exit("Error: Unable to write tile {} from raster {}.".format(str(tile), raster)) if len(tiles_map[tile_key]) == 1: progress.update() tiled.append(mercantile.Tile(x=x, y=y, z=z)) return tiled
def get_tiles(zoom, input, dst_crs="EPSG:3857"): print("getting tiles for", input) input = input.replace("s3://", "/vsicurl/http://s3.amazonaws.com/") with rasterio.drivers(): with rasterio.open(input) as src: # Compute the geographic bounding box of the dataset. (west, east), (south, north) = transform( src.crs, "EPSG:4326", src.bounds[::2], src.bounds[1::2]) # Initialize an iterator over output tiles. return mercantile.tiles( west, south, east, north, range(zoom, zoom + 1))
def find_tiles(geometry, min_zoom, max_zoom): assert min_zoom <= max_zoom, 'min zoom must be <= max zoom' selected_tiles = [] bound_tiles = mercantile.tiles(*geometry.bounds, zooms=range(min_zoom, max_zoom + 1)) for tile in bound_tiles: if box(*mercantile.bounds(tile)).intersects(geometry): selected_tiles.append(tile) return selected_tiles
def _download_image(self): """ return glued tiles as PIL image :param west: west longitude in degrees :param south: south latitude in degrees :param east: east longitude in degrees :param north: north latitude in degrees :param zoom: wanted size :return: Image """ tiles = list( mercantile.tiles(self._west, self._south, self._east, self._north, self.zoom)) min_x = min_y = max_x = max_y = None for tile in tiles: min_x = min(min_x, tile.x) if min_x is not None else tile.x min_y = min(min_y, tile.y) if min_y is not None else tile.y max_x = max(max_x, tile.x) if max_x is not None else tile.x max_y = max(max_y, tile.y) if max_y is not None else tile.y out_img = PIL.Image.new('RGB', ((max_x - min_x + 1) * self.TILE_SIZE, (max_y - min_y + 1) * self.TILE_SIZE)) pool = multiprocessing.Pool(self.pool_workers) results = pool.map(self._download_tile, tiles) pool.close() pool.join() for img, tile in results: left = tile.x - min_x top = tile.y - min_y bounds = (left * self.TILE_SIZE, top * self.TILE_SIZE, (left + 1) * self.TILE_SIZE, (top + 1) * self.TILE_SIZE) out_img.paste(img, bounds) tiles_bounding = list(mercantile.xy_bounds(t) for t in tiles) min_mercator_lng = min(t.left for t in tiles_bounding) max_mercator_lng = max(t.right for t in tiles_bounding) min_mercator_lat = min(t.bottom for t in tiles_bounding) max_mercator_lat = max(t.top for t in tiles_bounding) kx = out_img.size[0] / (max_mercator_lng - min_mercator_lng) ky = out_img.size[1] / (max_mercator_lat - min_mercator_lat) self._image = out_img self._left = min_mercator_lng self._right = max_mercator_lng self._top = max_mercator_lat self._bottom = min_mercator_lat self._kx = kx self._ky = ky
def worker(path): raster = rasterio_open(path) w, s, e, n = transform_bounds(raster.crs, "EPSG:4326", *raster.bounds) tiles = [mercantile.Tile(x=x, y=y, z=z) for x, y, z in mercantile.tiles(w, s, e, n, args.zoom)] tiled = [] for tile in tiles: if cover and tile not in cover: continue w, s, e, n = mercantile.xy_bounds(tile) warp_vrt = WarpedVRT( raster, crs="epsg:3857", resampling=Resampling.bilinear, add_alpha=False, transform=from_bounds(w, s, e, n, width, height), width=width, height=height, ) data = warp_vrt.read(out_shape=(len(raster.indexes), width, height), window=warp_vrt.window(w, s, e, n)) image = np.moveaxis(data, 0, 2) # C,H,W -> H,W,C tile_key = (str(tile.x), str(tile.y), str(tile.z)) if ( not args.label and len(tiles_map[tile_key]) == 1 and is_nodata(image, args.nodata, args.nodata_threshold, args.keep_borders) ): progress.update() continue if len(tiles_map[tile_key]) > 1: out = os.path.join(splits_path, str(tiles_map[tile_key].index(path))) else: out = args.out x, y, z = map(int, tile) if not args.label: tile_image_to_file(out, mercantile.Tile(x=x, y=y, z=z), image) if args.label: tile_label_to_file(out, mercantile.Tile(x=x, y=y, z=z), palette, image) if len(tiles_map[tile_key]) == 1: progress.update() tiled.append(mercantile.Tile(x=x, y=y, z=z)) return tiled
def handle(self, *args, **options): for layer in Layer.objects.all(): if options['verbosity'] >= 1: self.stdout.write(f'Generating {layer.name} tiles cache') bbox = layer.features.aggregate(bbox=Extent('geom'))['bbox'] if bbox: vtile = VectorTile(layer) zoom_range = range( layer.layer_settings_with_default('tiles', 'minzoom'), layer.layer_settings_with_default('tiles', 'maxzoom') + 1) for tile in tiles(*bbox, zoom_range): vtile.get_tile(tile.x, tile.y, tile.z)
def _tile_coords(self, bounds): """ Convert tile coords mins/maxs to lng/lat bounds """ tfm = partial(pyproj.transform, pyproj.Proj(init="epsg:3857"), pyproj.Proj(init="epsg:4326")) bounds = ops.transform(tfm, box(*bounds)).bounds params = list(bounds) + [[self.zoom_level]] tile_coords = [(tile.x, tile.y) for tile in mercantile.tiles(*params)] xtiles, ytiles = zip(*tile_coords) minx = min(xtiles) maxx = max(xtiles) miny = min(ytiles) maxy = max(ytiles) return minx, miny, maxx, maxy
def area(config, storage, dbname, host, port, username, connections, chunk_size, min_zoom, max_zoom, bbox): '''Generates tiles for an area''' # Get the directory the config is in full_path = os.path.join(os.getcwd(), config) root_path = os.path.dirname(full_path) config_path = os.path.relpath(full_path, root_path) filesystem = fs.osfs.OSFS(root_path) config = tilekiln.config.Config( filesystem.open(config_path).read(), filesystem) dbinfo = { "dbname": dbname, "host": host, "port": port, "username": username } min_zoom = min_zoom or config.minzoom max_zoom = max_zoom or config.maxzoom zoom_levels = [z for z in range(min_zoom, max_zoom + 1)] bounding_box = tuple(map(float, bbox.split(','))) if len(bounding_box) != 4: raise ValueError(f'Provided bounding box: "{bbox}" is invalid.' + ' It should have 4 elements separated by commas.') if any([ bounding_box[0] < -180, bounding_box[0] > 180, bounding_box[2] < -180, bounding_box[2] > 180, ]): raise ValueError( 'Longitude cannot be lower than -180 or higher than 180.') if any([ bounding_box[1] < -90, bounding_box[1] > 90, bounding_box[3] < -90, bounding_box[3] > 90 ]): raise ValueError( 'Latitude cannot be lower than -90 or higher than 90.') tiles = [(tile.z, tile.x, tile.y) for tile in mercantile.tiles(*bounding_box, zoom_levels)] # Apply some heuristics to guess a chunk size if chunk_size is None: chunk_size = int(min(max(len(tiles) / (2 * connections), 10), 50000)) kiln = tilekiln.kiln.Kiln(config, dbinfo, storage) kiln.generate_tiles(tiles, connections, chunk_size)
def calculate_zoom(self): # maximum number of needed tiles for thumbnail of given width and height max_tiles = (ceil(self.thumbnail_width / self.tile_size) + 1) * (ceil(self.thumbnail_height / self.tile_size) + 1) # zoom for which there are less needed tiles than max_tiles zoom = 0 for z in range(1, 16): if len(list(mercantile.tiles(*self._mercantile_bbox, z))) > max_tiles: break else: zoom = max(zoom, z) return zoom
def seed_inputs(bounds, zooms): for tile in mercantile.tiles(*bounds, zooms=zooms): s = tile_url(strava, tile), tile_path(os.path.join(tiledir, 'strava'), tile, 'png') v = tile_url(osm_vect, tile), tile_path(os.path.join(tiledir, 'osm'), tile, 'json') save_tile(*s) save_tile(*v)
def mbtiles(ctx, files, output, overwrite, title, description, layer_type, img_format, tile_size, zoom_levels, image_dump, num_workers, src_nodata, dst_nodata, resampling, rgba): """Export a dataset to MBTiles (version 1.1) in a SQLite file. The input dataset may have any coordinate reference system. It must have at least three bands, which will be become the red, blue, and green bands of the output image tiles. An optional fourth alpha band may be copied to the output tiles by using the --rgba option in combination with the PNG format. This option requires that the input dataset has at least 4 bands. If no zoom levels are specified, the defaults are the zoom levels nearest to the one at which one tile may contain the entire source dataset. If a title or description for the output file are not provided, they will be taken from the input dataset's filename. This command is suited for small to medium (~1 GB) sized sources. Python package: rio-mbtiles (https://github.com/mapbox/rio-mbtiles). """ output, files = resolve_inout(files=files, output=output, overwrite=overwrite) inputfile = files[0] log = logging.getLogger(__name__) with ctx.obj['env']: # Read metadata from the source dataset. with rasterio.open(inputfile) as src: validate_nodata(dst_nodata, src_nodata, src.profile.get('nodata')) base_kwds = {'dst_nodata': dst_nodata, 'src_nodata': src_nodata} if src_nodata is not None: base_kwds.update(nodata=src_nodata) if dst_nodata is not None: base_kwds.update(nodata=dst_nodata) # Name and description. title = title or os.path.basename(src.name) description = description or src.name # Compute the geographic bounding box of the dataset. (west, east), (south, north) = transform( src.crs, 'EPSG:4326', src.bounds[::2], src.bounds[1::2]) # Resolve the minimum and maximum zoom levels for export. if zoom_levels: minzoom, maxzoom = map(int, zoom_levels.split('..')) else: zw = int(round(math.log(360.0 / (east - west), 2.0))) zh = int(round(math.log(170.1022 / (north - south), 2.0))) minzoom = min(zw, zh) maxzoom = max(zw, zh) log.debug("Zoom range: %d..%d", minzoom, maxzoom) if rgba: if img_format == 'JPEG': raise click.BadParameter("RGBA output is not possible with JPEG format.") else: count = 4 else: count = 3 # Parameters for creation of tile images. base_kwds.update({ 'driver': img_format.upper(), 'dtype': 'uint8', 'nodata': 0, 'height': tile_size, 'width': tile_size, 'count': count, 'crs': TILES_CRS}) img_ext = 'jpg' if img_format.lower() == 'jpeg' else 'png' # Initialize the sqlite db. if os.path.exists(output): os.unlink(output) # workaround for bug here: https://bugs.python.org/issue27126 sqlite3.connect(':memory:').close() conn = sqlite3.connect(output) cur = conn.cursor() cur.execute( "CREATE TABLE tiles " "(zoom_level integer, tile_column integer, " "tile_row integer, tile_data blob);") cur.execute( "CREATE TABLE metadata (name text, value text);") # Insert mbtiles metadata into db. cur.execute( "INSERT INTO metadata (name, value) VALUES (?, ?);", ("name", title)) cur.execute( "INSERT INTO metadata (name, value) VALUES (?, ?);", ("type", layer_type)) cur.execute( "INSERT INTO metadata (name, value) VALUES (?, ?);", ("version", "1.1")) cur.execute( "INSERT INTO metadata (name, value) VALUES (?, ?);", ("description", description)) cur.execute( "INSERT INTO metadata (name, value) VALUES (?, ?);", ("format", img_ext)) cur.execute( "INSERT INTO metadata (name, value) VALUES (?, ?);", ("bounds", "%f,%f,%f,%f" % (west, south, east, north))) conn.commit() # Create a pool of workers to process tile tasks. pool = Pool(num_workers, init_worker, (inputfile, base_kwds, resampling), 100) # Constrain bounds. EPS = 1.0e-10 west = max(-180 + EPS, west) south = max(-85.051129, south) east = min(180 - EPS, east) north = min(85.051129, north) # Initialize iterator over output tiles. tiles = mercantile.tiles( west, south, east, north, range(minzoom, maxzoom + 1)) for tile, contents in pool.imap_unordered(process_tile, tiles): if contents is None: log.info("Tile %r is empty and will be skipped", tile) continue # MBTiles have a different origin than Mercantile/tilebelt. tiley = int(math.pow(2, tile.z)) - tile.y - 1 # Optional image dump. if image_dump: img_name = '%d-%d-%d.%s' % ( tile.x, tiley, tile.z, img_ext) img_path = os.path.join(image_dump, img_name) with open(img_path, 'wb') as img: img.write(contents) # Insert tile into db. cur.execute( "INSERT INTO tiles " "(zoom_level, tile_column, tile_row, tile_data) " "VALUES (?, ?, ?, ?);", (tile.z, tile.x, tiley, sqlite3.Binary(contents))) conn.commit() conn.close()
def tiles(ctx, zoom, input, 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] array. 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 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 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) output = json.dumps(vals) if seq: click.echo(u'\x1e') click.echo(output)
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 mbtiles(ctx, files, output_opt, title, description, layer_type, img_format, zoom_levels, image_dump, num_workers): """Export a dataset to MBTiles (version 1.1) in a SQLite file. The input dataset may have any coordinate reference system. It must have at least three bands, which will be become the red, blue, and green bands of the output image tiles. If no zoom levels are specified, the defaults are the zoom levels nearest to the one at which one tile may contain the entire source dataset. If a title or description for the output file are not provided, they will be taken from the input dataset's filename. This command is suited for small to medium (~1 GB) sized sources. Python package: rio-mbtiles (https://github.com/mapbox/rio-mbtiles). """ verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1 logger = logging.getLogger('rio') output, files = resolve_inout(files=files, output=output_opt) inputfile = files[0] with rasterio.drivers(CPL_DEBUG=verbosity > 2): # Read metadata from the source dataset. with rasterio.open(inputfile) as src: # Name and description. title = title or os.path.basename(src.name) description = description or src.name # Compute the geographic bounding box of the dataset. (west, east), (south, north) = transform( src.crs, 'EPSG:4326', src.bounds[::2], src.bounds[1::2]) # Resolve the minimum and maximum zoom levels for export. if zoom_levels: minzoom, maxzoom = map(int, zoom_levels.split('..')) else: zw = int(round(math.log(360.0/(east-west), 2.0))) zh = int(round(math.log(170.1022/(north-south), 2.0))) minzoom = min(zw, zh) maxzoom = max(zw, zh) logger.debug("Zoom range: %d..%d", minzoom, maxzoom) # Parameters for creation of tile images. base_kwds = { 'driver': img_format.upper(), 'dtype': 'uint8', 'nodata': 0, 'height': 256, 'width': 256, 'count': 3, 'crs': 'EPSG:3857'} img_ext = 'jpg' if img_format.lower() == 'jpeg' else 'png' # Initialize the sqlite db. if os.path.exists(output): os.unlink(output) conn = sqlite3.connect(output) cur = conn.cursor() cur.execute( "CREATE TABLE tiles " "(zoom_level integer, tile_column integer, " "tile_row integer, tile_data blob);") cur.execute( "CREATE TABLE metadata (name text, value text);") # Insert mbtiles metadata into db. cur.execute( "INSERT INTO metadata (name, value) VALUES (?, ?);", ("name", title)) cur.execute( "INSERT INTO metadata (name, value) VALUES (?, ?);", ("type", layer_type)) cur.execute( "INSERT INTO metadata (name, value) VALUES (?, ?);", ("version", "1.1")) cur.execute( "INSERT INTO metadata (name, value) VALUES (?, ?);", ("description", description)) cur.execute( "INSERT INTO metadata (name, value) VALUES (?, ?);", ("format", img_ext)) cur.execute( "INSERT INTO metadata (name, value) VALUES (?, ?);", ("bounds", "%f,%f,%f,%f" % (west, south, east, north))) conn.commit() # Create a pool of workers to process tile tasks. pool = Pool(num_workers, init_worker, (inputfile, base_kwds), 100) # Constrain bounds. EPS = 1.0e-10 west = max(-180+EPS, west) south = max(-85.051129, south) east = min(180-EPS, east) north = min(85.051129, north) # Initialize iterator over output tiles. tiles = mercantile.tiles( west, south, east, north, range(minzoom, maxzoom+1)) for tile, contents in pool.imap_unordered(process_tile, tiles): # MBTiles has a different origin than Mercantile/tilebelt. tiley = int(math.pow(2, tile.z)) - tile.y - 1 # Optional image dump. if image_dump: img_name = '%d-%d-%d.%s' % ( tile.x, tiley, tile.z, img_ext) img_path = os.path.join(image_dump, img_name) with open(img_path, 'wb') as img: img.write(contents) # Insert tile into db. cur.execute( "INSERT INTO tiles " "(zoom_level, tile_column, tile_row, tile_data) " "VALUES (?, ?, ?, ?);", (tile.z, tile.x, tiley, buffer(contents))) conn.commit() conn.close()
def test_tiles_single_zoom(): bounds = (-105, 39.99, -104.99, 40) tiles = list(mercantile.tiles(*bounds, zooms=14)) expect = [mercantile.Tile(x=3413, y=6202, z=14), mercantile.Tile(x=3413, y=6203, z=14)] assert sorted(tiles) == sorted(expect)
def test_tiles_truncate(): """Input is truncated""" assert list(mercantile.tiles(-181.0, 0.0, -170.0, 10.0, zooms=[2], truncate=True)) \ == list(mercantile.tiles(-180.0, 0.0, -170.0, 10.0, zooms=[2]))
def test_tiles_antimerdian_crossing_bbox(): """Antimeridian-crossing bounding boxes are handled""" bounds = (175.0, 5.0, -175.0, 10.0) assert len(list(mercantile.tiles(*bounds, zooms=[2]))) == 2
def test_global(): assert len(list(mercantile.tiles(-180, -90, 180, 90, [1], truncate=True))) == 4
def test_global_tiles_clamped(): """Y is clamped to (0, 2 ** zoom - 1)""" tiles = list(mercantile.tiles(-180, -90, 180, 90, [1])) assert len(tiles) == 4 assert min(t.y for t in tiles) == 0 assert max(t.y for t in tiles) == 1
def seed_outputs(bounds, zooms): for tile in mercantile.tiles(*bounds, zooms=zooms): print(tile) render_offroad_tile(tile)