tiling#
Building Tilesets using Datashader#
Warning
This notebook is a work in progress, and the tiling functionality may not be fully implemented yet.
If you’d like to contribute or report missing features, feel free to open an issue in the Datashader repository.
Datashader provides render_tiles which is a utility function for creating tilesets from arbitrary datashader pipelines.
from datashader.tiles import render_tiles
A couple of notes about the tiling process:
By default, uses a simple
Web Mercator Tiling Scheme (EPSG:3857)call
render_tileswith the following arguments:
extent_of_area_i_want_to_tile = (-500000, -500000, 500000, 500000) # xmin, ymin, xmax, ymax
render_tiles(extent_of_data_i_want_to_handle,
tile_levels=range(6),
output_path='example_tileset_output_directory',
load_data_func=function_which_returns_dataframe,
rasterize_func=function_which_creates_xarray_aggregate,
shader_func=function_which_renders_aggregate_to_datashader_image,
post_render_func=function_which_post_processes_image)
data representing x / y coordinates is assumed to be represented in meters (m) based on the Web Mercator coordinate system.
the tiling extent is subdivided into
supertilesgenerally of size4096 x 4096the
load_data_funcreturns a dataframe-like object and contains your data access specific code.the
rasterize_funcreturns axr.DataArrayand contains your xarray specific code.the
shader_funcreturns ads.Imageand contains your datashader specific code.the
post_render_funcis called once for each final tile (default 256 x 256) and contains PIL (Python Imaging Library) specific code. This is the hook for adding additional filters, text, watermarks, etc. to output tiles.
Creating Tile Component Functions#
Create load_data_func#
accepts
x_rangeandy_rangearguments which correspond to the ranges of the supertile being rendered.returns a dataframe-like object (pd.Dataframe / dask.Dataframe)
this example
load_data_funccreates a pandas dataframe withxandyfields sampled from a wald distribution
import pandas as pd
import numpy as np
df = None
def load_data_func(x_range, y_range):
global df
if df is None:
xoffsets = [-1, 1, -1, 1]
yoffsets = [-1, 1, 1, -1]
xs = np.concatenate([np.random.wald(10000000, 10000000, size=10000000) * offset for offset in xoffsets])
ys = np.concatenate([np.random.wald(10000000, 10000000, size=10000000) * offset for offset in yoffsets])
df = pd.DataFrame(dict(x=xs, y=ys))
return df.loc[df['x'].between(*x_range) & df['y'].between(*y_range)]
Create rasterize_func#
accepts
df,x_range,y_range,height,widtharguments which correspond to the data, ranges, and plot dimensions of the supertile being rendered.returns an
xr.DataArrayobject representing the aggregate.
import datashader as ds
def rasterize_func(df, x_range, y_range, height, width):
# aggregate
cvs = ds.Canvas(x_range=x_range, y_range=y_range,
plot_height=height, plot_width=width)
agg = cvs.points(df, 'x', 'y')
return agg
Create shader_func#
accepts
agg (xr.DataArray),span (tuple(min, max)). The span argument can be used to control color mapping / auto-ranging across supertiles.returns an
ds.Imageobject representing the shaded image.
import datashader.transfer_functions as tf
from datashader.colors import viridis
def shader_func(agg, span=None):
img = tf.shade(agg, cmap=reversed(viridis), span=span, how='log')
img = tf.set_background(img, 'black')
return img
Create post_render_func#
accepts
img,extrasarguments which correspond to the output PIL.Image before it is write to disk (or S3), and additional image properties.returns image
(PIL.Image)this is a good place to run any non-datashader-specific logic on each output tile.
from PIL import ImageDraw
def post_render_func(img, **kwargs):
info = "x={},y={},z={}".format(kwargs['x'], kwargs['y'], kwargs['z'])
draw = ImageDraw.Draw(img)
draw.text((5, 5), info, fill='rgb(255, 255, 255)')
return img
Render tiles to local filesystem#
full_extent_of_data = (-500000, -500000, 500000, 500000)
output_path = 'tiles_output_directory/wald_tiles'
results = render_tiles(full_extent_of_data,
range(3),
load_data_func=load_data_func,
rasterize_func=rasterize_func,
shader_func=shader_func,
post_render_func=post_render_func,
output_path=output_path)
calculating statistics for level 0
rendering 1 supertiles for zoom level 0 with span=(np.uint32(0), np.uint32(2969))
calculating statistics for level 1
rendering 1 supertiles for zoom level 1 with span=(np.uint32(0), np.uint32(781))
calculating statistics for level 2
rendering 1 supertiles for zoom level 2 with span=(np.uint32(0), np.uint32(223))
Preview the tileset using Bokeh#
Browse to the tile output directory and start an http server:
$> cd test_tiles_output
$> python -m http.server
Starting up http-server, serving ./
Available on:
http://127.0.0.1:8080
http://192.168.1.7:8080
Hit CTRL-C to stop the server
build a
bokeh.plotting.Figure
from bokeh.plotting import figure
from bokeh.models.tiles import WMTSTileSource
from bokeh.io import show
from bokeh.io import output_notebook
output_notebook()
xmin, ymin, xmax, ymax = full_extent_of_data
p = figure(width=800, height=800,
x_range=(int(-20e6), int(20e6)),
y_range=(int(-20e6), int(20e6)),
tools="pan,wheel_zoom,reset")
p.background_fill_color = 'black'
p.grid.grid_line_alpha = 0
p.axis.visible = False
p.add_tile(WMTSTileSource(url="http://localhost:8080/{Z}/{X}/{Y}.png"),
render_parents=False)
show(p)
Render tiles to Amazon Simple Storage Service (S3)#
To render tiles directly to S3, you only need to use the s3:// protocol in your output_path argument
Requires AWS Access / Secret Keys with appropriate IAM permissions for uploading to S3.
Requires extra
boto3dependency:
conda install boto3
Configuring credentials#
The mechanism in which boto3 looks for credentials is to search through a list of possible locations and stop as soon as it finds credentials. The order in which Boto3 searches for credentials is:
~~Passing credentials as parameters in the boto.client() method~~
~~Passing credentials as parameters when creating a Session object~~
Environment variables
Shared credential file (~/.aws/credentials)
AWS config file (~/.aws/config)
Assume Role provider
Boto2 config file (/etc/boto.cfg and ~/.boto)
Instance metadata service on an Amazon EC2 instance that has an IAM role configured.
Datashader’s
render_tilesfunction supports only credential search locations highlighted in bold aboveNOTE: all tiles written to S3 are marked with
public-readACL settings.
Setup tile bucket using AWS CLI#
$> aws s3 mb s3://datashader-tiles-testing/
full_extent_of_data = (int(-20e6), int(-20e6), int(20e6), int(20e6))
output_path = 's3://datashader-tiles-testing/wald_tiles/'
try:
results = render_tiles(full_extent_of_data,
range(3),
load_data_func=load_data_func,
rasterize_func=rasterize_func,
shader_func=shader_func,
post_render_func=post_render_func,
output_path=output_path)
except ImportError:
print('you must install boto3 to save tiles to Amazon S3')
calculating statistics for level 0
rendering 1 supertiles for zoom level 0 with span=(np.uint32(0), np.uint32(2969))
you must install boto3 to save tiles to Amazon S3
Preview S3 Tiles#
xmin, ymin, xmax, ymax = full_extent_of_data
p = figure(width=800, height=800,
x_range=(int(-20e6), int(20e6)),
y_range=(int(-20e6), int(20e6)),
tools="pan,wheel_zoom,reset")
p.axis.visible = False
p.background_fill_color = 'black'
p.grid.grid_line_alpha = 0
p.add_tile(WMTSTileSource(url="https://datashader-tiles-testing.s3.amazonaws.com/wald_tiles/{Z}/{X}/{Y}.png"),
render_parents=False)
show(p)