Skip to content

Instantly share code, notes, and snippets.

@achavez
Created November 28, 2016 15:33
Show Gist options
  • Save achavez/41d149052d97a34773336d8bc6c1e63c to your computer and use it in GitHub Desktop.
Save achavez/41d149052d97a34773336d8bc6c1e63c to your computer and use it in GitHub Desktop.
First attempt at using our Mapbox-style vector tiles with D3
var layers = ['water', 'road'];
window.renderTiles = function(d) {
var svg = d3.select(this);
this._xhr = d3.request("http://maps.dallasnews.com/" + d[2] + "/" + d[0] + "/" + d[1] + ".pbf").responseType('arraybuffer').get(function(error, json) {
if(error) return console.error(error);
var tile = new VectorTile( new Pbf( new Uint8Array(json.response) ) );
var extents = 4096;
var data = {};
for (var key in tile.layers) {
data[key] = tile.layers[key].toGeoJSON();
}
var tile_projection = d3.geoTransform({
point: function(x, y) {
// Sometimes PBF points in a mixed-geometry layer are corrupted
if(!isNaN(y)){
x = x/extents*256;
y = y/extents*256;
} else {
y = x[0][1]/extents * 256;
x = x[0][0]/extents * 256;
}
this.stream.point(x, y);
}
});
var tilePath = d3.geoPath()
.projection(tile_projection);
// build up a single concatenated array of all tile features from all tile layers
var features = [];
layers.forEach(function(layer){
if(data[layer]) {
for(var i in data[layer].features) {
// Don't include any label placement points
// if(data[layer].features[i].properties.label_placement) { continue }
// Don't show large buildings at z13 or below.
// if(zoom <= 13 && layer == 'buildings') { continue }
// Don't show small buildings at z14 or below.
// if(zoom <= 14 && layer == 'buildings' && data[layer].features[i].properties.area < 2000) { continue }
data[layer].features[i].layer_name = layer;
features.push(data[layer].features[i]);
}
}
});
// put all the features into SVG paths
svg.selectAll("path")
.data(features.sort(function(a, b) {
return a.properties.sort_rank ? a.properties.sort_rank - b.properties.sort_rank : 0;
}))
.enter().append("path")
.attr("class", function(d) {
var kind = d.properties.kind || '';
if (d.properties.boundary) {
kind += '_boundary';
}
if (d.layer_name === 'road') {
console.debug(d);
}
return d.layer_name + '-layer ' + kind;
})
.attr("d", tilePath);
});
};
var width = window.innerWidth,
height = window.innerHeight,
prefix = prefixMatch(["webkit", "ms", "Moz", "O"]);
var pi = Math.PI,
tau = 2 * pi;
var tile = d3.tile()
.size([width, height]);
var projection = d3.geoMercator()
.scale(1 / tau)
.translate([0, 0]);
var tilePath = d3.geoPath()
.projection(projection);
var zoom = d3.zoom()
.scaleExtent([1 << 15, 1 << 23])
.on("zoom", zoomed);
var center = projection([-96.9785, 32.8924]);
var map = d3.select("#map")
.attr("class", "map")
.style("width", width + "px")
.style("height", height + "px");
var layer = map.append("div")
.attr("class", "layer");
map.call(zoom)
.call(zoom.transform, d3.zoomIdentity
.translate(width / 2, height / 2)
.scale(1 << 15)
.translate(-center[0], -center[1])
);
var zoom_controls = map.append("div")
.attr("class", "zoom-container");
var zoom_in = zoom_controls.append("a")
.attr("class", "zoom")
.attr("id", "zoom_in")
.text("+");
var zoom_out = zoom_controls.append("a")
.attr("class", "zoom")
.attr("id", "zoom_out")
.text("-");
// var info = map.append("div")
// .attr("class", "info")
// .html('<a href="http://bl.ocks.org/mbostock/5593150" target="_top">Mike Bostock</a> | © <a href="https://www.openstreetmap.org/copyright" target="_top">OpenStreetMap contributors</a> | <a href="https://mapzen.com/projects/vector-tiles" title="Tiles courtesy of Mapzen" target="_top">Mapzen</a>');
// zoomed();
// Resize when window resizes
// window.onresize = function () {
// width = window.innerWidth;
// height = window.innerHeight;
// map.style("width", width + "px")
// .style("height", height + "px");
// tile = d3.geo.tile()
// .size([width, height]);
// zoomed();
// }
function zoomed() {
var transform = d3.event.transform;
var tiles = tile
.scale(transform.k)
.translate([transform.x, transform.y])
();
projection
.scale(transform.k / tau)
.translate([transform.x, transform.y]);
var image = layer
.style(prefix + "transform", matrix3d(tiles.scale, tiles.translate))
.selectAll(".tile")
.data(tiles, function(d) { return d; });
image.exit()
.each(function(d) { this._xhr.abort(); })
.remove();
image.enter().append("svg")
.attr("class", "tile")
.style("left", function(d) { return d[0] * 256 + "px"; })
.style("top", function(d) { return d[1] * 256 + "px"; })
.each(window.renderTiles);
}
function matrix3d(scale, translate) {
var k = scale / 256, r = scale % 1 ? Number : Math.round;
return "matrix3d(" + [k, 0, 0, 0, 0, k, 0, 0, 0, 0, k, 0, r(translate[0] * scale), r(translate[1] * scale), 0, 1 ] + ")";
}
function prefixMatch(p) {
var i = -1, n = p.length, s = document.body.style;
while (++i < n) if (p[i] + "Transform" in s) return "-" + p[i].toLowerCase() + "-";
return "";
}
// zoom controls
function interpolateZoom (translate, scale) {
var self = this;
return d3.transition().duration(350).tween("zoom", function () {
var iTranslate = d3.interpolate(zoom.translate(), translate),
iScale = d3.interpolate(zoom.scale(), scale);
return function (t) {
zoom
.scale(iScale(t))
.translate(iTranslate(t));
zoomed();
};
});
}
function zoomClick() {
var clicked = d3.event.target,
direction = 1,
factor = 0.2,
target_zoom = 1,
center = [width / 2, height / 2],
extent = zoom.scaleExtent(),
translate = zoom.translate(),
translate0 = [],
l = [],
view = {x: translate[0], y: translate[1], k: zoom.scale()};
d3.event.preventDefault();
direction = (this.id === 'zoom_in') ? 1 : -1;
target_zoom = zoom.scale() * (1 + factor * direction);
if (target_zoom < extent[0] || target_zoom > extent[1]) { return false; }
translate0 = [(center[0] - view.x) / view.k, (center[1] - view.y) / view.k];
view.k = target_zoom;
l = [translate0[0] * view.k + view.x, translate0[1] * view.k + view.y];
view.x += center[0] - l[0];
view.y += center[1] - l[1];
interpolateZoom([view.x, view.y], view.k);
}
d3.selectAll('a.zoom').on('click', zoomClick);
//disable mousewheel zoom if iframed
if (window.self !== window.top) {
map.on("wheel.zoom", null);
document.documentElement.className += ' mapzen-demo-iframed';
}
// Hide zoom control on touch devices, which interferes with project page navigation overlay
if (('ontouchstart' in window) || (window.DocumentTouch && document instanceof DocumentTouch)) {
document.getElementsByClassName('zoom-container')[0].style.display = 'none';
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment