Created
November 28, 2016 15:33
-
-
Save achavez/41d149052d97a34773336d8bc6c1e63c to your computer and use it in GitHub Desktop.
First attempt at using our Mapbox-style vector tiles with D3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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