|
<!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> |