|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
.background { |
|
fill: #a4bac7; |
|
} |
|
|
|
.foreground { |
|
fill: none; |
|
stroke: #333; |
|
stroke-width: 1.5px; |
|
} |
|
|
|
.graticule { |
|
fill: none; |
|
stroke: #fff; |
|
stroke-width: .5px; |
|
} |
|
|
|
.graticule :nth-child(2n) { |
|
stroke-dasharray: 2,2; |
|
} |
|
|
|
.land { |
|
fill: #d7c7ad; |
|
stroke: #766951; |
|
} |
|
|
|
.night { |
|
stroke: steelblue; |
|
fill: steelblue; |
|
fill-opacity: .3; |
|
} |
|
|
|
.sun { |
|
stroke: red; |
|
fill: yellow; |
|
fill-opacity: .7; |
|
} |
|
|
|
.boundary { |
|
fill: none; |
|
stroke: #a5967e; |
|
} |
|
|
|
</style> |
|
<body> |
|
<script src="http://d3js.org/d3.v3.min.js"></script> |
|
<script src="http://d3js.org/d3.geo.projection.v0.min.js"></script> |
|
<script src="http://d3js.org/d3.geo.polyhedron.v0.min.js"></script> |
|
<script src="http://d3js.org/topojson.v0.min.js"></script> |
|
<script> |
|
|
|
var width = 960, |
|
height = 550; |
|
|
|
var π = Math.PI, |
|
radians = π / 180, |
|
degrees = 180 / π; |
|
|
|
var projection = d3.geo.polyhedron.waterman() |
|
.scale(118) |
|
.translate([width / 2, height / 2]) |
|
.rotate([20, 0]); |
|
|
|
var circle = d3.geo.circle() |
|
.angle(90); |
|
|
|
var sun_circle = d3.geo.circle() |
|
.angle(2) |
|
.precision(0.5); |
|
|
|
var path = d3.geo.path() |
|
.projection(projection); |
|
|
|
var graticule = d3.geo.graticule() |
|
.extent([[-180, -90], [180, 90]]); |
|
|
|
var svg = d3.select("body").append("svg") |
|
.attr("width", width) |
|
.attr("height", height); |
|
|
|
svg.append("defs").append("path") |
|
.datum({type: "Sphere"}) |
|
.attr("id", "sphere") |
|
.attr("d", path); |
|
|
|
svg.append("clipPath") |
|
.attr("id", "clip") |
|
.append("use") |
|
.attr("xlink:href", "#sphere"); |
|
|
|
svg.append("use") |
|
.attr("class", "background") |
|
.attr("xlink:href", "#sphere"); |
|
|
|
svg.append("g") |
|
.attr("class", "graticule") |
|
.attr("clip-path", "url(#clip)") |
|
.selectAll("path") |
|
.data(graticule.lines) |
|
.enter().append("path") |
|
.attr("d", path); |
|
|
|
svg.append("use") |
|
.attr("class", "foreground") |
|
.attr("xlink:href", "#sphere"); |
|
|
|
|
|
d3.json("/mbostock/raw/4090846/world-50m.json", function(error, world) { |
|
svg.append("path") |
|
.datum(topojson.object(world, world.objects.land)) |
|
.attr("class", "land") |
|
.attr("clip-path", "url(#clip)") |
|
.attr("d", path); |
|
|
|
svg.append("path", ".graticule") |
|
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; })) |
|
.attr("clip-path", "url(#clip)") |
|
.attr("class", "boundary") |
|
.attr("d", path); |
|
|
|
var night = svg.append("path") |
|
.attr("class", "night") |
|
.attr("clip-path", "url(#clip)") |
|
.attr("d", path); |
|
|
|
// A. this should work but while correctly positioned it is not visible... |
|
// var sun = svg.append("path") |
|
// .attr("class", "sun") |
|
// .attr("clip-path", "url(#clip)") |
|
// .attr("d", "m7,25a18,18 0 1,1 0,.1zm3,0a15,15 0 1,0 0-.1zm11,0a4,4 0 1,0 0-.1z"); |
|
|
|
// B. this works |
|
// var sun = svg.append("circle") |
|
// .attr("class", "sun") |
|
// .attr("clip-path", "url(#clip)") |
|
// .attr("r", 10); |
|
|
|
// C. this work-ish, the path is projected as per butterfly... |
|
// var sun = svg.append("path") |
|
// .attr("class", "sun") |
|
// .attr("clip-path", "url(#clip)") |
|
// .attr("d", path); |
|
|
|
// D. use an external image |
|
var sun = svg.append("image") |
|
.attr("class", "sun") |
|
.attr("clip-path", "url(#clip)") |
|
.attr("xlink:href", "Sun_symbol.svg") |
|
.attr("width", 30) |
|
.attr("height", 30); |
|
|
|
|
|
redraw(); |
|
setInterval(redraw, 1000); |
|
|
|
function redraw() { |
|
var p = solarPosition(new Date), |
|
s = projection(p); |
|
night.datum(circle.origin(antipode(p))).attr("d", path); |
|
|
|
// A. |
|
// sun.attr("transform", "translate(" + (s[0] - 25) + ", " + (s[1] - 25) + ")"); |
|
|
|
// B. |
|
// sun.attr("cx", s[0]).attr("cy", s[1]); |
|
|
|
// C. |
|
// sun.datum(sun_circle.origin(p)).attr("d", path); |
|
|
|
// D. |
|
sun |
|
.attr("x", s[0] - (sun[0][0].width.baseVal.value / 2)) |
|
.attr("y", s[1] - (sun[0][0].height.baseVal.value / 2)); |
|
} |
|
}); |
|
|
|
function antipode(position) { |
|
return [position[0] + 180, -position[1]]; |
|
} |
|
|
|
function solarPosition(time) { |
|
var centuries = (time - Date.UTC(2000, 0, 1, 12)) / 864e5 / 36525, // since J2000 |
|
longitude = (d3.time.day.utc.floor(time) - time) / 864e5 * 360 - 180; |
|
return [ |
|
longitude - equationOfTime(centuries) * degrees, |
|
solarDeclination(centuries) * degrees |
|
]; |
|
} |
|
|
|
// Equations based on NOAA’s Solar Calculator; all angles in radians. |
|
// http://www.esrl.noaa.gov/gmd/grad/solcalc/ |
|
|
|
function equationOfTime(centuries) { |
|
var e = eccentricityEarthOrbit(centuries), |
|
m = solarGeometricMeanAnomaly(centuries), |
|
l = solarGeometricMeanLongitude(centuries), |
|
y = Math.tan(obliquityCorrection(centuries) / 2); |
|
y *= y; |
|
return y * Math.sin(2 * l) |
|
- 2 * e * Math.sin(m) |
|
+ 4 * e * y * Math.sin(m) * Math.cos(2 * l) |
|
- 0.5 * y * y * Math.sin(4 * l) |
|
- 1.25 * e * e * Math.sin(2 * m); |
|
} |
|
|
|
function solarDeclination(centuries) { |
|
return Math.asin(Math.sin(obliquityCorrection(centuries)) * Math.sin(solarApparentLongitude(centuries))); |
|
} |
|
|
|
function solarApparentLongitude(centuries) { |
|
return (solarTrueLongitude(centuries) * degrees - 0.00569 - 0.00478 * Math.sin((125.04 - 1934.136 * centuries) * radians)) * radians; |
|
} |
|
|
|
function solarTrueLongitude(centuries) { |
|
return solarGeometricMeanLongitude(centuries) + solarEquationOfCenter(centuries); |
|
} |
|
|
|
function solarGeometricMeanAnomaly(centuries) { |
|
return (357.52911 + centuries * (35999.05029 - 0.0001537 * centuries)) * radians; |
|
} |
|
|
|
function solarGeometricMeanLongitude(centuries) { |
|
var l = (280.46646 + centuries * (36000.76983 + centuries * 0.0003032)) % 360; |
|
return (l < 0 ? l + 360 : l) / 180 * π; |
|
} |
|
|
|
function solarEquationOfCenter(centuries) { |
|
var m = solarGeometricMeanAnomaly(centuries); |
|
return (Math.sin(m) * (1.914602 - centuries * (0.004817 + 0.000014 * centuries)) |
|
+ Math.sin(m + m) * (0.019993 - 0.000101 * centuries) |
|
+ Math.sin(m + m + m) * 0.000289) * radians; |
|
} |
|
|
|
function obliquityCorrection(centuries) { |
|
return (meanObliquityOfEcliptic(centuries) * degrees + 0.00256 * Math.cos((125.04 - 1934.136 * centuries) * radians)) * radians; |
|
} |
|
|
|
function meanObliquityOfEcliptic(centuries) { |
|
return (23 + (26 + (21.448 - centuries * (46.8150 + centuries * (0.00059 - centuries * 0.001813))) / 60) / 60) * radians; |
|
} |
|
|
|
function eccentricityEarthOrbit(centuries) { |
|
return 0.016708634 - centuries * (0.000042037 + 0.0000001267 * centuries); |
|
} |
|
|
|
</script> |