Skip to content

Instantly share code, notes, and snippets.

@harrisoncramer
Last active November 22, 2017 17:11
Show Gist options
  • Save harrisoncramer/bf07f6926007830640a1a145a1a9fea5 to your computer and use it in GitHub Desktop.
Save harrisoncramer/bf07f6926007830640a1a145a1a9fea5 to your computer and use it in GitHub Desktop.
Military Bases
license: gpl-3.0

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.

<!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>
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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;
}
Display the source blob
Display the rendered blob
Raw
Loading
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