Skip to content

Instantly share code, notes, and snippets.

@espinielli
Forked from mbostock/.block
Last active December 12, 2015 01:28
Show Gist options
  • Save espinielli/4690851 to your computer and use it in GitHub Desktop.
Save espinielli/4690851 to your computer and use it in GitHub Desktop.
Solar Terminator on Butterfly Map Projection

Forked from Mike Bostock's Solar Terminator gist. Changed map projection to Waterman's Butterfly.

It would be nice to:

  • fade transition from day to night
  • use satellite image as background (one for day area, the other with lights for the night area)
  • plot the position of the Sun [done] and the Moon, like here or here
  • use astronomical symbols for Sun [done] and Moon (there are SVG versions...)
<!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>
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