The current solar terminator is shown.
Thanks to Ben Elsen and NOAA for help implementing the correct equations for the position of the sun, which turned out to be quite a bit more complicated than I expected.
The current solar terminator is shown.
Thanks to Ben Elsen and NOAA for help implementing the correct equations for the position of the sun, which turned out to be quite a bit more complicated than I expected.
| <!DOCTYPE html> | |
| <meta charset="utf-8"> | |
| <style> | |
| body { | |
| margin: 0; | |
| } | |
| .map { | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .layer { | |
| position: absolute; | |
| } | |
| .tile { | |
| position: absolute; | |
| width: 256px; | |
| height: 256px; | |
| } | |
| .overlay { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| pointer-events: none; | |
| } | |
| .night { | |
| stroke: none; | |
| fill: #000; | |
| fill-opacity: .7; | |
| filter: url(#blur); | |
| } | |
| </style> | |
| <body> | |
| <script src="http://d3js.org/d3.v3.min.js"></script> | |
| <script src="http://d3js.org/d3.geo.tile.v0.min.js"></script> | |
| <script> | |
| var width = Math.max(960, window.innerWidth), | |
| height = Math.max(500, window.innerHeight), | |
| prefix = prefixMatch(["webkit", "ms", "Moz", "O"]); | |
| var tile = d3.geo.tile() | |
| .size([width, height]); | |
| var width = Math.max(960, window.innerWidth), | |
| height = Math.max(500, window.innerHeight), | |
| prefix = prefixMatch(["webkit", "ms", "Moz", "O"]); | |
| var tile = d3.geo.tile() | |
| .size([width, height]); | |
| var projection = d3.geo.mercator(); | |
| var zoom = d3.behavior.zoom() | |
| .scale(1 << 11) | |
| .scaleExtent([1 << 9, 1 << 23]) | |
| .translate([width / 2, height / 2]) | |
| .on("zoom", redraw); | |
| var map = d3.select("body").append("div") | |
| .attr("class", "map") | |
| .style("width", width + "px") | |
| .style("height", height + "px") | |
| .call(zoom); | |
| var layer = map.append("div") | |
| .attr("class", "layer"); | |
| var π = Math.PI, | |
| radians = π / 180, | |
| degrees = 180 / π; | |
| var circle = d3.geo.circle() | |
| .angle(90); | |
| var path = d3.geo.path() | |
| .projection(projection); | |
| var svg = d3.select("body").append("svg") | |
| .attr("class", "overlay") | |
| .attr("width", width) | |
| .attr("height", height); | |
| svg.append("defs") | |
| .append("filter") | |
| .attr("id", "blur") | |
| .append("feGaussianBlur") | |
| .attr("in", "SourceGraphic") | |
| .attr("stdDeviation", 15); | |
| var night = svg.append("path") | |
| .attr("class", "night") | |
| .attr("d", path); | |
| redraw(); | |
| setInterval(redraw, 60000); | |
| function redraw() { | |
| var tiles = tile | |
| .scale(zoom.scale()) | |
| .translate(zoom.translate()) | |
| (); | |
| projection | |
| .scale(zoom.scale() / 2 / Math.PI) | |
| .translate(zoom.translate()); | |
| var image = layer | |
| .style(prefix + "transform", matrix3d(tiles.scale, tiles.translate)) | |
| .selectAll(".tile") | |
| .data(tiles, function(d) { return d; }); | |
| image.exit() | |
| .remove(); | |
| image.enter().append("img") | |
| .attr("class", "tile") | |
| .attr("src", function(d) { return "http://" + ["a", "b", "c", "d"][Math.random() * 4 | 0] + ".tiles.mapbox.com/v3/examples.map-vyofok3q/" + d[2] + "/" + d[0] + "/" + d[1] + ".png"; }) | |
| .style("left", function(d) { return (d[0] << 8) + "px"; }) | |
| .style("top", function(d) { return (d[1] << 8) + "px"; }) | |
| .on('dragstart', function() { d3.event.preventDefault(); }); | |
| night.datum(circle.origin(antipode(solarPosition(new Date)))) | |
| .attr("d", path); | |
| } | |
| function matrix3d(scale, translate) { | |
| var k = scale / 256, r = scale % 1 ? Number : Math.round; | |
| return "matrix3d(" + [k, 0, 0, 0, 0, k, 0, 0, 0, 0, k, 0, r(translate[0] * scale), r(translate[1] * scale), 0, 1 ] + ")"; | |
| } | |
| function prefixMatch(p) { | |
| var i = -1, n = p.length, s = document.body.style; | |
| while (++i < n) if (p[i] + "Transform" in s) return "-" + p[i].toLowerCase() + "-"; | |
| return ""; | |
| } | |
| 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) - (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) + 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> |