Published unlisted
Edited
Sep 24, 2019
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
factory = buildFactory((z, x, y) => tileSource.url(x, y, z))
Insert cell
Insert cell
cache = tileRack.initCache(tileSize, factory)
Insert cell
Insert cell
tile = d3
.tile()
.tileSize(tileSize)
.size([width, height])
.clampX(false) // Allow panning across longitude 180 degrees
Insert cell
Insert cell
projection = d3
.geoMercator()
.center([-122.4183, 37.7750])
.scale(Math.pow(2, 21) / (2 * Math.PI))
.translate([width / 2, height / 2])
Insert cell
mutable transform = d3.zoomIdentity
.translate(projection([0, 0])[0], projection([0, 0])[1])
.scale(projection.scale() * 2 * Math.PI)
Insert cell
Insert cell
Insert cell
globalTileSet = tile(transform)
Insert cell
globalTileSet.scale
Insert cell
viewof context = {
const context = DOM.context2d(width, height);

// Set up zooming, and attach it to the canvas
const zoom = d3
.zoom()
.scaleExtent([1 << 8, 1 << 26])
.extent([[0, 0], [width, height]])
.on("zoom", () => zoomed(d3.event.transform));

d3.select(context.canvas)
.call(zoom)
.call(zoom.transform, mutable transform); // Sets initial zoom transform

// Update the set of tiles when the transform changes
function zoomed(transform) {
mutable transform = transform;
}

context.canvas.value = context;
return context.canvas;
}
Insert cell
update = {
// Redraw the canvas on each animation frame when the tiles have updated
context.clearRect(0, 0, width, height);
drawTiles(context, tile(transform));
}
Insert cell
Insert cell
function drawTiles(ctx, tileset) {
let scale = tileset.scale;
// Edit translation: make sure the first tile starts at an integer pixel coordinate
const tx = Math.round(tileset.translate[0] * scale) / scale;
const ty = Math.round(tileset.translate[1] * scale) / scale;
// If we are on an integer zoom level, all tiles will now be pixel-aligned

for (const [x, y, z] of tileset) {
const [xw, yw, zw] = d3.tileWrap([x, y, z]);
let tileBox = cache.retrieve([zw, xw, yw]);
if (!tileBox) continue;
drawTileBox(ctx, tileBox, x + tx, y + ty, scale);
}
}
Insert cell
Insert cell
function drawTileBox(ctx, box, x, y, scale) {
// Adjust cropping/scaling parameters for the actual pixel size of the tile,
// which, for some tile services, may be 2 * tileSize on hi-res screens
let sx = box.sx * tileSource.deviceFactor;
let sy = box.sy * tileSource.deviceFactor;
let sw = box.sw * tileSource.deviceFactor;
ctx.drawImage(
box.tile.img,
sx,
sy,
sw,
sw, // Crops to target region if we have a parent tile
x * scale,
y * scale,
scale,
scale
);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
tileRack = import("tile-rack@0.0.2")
Insert cell
d3 = require("d3@5", "d3-geo@1", "d3-tile@1")
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more