Published
Edited
Apr 4, 2020
Importers
7 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function getTileMetric(layout, tileset, padding = 0.595) {
const zoom = tileset[0][2];
const nTiles = 2 ** zoom;
const tileLength = tileset.scale; // Length of one tile in pixels
const mapResolution = layout.tileSize() / tileLength;

function wrap(x, xmax) {
while (x < 0) x += xmax;
while (x >= xmax) x -= xmax;
return x;
}

// Map is viewport + padding. Store the map cornerpoints in tile units
const [vpWidth, vpHeight] = layout.size();
const pad = padding * mapResolution; // In tile units
const x0 = wrap(-tileset.translate[0] - pad, nTiles);
const x1 = x0 + vpWidth / tileLength + 2 * pad; // May cross antimeridian
const y0 = -tileset.translate[1] - pad;
const y1 = y0 + vpHeight / tileLength + 2 * pad;

return function(tile) {
let zoomFac = 2 ** (zoom - tile.z);
let tileResolution = Math.min(1, mapResolution / zoomFac);

// Convert the tile cornerpoints to tile units at MAP zoom level
let tb = {
x0: tile.x * zoomFac,
x1: (tile.x + 1) * zoomFac,
y0: tile.y * zoomFac,
y1: (tile.y + 1) * zoomFac
};

// Find intersections of map and tile. Be careful with the antimeridian
let xOverlap = Math.max(
// Test for intersection with the tile in its raw position
Math.min(x1, tb.x1) - Math.max(x0, tb.x0),
// Test with the tile shifted across the antimeridian
Math.min(x1, tb.x1 + nTiles) - Math.max(x0, tb.x0 + nTiles)
);
let yOverlap = Math.min(y1, tb.y1) - Math.max(y0, tb.y0);
let overlapArea = Math.max(0, xOverlap) * Math.max(0, yOverlap);
let visibleArea = overlapArea / mapResolution ** 2;

// Flip sign to put most valuable tiles at the minimum
return 1.0 - visibleArea * tileResolution;
};
}
Insert cell
metric = getTileMetric(tiler, tileset)
Insert cell
numCachedTiles = {
while (true) {
drawTiles(mapContext);
yield cache.trim(metric, 0.75);
}
}
Insert cell
Insert cell
Insert cell
Insert cell
cache = tileRack.initRasterCache(cacheTileSize, (z, x, y) =>
tileSource.url(x, y, z)
)
Insert cell
mutable cacheTileSize = tileSource.realTileSize();
Insert cell
resolutionString = {
let mqString = `(resolution: ${devicePixelRatio}dppx)`;
matchMedia(mqString).addEventListener("change", () => {
mutable cacheTileSize = tileSource.realTileSize();
});
return mqString;
}
Insert cell
Insert cell
Insert cell
tiler = d3
.tile()
.tileSize(tileSource.cssTileSize())
.size([width, height])
.clampX(false) // Allow panning across longitude 180 degrees
Insert cell
tileset = tiler(transform)
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
zoom = d3
.zoom()
.scaleExtent([1 << 10, 1 << 26])
.extent([[0, 0], [width, height]])
.translateExtent([[-Infinity, -0.5], [Infinity, 0.5]])
.on("zoom", () => {
mutable transform = d3.event.transform;
})
Insert cell
d3
.select(mapContext.canvas)
.call(zoom)
.call(zoom.transform, mutable transform)
Insert cell
Insert cell
Insert cell
html`<style>
.mapContainer {
position: relative;
width: ${width}px;
padding: 0;
}
.attribution {
font-family: Arial, sans-serif;
font-size: 14px;
background: rgba(255, 255, 255, 0.8);
position: absolute;
bottom: 0;
right: 0;
padding: 0 4px 0 4px;
}
</style>`
Insert cell
import { drawTileBox } from "@jjhembd/zoomable-map-with-tile-cache"
Insert cell
Insert cell
Insert cell
function rescalePixels(cssTileSize) {
return devicePixelRatio > 1 ? 2 * cssTileSize : cssTileSize;
}
Insert cell
Insert cell
tileRack = import("tile-rack@0.2.0")
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