Map of US military installations around the world. Most of the data comes from the 2013 Base Structure Report. I didn't collate or create the data, I am just practicing mapping things I find interesting. The much more sophisticated version of this visualization, and the original, can be found at http://empire.is and was created by Josh Begley. I highly recommend you check it out.
Last active
November 22, 2017 17:11
-
-
Save harrisoncramer/bf07f6926007830640a1a145a1a9fea5 to your computer and use it in GitHub Desktop.
Military Bases
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
license: gpl-3.0 |
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
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Military Bases</title> | |
<link rel="stylesheet" href="style.css"> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<link href="https://fonts.googleapis.com/css?family=Raleway" rel="stylesheet"> | |
</head> | |
<body> | |
<div id="title">US Military Installations </div> | |
</body> | |
<footer> | |
<script> | |
var height = 500 | |
var height = 500 | |
var width = 500 | |
var scale = 225 | |
d3.select("body").append("g").attr("id","Chart") | |
d3.select("g#Chart").append("svg") | |
.attr("width",width) | |
.attr("height",height) | |
var g = d3.select("svg").append("g") | |
var promiseWrapper = (xhr, d) => new | |
Promise(resolve => xhr(d, (p) => resolve(p))) | |
Promise.all([promiseWrapper(d3.json, "world.geojson"), | |
promiseWrapper(d3.json, "militaryBases.geojson")]) | |
.then(resolve => { | |
createMap(resolve[0], resolve[1]) | |
}) | |
function createMap(countries,bases){ | |
var projection = d3.geoOrthographic() | |
.scale(scale) | |
.translate([width/2,height/2]) | |
var geoPath = d3.geoPath() | |
.projection(projection); | |
// SET UP ZOOM // | |
var zoomSettings = d3.zoomIdentity | |
.translate(0,0) // Start out untranslated | |
.scale(scale) | |
var rotateScaleY = d3.scaleLinear() // This will be used to transform the x-zoom attribute (which ranges from -500 to 500) into degrees. | |
.domain([-500,0,500]) | |
.range([180,0,-180]) | |
var rotateScaleX = d3.scaleLinear() // This will be used to transform the x-zoom attribute (which ranges from -500 to 500) into degrees. | |
.domain([-250,0,250]) | |
.range([-180,0,180]) | |
var xRotate = 0 | |
var yRotate = 0 | |
var zoom = d3.zoom() | |
.on("zoom", rotate) | |
function rotate() { | |
var e = d3.event | |
var rotateScale = projection.scale()/scale | |
var xRotate = rotateScaleX(e.transform.x) % 360 // Gives you the angle of rotation | |
var yRotate = rotateScaleY(e.transform.y) % 360 | |
projection | |
.rotate([xRotate,yRotate]) | |
.scale(projection.scale()) | |
// console.log(xRotate,yRotate) | |
//Redraw the paths + graticules + cities with the updated projection. | |
d3.selectAll("path.graticule").attr("d",geoPath) | |
d3.selectAll("path.countries").attr("d", geoPath) | |
d3.selectAll("circle.bases") | |
.each(function (d, i) { | |
var projectedPoint = projection([d.geometry.coordinates[0],d.geometry.coordinates[1]]) | |
var x = parseInt(d.geometry.coordinates[0]) | |
var y = parseInt(d.geometry.coordinates[1]) | |
var display = (x + xRotate < 90 && x + xRotate > -90 | |
|| (x + xRotate < -270 && x + xRotate > -450) | |
|| (x + xRotate > 270 && x + xRotate < 450)) && y + yRotate < 90 && y + yRotate > -90 | |
|| (y + yRotate < -270 && y + yRotate > -450) | |
|| (y + yRotate > 270 && y + yRotate < 450) | |
? "block" : "none"; | |
d3.select(this) | |
.attr("cx", projectedPoint[0]) | |
.attr("cy", projectedPoint[1]) | |
.style("display", display) | |
}) | |
} | |
d3.select("#Chart").append("button").on("click", () => { zoomButton("in")}).html("Zoom In").attr("id","in") | |
d3.select("#Chart").append("button").on("click", () => { zoomButton("out")}).html("Zoom Out").attr("id","out") | |
function zoomButton(zoomDirection){ | |
// Get the current rotation + zoom | |
xRotate = projection.rotate()[0] | |
yRotate = projection.rotate()[1] | |
var backgroundZoom = 1; | |
if(zoomDirection == "in"){ | |
// Increase the scale | |
var newZoom = projection.scale() * 1.5; | |
backgroundZoom = 1.5 | |
} | |
else if(zoomDirection = "out"){ | |
// Decrease the scale | |
var newZoom = projection.scale() * .75; | |
backgroundZoom = .75 | |
} | |
projection.scale(newZoom) | |
d3.selectAll("path.graticule").transition().duration(250).attr("d",geoPath) | |
d3.selectAll("path.countries").transition().duration(250).attr("d", geoPath) | |
d3.selectAll("circle.bases") | |
.each(function (d, i) { | |
var projectedPoint = projection([d.geometry.coordinates[0],d.geometry.coordinates[1]]) | |
var x = parseInt(d.geometry.coordinates[0]) | |
var y = parseInt(d.geometry.coordinates[1]) | |
var display = (x + xRotate < 90 && x + xRotate > -90 | |
|| (x + xRotate < -270 && x + xRotate > -450) | |
|| (x + xRotate > 270 && x + xRotate < 450)) && y + yRotate < 90 && y + yRotate > -90 | |
|| (y + yRotate < -270 && y + yRotate > -450) | |
|| (y + yRotate > 270 && y + yRotate < 450) | |
? "block" : "none"; | |
d3.select(this) | |
.transition().duration(250) | |
.attr("cx", projectedPoint[0]) | |
.attr("cy", projectedPoint[1]) | |
.style("display", display) | |
}) | |
var currentR = d3.select("#backgroundCircle").attr("r") | |
console.log(newZoom/scale,currentR) | |
d3.select("#backgroundCircle") | |
.transition().duration(250) | |
.attr("r", function(){ | |
return currentR * backgroundZoom; | |
}) | |
} | |
// DRAW PATHS | |
d3.select("svg") | |
.call(zoom) | |
.call(zoom.transform, zoomSettings) | |
.on("wheel.zoom",null) | |
.on("dblclick.zoom",null) | |
g.selectAll("path").data(countries.features) | |
.enter() | |
.append("path") | |
.attr("class", "countries") | |
.attr("d", geoPath) | |
g.selectAll("circle").data(bases) | |
.enter() | |
.append("circle") | |
.attr("class", "bases") | |
.attr("r", 2) | |
.attr("cx", d => projection([d.geometry.coordinates][0],d.geometry.coordinates[1])[0]) | |
.attr("cy", d => projection([d.geometry.coordinates][0],d.geometry.coordinates[1])[1]) | |
.style("display", function(d){ | |
var x = parseInt(d.geometry.coordinates[0]) | |
var y = parseInt(d.geometry.coordinates[1]) | |
var display = (x + xRotate < 90 && x + xRotate > -90 | |
|| (x + xRotate < -270 && x + xRotate > -450) | |
|| (x + xRotate > 270 && x + xRotate < 450)) && y + yRotate < 90 && y + yRotate > -90 | |
|| (y + yRotate < -270 && y + yRotate > -450) | |
|| (y + yRotate > 270 && y + yRotate < 450) | |
? "block" : "none"; | |
return display | |
}) | |
.on("mouseover", displayName) | |
.on("mouseout", hideName) | |
var tooltip = d3.select("body").append("div") | |
.attr("id","tooltip") | |
.classed("showing",true) | |
.style("opacity",0) | |
function displayName(d){ | |
tooltip.text(d.properties.name) | |
var coordinates = [0,0] | |
coordinates = d3.mouse(this); | |
var x = coordinates[0] | |
var y = coordinates[1] | |
tooltip.style("left",x + 20 + "px") | |
tooltip.style("top",y + 30 + "px") | |
d3.select("#tooltip").transition().duration(250).style("opacity",1) | |
} | |
function hideName(){ | |
d3.select("#tooltip").transition().duration(250).style("opacity",0) | |
} | |
g.insert("circle","path") | |
.attr("id","backgroundCircle") | |
.attr("r",scale) | |
.attr("cx",width/2) | |
.attr("cy", height/2) | |
} | |
</script> | |
</footer> | |
</html> |
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
circle { | |
fill: yellow; | |
} | |
path.countries{ | |
stroke-width: 1px; | |
fill: #3B4E34; | |
stroke: white; | |
} | |
.showing { | |
opacity: 1; | |
position: absolute; | |
text-align: center; | |
padding: 5px; | |
font-family: "Raleway", sans-serif; | |
background: white; | |
font-size: 10px; | |
border: 0px; | |
border-radius: 2px; | |
pointer-events: none; | |
} | |
#backgroundCircle{ | |
fill: #4C8DC6; | |
} | |
button { | |
position: absolute; | |
left: 20px; | |
outline: none; | |
font-family: "Raleway", sans-serif; | |
background-color: lightgrey; | |
cursor: pointer; | |
} | |
button#in { | |
top: 60px; | |
} | |
button#out { | |
top: 80px; | |
} | |
#title { | |
opacity: 1; | |
text-align: center; | |
padding: 10px; | |
font-family: "Raleway", sans-serif; | |
width: 480px; | |
border: 0px; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment