A map of GISDev participants, based on the excellent work of Marc Neuwirth.
$.getJSON(
"https://spreadsheets.google.com/feeds/list/1p0NKYGkH2MHcqzd7olnTbmLEexZ8-Uz7pTaK--1mPvI/1/public/values?alt=json",
function(data) {
// process google sheet into geojson
var devGeojson = {
type: 'FeatureCollection',
features: []
};
var devs = data.feed.entry;
for (var i in devs) {
if (devs[i].gsx$latitude.$t) {
var feature = {
type: 'Feature',
properties: {
title: '<h1>' + devs[i].gsx$name.$t + '</h1>',
'marker-color': '#227FBB',
'marker-size': 'large',
'marker-symbol': 'rocket',
url: 'https://gisdevs.slack.com/team/' + devs[i].gsx$name.$t
},
geometry: {
type: 'Point',
coordinates: [devs[i].gsx$longitude.$t, devs[i].gsx$latitude.$t]
}
};
devGeojson.features.push(feature);
}
}
(function(w, d3, undefined) {
"use strict";
var width, height;
function getSize() {
width = w.innerWidth,
height = w.innerHeight;
if (width === 0 || height === 0) {
setTimeout(function() {
getSize();
}, 100);
} else {
init();
}
}
function init() {
//Setup path for outerspace
var space = d3.geo.azimuthal()
.mode("equidistant")
.translate([width/2, height/2]);
space
.scale(space.scale() * 3);
var spacePath = d3.geo.path()
.projection(space)
.pointRadius(1);
//Setup path for globe
var projection = d3.geo.azimuthal()
.mode("orthographic")
.translate([width/2, height/2])
.scale(300)
.origin([-30,40])
var scale0 = projection.scale();
var path = d3.geo.path()
.projection(projection)
.pointRadius(2);
//Setup path for devs
var devPath = d3.geo.path()
.projection(projection)
.pointRadius(6);
//Setup zoom behavior
var zoom = d3.behavior
.zoom(true)
.translate(projection.origin())
.scale(projection.scale())
.scaleExtent([200, 800])
.on("zoom", move);
var circle = d3.geo.greatCircle()
.origin([-30,40]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.call(zoom)
.on("dblclick.zoom", null);
//Create a list of random stars and add them to outerspace
var starList = createStars(1000);
var stars = svg.append("g")
.selectAll("g")
.data(starList)
.enter()
.append("path")
.attr("class", "star")
.attr("d", function(d) {
spacePath.pointRadius(d.properties.radius);
return spacePath(d);
});
svg.append("rect")
.attr("class", "frame")
.attr("width", width)
.attr("height", height);
//Create the base globe
var backgroundCircle = svg.append("circle")
.attr('cx', width / 2)
.attr('cy', height / 2)
.attr('r', projection.scale())
.attr('class', 'globe')
.attr("filter", "url(#glow)")
.attr("fill", "url(#gradBlue)");
var countryG = svg.append("g"),
devG = svg.append("g"),
globeFeatures,
devFeatures;
//Add all of the countries to the globe
d3.json("world-countries.json", function(collection) {
globeFeatures = countryG.selectAll(".feature")
.data(collection.features);
globeFeatures.enter().append("path")
.attr("class", "feature")
.attr("d", function(d) {
return path(circle.clip(d));
});
});
// add dev points to the globe
//console.log(devGeojson.features);
devFeatures = devG.selectAll(".dev")
.data(devGeojson.features);
devFeatures.enter()
.append("path")
.attr("class", "dev")
.attr("d", function(d) {
return devPath(circle.clip(d));
});
//Redraw all items with new projections
function redraw() {
globeFeatures.attr("d", function(d) {
return path(circle.clip(d));
});
devFeatures.attr("d", function(d) {
return devPath(circle.clip(d));
});
stars.attr("d", function(d) {
spacePath.pointRadius(d.properties.radius);
return spacePath(d);
});
}
function move() {
if (d3.event) {
var scale = d3.event.scale;
var origin = [d3.event.translate[0] * -1, d3.event.translate[1]];
projection.scale(scale);
space.scale(scale * 3);
backgroundCircle.attr('r', scale);
path.pointRadius(2 * scale / scale0);
devPath.pointRadius(6 * scale / scale0);
projection.origin(origin);
circle.origin(origin);
//globe and stars spin in the opposite direction because of the projection mode
var spaceOrigin = [origin[0] * -1, origin[1] * -1];
space.origin(spaceOrigin);
redraw();
}
}
function createStars(number) {
var data = [];
for (var i = 0; i < number; i++) {
data.push({
geometry: {
type: 'Point',
coordinates: randomLonLat()
},
type: 'Feature',
properties: {
radius: Math.random() * 1.5
}
});
}
return data;
}
function randomLonLat() {
return [Math.random() * 360 - 180, Math.random() * 180 - 90];
}
}
getSize();
}(window, d3));
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<svg id="defs">
<defs>
<linearGradient id="gradBlue" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#005C99;stop-opacity:1" />
<stop offset="100%" style="stop-color:#0099FF;stop-opacity:1" />
</linearGradient>
<filter id="glow">
<feColorMatrix type="matrix"
values=
"0 0 0 0 0
0 0 0 0.9 0
0 0 0 0.9 0
0 0 0 1 0"/>
<feGaussianBlur stdDeviation="5.5" result="coloredBlur"/>
<feMerge>
<feMergeNode in="coloredBlur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
</svg>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="d3.custom.js"></script>
<script src="script.js"></script>
</body>
</html>
body {
background-color: #444444;
background-image: -webkit-gradient(linear, left top, right bottom, from(#000), to(#333));
background-image: -webkit-linear-gradient(left top, #000, #333);
background-image: -moz-linear-gradient(left top, #000, #333);
overflow: hidden;
}
#info {
position: absolute;
z-index: 10;
left: 25px;
top: 25px;
}
#defs {
height: 0;
width:0;
}
.frame {
fill: none;
pointer-events: all;
}
.feature {
fill: #c2c2c2;
stroke: #e2e2e2;
stroke-width: .5px;
}
.dev {
fill: #ff8e00;
fill-opacity: 0.6;
stroke: #333;
stroke-width: 1px;
}
.globe {
stroke: rgba(255, 255, 255, .5);
stroke-width: .25px;
}
.star {
fill: #fff;
stroke: rgba(255, 255, 255, .5);
stroke-width: .25px;
}