Experiment to zoom in and out based on given lat/lon bounds. Projects and translates the bounds to figure out the new scale/translation vector to pass to the geo.tile function. Was more annoying than I thought it would be.
Created
June 11, 2014 01:07
-
-
Save trevorgerhardt/69286b523d00d0d8c3d6 to your computer and use it in GitHub Desktop.
Map tile manual zooming on lat/lon bounds
This file contains hidden or 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
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset='utf-8'> | |
<style> | |
body { | |
margin: 0; | |
font-family: 'Helvetica', arial, sans-serif; | |
font-weight: 300; | |
width: 500px; | |
height: 500px; | |
} | |
.map { | |
position: relative; | |
overflow: hidden; | |
} | |
.layer { | |
position: absolute; | |
} | |
.tile { | |
pointer-events: none; | |
position: absolute; | |
width: 256px; | |
height: 256px; | |
} | |
form { | |
position: absolute; | |
z-index: 100; | |
border-radius: 10px; | |
margin: 10px; | |
} | |
input { | |
font-size: 1em; | |
padding: 5px; | |
font-weight: 300; | |
} | |
input[type=submit] { | |
background-color: #fff; | |
text-align: center; | |
} | |
form span { | |
display: inline-block; | |
padding: 5px; | |
} | |
</style> | |
</head> | |
<body> | |
<form> | |
<input type="text" name="lat1" value="39"> | |
<input type="text" name="lon1" value="-79"> | |
<br> | |
<input type="text" name="lat2" value="37"> | |
<input type="text" name="lon2" value="-77"> | |
<br> | |
<span>x:</span><span id="translatex"></span><span>y:</span><span id="translatey"></span><span>scale:</span> <span id="scale"></span> | |
<br> | |
<input type="submit" value="Update"> | |
</form> | |
<script src='http://d3js.org/d3.v3.min.js'></script> | |
<script src='http://d3js.org/d3.geo.tile.v0.min.js'></script> | |
<script> | |
var width = window.innerWidth; | |
var height = window.innerHeight; | |
var prefix = prefixMatch(['webkit', 'ms', 'Moz', 'O']); | |
var input = { | |
lat1: d3.select('[name=lat1]'), | |
lon1: d3.select('[name=lon1]'), | |
lat2: d3.select('[name=lat2]'), | |
lon2: d3.select('[name=lon2]'), | |
translatex: d3.select('#translatex'), | |
translatey: d3.select('#translatey'), | |
scale: d3.select('#scale') | |
}; | |
var tile = d3.geo.tile() | |
.size([width, height]); | |
var zoom = d3.behavior.zoom() | |
.scale(1 << 12) | |
.scaleExtent([1 << 10, 1 << 23]) | |
.translate([width / 2, height / 2]) | |
.on('zoom', zoomed); | |
var projection = d3.geo.mercator() | |
.scale(zoom.scale() / 2 / Math.PI) | |
.translate([width / 2, height / 2]); | |
var map = d3.select('body').append('div') | |
.attr('class', 'map') | |
.style('width', width + 'px') | |
.style('height', height + 'px') | |
.call(zoom); | |
var layer = map.append('div') | |
.attr('class', 'layer'); | |
document.getElementsByTagName('form')[0].addEventListener('submit', function(e) { | |
e.preventDefault(); | |
submit(); | |
}); | |
zoomed(); | |
function zoomed(is, it) { | |
var s = is || zoom.scale(); | |
var t = it || zoom.translate(); | |
tile | |
.scale(s) | |
.translate(t); | |
projection | |
.scale(s / 2 / Math.PI) | |
.translate(t); | |
var nw = projection.invert([ 0, 0 ]); | |
var se = projection.invert([ width, height ]); | |
input.lat1.property('value', nw[1]); | |
input.lon1.property('value', nw[0]); | |
input.lat2.property('value', se[1]); | |
input.lon2.property('value', se[0]); | |
var r = Math.round; | |
input.scale.text(r(s)); | |
input.translatex.text(r(t[0])); | |
input.translatey.text(r(t[1])); | |
renderTiles(tile()); | |
} | |
function renderTiles(tiles) { | |
var image = layer | |
.style(prefix + 'transform', matrix3d(tiles.scale, tiles.translate)) | |
.selectAll('.tile') | |
.data(tiles, function(d) { | |
return d; | |
}); | |
image.exit() | |
.remove(); | |
image.enter().append('img') | |
.attr('class', 'tile') | |
.attr('src', function(d) { | |
return 'http://' + ['a', 'b', 'c', 'd'][Math.random() * 4 | 0] + | |
'.tiles.mapbox.com/v3/conveyal.ie3o67m0/' + d[2] + '/' + d[0] + | |
'/' + d[1] + '.png'; | |
}) | |
.style('left', function(d) { | |
return (d[0] << 8) + 'px'; | |
}) | |
.style('top', function(d) { | |
return (d[1] << 8) + 'px'; | |
}); | |
} | |
function submit() { | |
var f = parseFloat; | |
zoomToLLBounds([f(input.lon1.property('value')), f(input.lat1.property('value'))], [f(input.lon2.property('value')), f(input.lat2.property('value'))]); | |
} | |
function zoomToLLBounds(nw, se) { | |
var pnw = projection(nw); | |
var pse = projection(se); | |
var scale = zoom.scale(); | |
var translate = zoom.translate(); | |
var dx = pse[0] - pnw[0]; | |
var dy = pse[1] - pnw[1]; | |
scale = scale * (1 / Math.max(dx / width, dy / height)); | |
projection | |
.translate([ width / 2, height / 2]) | |
.scale(scale / 2 / Math.PI); | |
// reproject | |
pnw = projection(nw); | |
pse = projection(se); | |
var x = (pnw[0] + pse[0]) / 2; | |
var y = (pnw[1] + pse[1]) / 2; | |
translate = [width - x, height - y]; | |
zoom | |
.scale(scale) | |
.translate(translate); | |
zoomed(scale, translate); | |
} | |
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 ''; | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment