Published
Edited
Sep 25, 2019
1 star
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(tileSource.realTileSize(), factory)
Insert cell
Insert cell
tile = d3
.tile()
.tileSize(tileSource.cssTileSize())
.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
initialTransform = d3.zoomIdentity
.translate(projection([0, 0])[0], projection([0, 0])[1])
.scale(projection.scale() * 2 * Math.PI)
Insert cell
Insert cell
Insert cell
mutable globalTileSet = tile(initialTransform)
Insert cell
globalTileSet.scale
Insert cell
map = {
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, initialTransform); // Sets initial zoom transform

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

// Redraw the canvas on each animation frame, in case the tiles have updated
while (true) {
context.clearRect(0, 0, width, height);
drawTiles(context, mutable globalTileSet);
yield context.canvas;
}
}
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) {
ctx.drawImage(
box.tile.img,
box.sx,
box.sy,
box.sw,
box.sw,
x * scale,
y * scale,
scale,
scale
);
}
Insert cell
Insert cell
Insert cell
function rescalePixels(cssTileSize) {
return devicePixelRatio > 1 ? 2 * cssTileSize : cssTileSize;
}
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