Create a gist now

Instantly share code, notes, and snippets.

@dwtkns /README.md
Last active May 1, 2017

What would you like to do?
Slippy map + extent indicator
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
margin: 0;
}
/* misc */
.info {
position: absolute;
bottom: 10px;
left: 10px;
font: 14px sans-serif;
}
.attrib {
position: absolute;
bottom: 10px;
right: 10px;
font: 10px sans-serif;
padding: 5px;
background-color:white;
opacity:.8;
}
.attrib a {
color: black;
font-weight:800;
}
/* tiles */
.map {
position: relative;
overflow: hidden;
}
.layer {
position: absolute;
}
.tile {
position: absolute;
width: 256px;
height: 256px;
opacity:.8;
}
/* globe */
svg {
position:absolute;
bottom:10px;
}
.land {
fill: rgb(84, 77, 69);
stroke-opacity: 1;
}
.countries path {
stroke: rgb(80, 64, 39);
stroke-linejoin: round;
stroke-width:.5;
fill: rgb(117, 87, 57);
opacity: .1;
}
.countries path:hover {
fill-opacity:.1;
stroke-width:1;
opacity: 1;
}
.graticule {
fill: none;
stroke: black;
stroke-width:.5;
opacity:.3;
}
.extent {
fill: #933;
opacity: .6;
}
.noclicks {
pointer-events:none;
}
.point { fill:rgb(57, 38, 19); }
/* point classes */
.point.r1 { opacity: .8; }
.point.r2 { opacity: .8; }
.point.r3,
.point.r4,
.point.r5 { opacity: .3; }
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/d3.geo.tile.v0.min.js"></script>
<script src="http://d3js.org/queue.v1.min.js"></script>
<script src="http://d3js.org/topojson.v0.min.js"></script>
<script>
// slippy map code from
// http://bl.ocks.org/3943330 by tmcw
// http://bl.ocks.org/4132797 by mbostock
var width = window.innerWidth,
height = window.innerHeight,
prefix = prefixMatch(["webkit", "ms", "Moz", "O"]);
var inset = {
w: 320,
h: 320,
projection: null, extentRect: null, svg: null, path: null, graticule: null,
init: function() {
inset.projection = d3.geo.orthographic()
.scale(140)
.translate([inset.w / 2, inset.h / 2])
.clipAngle(90)
inset.path = d3.geo.path()
.projection(inset.projection)
.pointRadius(1.5);
inset.graticule = d3.geo.graticule();
inset.extentRect = [{
"type": "Feature",
"geometry": { "type": "Polygon", "coordinates": [[]]}
}]
inset.svg = d3.select("body").append("svg")
.attr("width", inset.w)
.attr("height", inset.h)
.attr("class","noclicks")
queue()
.defer(d3.json, "world-110m.json")
.defer(d3.json, "places.json")
.await(inset.ready);
},
ready: function(error,world,places) {
var scale = inset.projection.scale();
var defs = inset.svg.append("defs")
var ocean = defs.append("radialGradient")
.attr("id", "ocean")
.attr("cx", "75%")
.attr("cy", "25%");
ocean.append("stop").attr("offset", "5%").attr("stop-color", "#e6e6f4");
ocean.append("stop").attr("offset", "100%").attr("stop-color", "#a2abb3");
var highlight = defs.append("radialGradient")
.attr("id", "highlight")
.attr("cx", "75%")
.attr("cy", "25%");
highlight.append("stop")
.attr("offset", "5%").attr("stop-color", "#ffd")
.attr("stop-opacity","0.6");
highlight.append("stop")
.attr("offset", "100%").attr("stop-color", "#ba9")
.attr("stop-opacity","0.2");
var shade = defs.append("radialGradient")
.attr("id", "shade")
.attr("cx", "50%")
.attr("cy", "40%");
shade.append("stop")
.attr("offset","50%").attr("stop-color", "#a2abb3")
.attr("stop-opacity","0")
shade.append("stop")
.attr("offset","100%").attr("stop-color", "#57616b")
.attr("stop-opacity","0.3")
var halo = defs.append("radialGradient")
.attr("id", "halo")
.attr("cx", "50%")
.attr("cy", "50%");
halo.append("stop")
.attr("offset","85%").attr("stop-color", "#FFF")
.attr("stop-opacity","1")
halo.append("stop")
.attr("offset","100%").attr("stop-color", "#FFF")
.attr("stop-opacity","0")
inset.svg.append("ellipse")
.attr("cx", inset.w/2).attr("cy", inset.h/2)
.attr("rx", scale+20)
.attr("ry", scale+20)
.attr("class", "noclicks")
.style("fill", "url(#halo)");
inset.svg.append("circle")
.attr("cx", inset.w / 2).attr("cy", inset.h / 2)
.attr("r", scale)
.attr("class", "noclicks")
.style("fill", "url(#ocean)");
inset.svg.append("path")
.datum(topojson.object(world, world.objects.land))
.attr("class", "land")
.attr("d", inset.path);
inset.svg.append("path")
.datum(inset.graticule)
.attr("class", "graticule noclicks")
.attr("d", inset.path);
inset.svg.append("circle")
.attr("cx", inset.w / 2).attr("cy", inset.h / 2)
.attr("r", scale)
.attr("class","noclicks")
.style("fill", "url(#highlight)");
inset.svg.append("circle")
.attr("cx", inset.w / 2).attr("cy", inset.h/ 2)
.attr("r", scale)
.attr("class","noclicks")
.style("fill", "url(#shade)");
inset.svg.append("g").attr("class","points")
.selectAll("text").data(places.features)
.enter().append("path")
.attr("class", function(d){
return "point r" + (5-d.properties.scalerank)
})
.attr("d", inset.path);
inset.svg.append("g").attr("class","extents")
.selectAll("path").data(inset.extentRect)
.enter().append("path")
.attr("class", "extent")
.attr("d", inset.path);
},
refresh: function(dims) {
inset.projection.rotate([-dims.center[0],-dims.center[1]])
var e = dims.topline.concat(dims.bottomline);
e.push([dims.topline[0]])
inset.extentRect[0].geometry.coordinates[0] = e;
inset.svg.select(".extent").attr("d", inset.path);
inset.svg.select(".land").attr("d", inset.path);
inset.svg.select(".graticule").attr("d", inset.path);
inset.svg.select(".extent").attr("d", inset.path);
inset.svg.selectAll(".point").attr("d", inset.path);
}
}
var bg = {
tile: null,
init: function() {},
refresh: function() {},
}
var tile = d3.geo.tile()
.size([width, height]);
var projection = d3.geo.mercator();
var zoom = d3.behavior.zoom()
.scale(1 << 13)
.scaleExtent([1 << 12, 1 << 23])
.translate([width / 2, height / 2])
.on("zoom", refresh);
var map = d3.select("body").append("div")
.attr("class", "map")
.style("width", width + "px")
.style("height", height + "px")
.call(zoom)
.on("mousemove", mousemoved);
var layer = map.append("div").attr("class", "layer");
var info = map.append("div").attr("class", "info");
var attrib = map.append("div").attr("class", "attrib").html('Map tiles by <a href="http://stamen.com">Stamen Design</a>, under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, under <a href="http://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>.')
inset.init();
refresh();
function refresh() {
var tiles = tile
.scale(zoom.scale())
.translate(zoom.translate())
();
projection
.scale(zoom.scale())
.translate(zoom.translate());
var map_dims = {
topline: [],
bottomline: [],
center: projection.invert([width/2,height/2])
// for static globe / moving extent
// center: [-70,45]
}
var samples = 8,
step = width/samples;
for (var i = 0; i < samples; i++) {
map_dims.topline
.push(projection.invert( [step*i,0] ))
map_dims.bottomline
.push(projection.invert( [step*(samples-i-1),height] ))
}
inset.refresh(map_dims)
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://tile.stamen.com/toner-lite/" + 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 mousemoved() {
info.text(formatLocation(projection.invert(d3.mouse(this)), zoom.scale()));
}
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 "";
}
function formatLocation(p, k) {
var format = d3.format("." + Math.floor(Math.log(k) / 2 - 2) + "f");
return (p[1] < 0 ? format(-p[1]) + "°S" : format(p[1]) + "°N") + " "
+ (p[0] < 0 ? format(-p[0]) + "°W" : format(p[0]) + "°E");
}
</script>
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment