Skip to content

Instantly share code, notes, and snippets.

@johan
Forked from johan/README.md
Last active December 11, 2015 18:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save johan/4645873 to your computer and use it in GitHub Desktop.
Save johan/4645873 to your computer and use it in GitHub Desktop.
Sunny side of the Earth, for any date and time.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
input { position: absolute; }
#d { left: 326px; top: 24px; }
#t { left: 370px; top: 61px; }
.night {
stroke: rgb(35, 65, 90); /* steelblue at 50% brightness */
fill: rgb(35, 65, 90);
fill-opacity: .3;
}
.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;
}
.boundary {
fill: none;
stroke: #a5967e;
}
</style>
<body marginwidth="0" marginheight="0">
<input type="date" id="d"/>
<input type="time" id="t"/>
<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/topojson.v0.min.js"></script>
<script>
var time = new Date, last = new Date
, width = 960
, height = 500
;
// Interrupted Mollweide, http://bl.ocks.org/4498187
var projection = d3.geo.interrupt(d3.geo.mollweide.raw)
.lobes([[ // northern hemisphere
[[-180, 0], [-100, 90], [ -40, 0]],
[[ -40, 0], [ 30, 90], [ 180, 0]]
], [ // southern hemisphere
[[-180, 0], [-160, -90], [-100, 0]],
[[-100, 0], [ -60, -90], [ -20, 0]],
[[ -20, 0], [ 20, -90], [ 80, 0]],
[[ 80, 0], [ 140, -90], [ 180, 0]]
]])
.translate([width / 2, height / 2]);
var circle = d3.geo.circle()
.angle(89.9975572);
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var defs = svg.append("defs");
defs.append("path")
.datum({type: "Sphere"})
.attr("id", "sphere")
.attr("d", path);
defs.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")
.selectAll("path")
.data(graticule.lines)
.enter().append("path")
.attr("d", path);
svg.append("use")
.attr("class", "foreground")
.attr("xlink:href", "#sphere");
d3.json("/d/4090846/world-50m.json", function(error, world) {
svg.insert("path", ".graticule")
.datum(topojson.object(world, world.objects.land))
.attr("clip-path", "url(#clip)")
.attr("class", "land")
.attr("d", path);
svg.insert("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);
var at_d = document.querySelector('#d')
, at_t = document.querySelector('#t')
;
update(time);
redraw();
setInterval(redraw, 1000);
at_d.addEventListener('input', redraw, false);
at_t.addEventListener('input', redraw, false);
function z(n, z) { z = z || 2; return ('0000'+n).slice(-z); }
function date(t) {
if (!t) t = document.querySelector('#d').value;
if (typeof t === 'string') return t;
return [z(t.getFullYear(), 4), z(t.getMonth()+1), z(t.getDate())].join('-');
}
function hhmm(t) {
if (!t) t = document.querySelector('#t').value;
if (typeof t === 'string') return t;
return z(t.getHours()) +':'+ z(t.getMinutes());
}
function update(t) {
at_d.value = date(t);
at_t.value = hhmm(t);
}
function redraw(e) {
if (typeof e === 'object') {
var t = (date() +' '+ hhmm()).split(/[- :]/).map(Number), y = t.shift();
t = new Date(y, t.shift()-1, t.shift(), t.shift(), t.shift());
t.setFullYear(y); // compensate for inane js year 0..99 handling
if (!isNaN(t)) time = t;
}
else { // update clock display one tick (dt)
var now = new Date, dt = now - last;
time = new Date(time.getTime() + dt);
update(time);
}
last = new Date;
var sunPos = getSolarWGSPosition(time);
var darknessAngle = 90 - Math.asin( (constants.meanRsun - constants.meanRearth) / sunPos.range ) * constants.rad2deg;
night.datum(circle.origin([ -180+sunPos.lon, -sunPos.lat ]).angle(darknessAngle)).attr("d", path);
}
});
var getSolarWGSPosition = function getSolarWGSPosition(time) {
var eci_pos = getSolarECI(time);
var wgs_pos = ECItoWGS84(eci_pos, time);
return {lat: wgs_pos.latitude, lon: wgs_pos.longitude, range: eci_pos.w};
};
var getSolarECI = function getSolarECI(time) {
var mjd, year, T, M, L, e, C, O, Lsa, nu, R, eps;
var jd_utc = unix2jd( time / 1000 );
mjd = jd_utc - 2415020.0;
year = 1900 + mjd / 365.25;
T = (mjd + constants.deltaUTCTT / 86400) / 36525.0;
M = (( 358.47583 + ((35999.04975 * T) % 360) - (0.000150 + 0.0000033 * T) * (T*T) ) % 360) * constants.deg2rad;
L = ((279.69668 + ((36000.76892 * T) % 360.0) + 0.0003025 * (T*T) ) % 360) * constants.deg2rad;
e = 0.01675104 - (0.0000418 + 0.000000126 * T) * T;
C = ((1.919460 - (0.004789 + 0.000014 * T) * T) * Math.sin(M) + (0.020094 - 0.000100 * T) * Math.sin(2 * M) + 0.000293 * Math.sin(3 * M)) * constants.deg2rad;
O = ((259.18 - 1934.142 * T) % 360) * constants.deg2rad;
Lsa = (L + C -((0.00569 - 0.00479 * Math.sin(O)) * constants.deg2rad)) % (2*Math.PI);
nu = (M + C) % (2*Math.PI);
R = 1.0000002 * (1.0 - (e*e)) / (1.0 + e * Math.cos(nu));
eps = ((23.452294 - (0.0130125 + (0.00000164 - 0.000000503 * T) * T) * T + 0.00256 * Math.cos(O)) * constants.deg2rad);
R = constants.au * R;
var x = R * Math.cos (Lsa);
var y = R * Math.sin (Lsa) * Math.cos (eps);
var z = R * Math.sin (Lsa) * Math.sin (eps);
var w = R;
return { x : x, y : y, z : z, w : w }
};
var ECItoWGS84 = function ECItoWGS84(eci, time) {
var jd_utc = unix2jd( time / 1000 );
var theta = Math.atan2(eci.y, eci.x); // radians
var lon = (theta - thetaG_JD(jd_utc)) % (2*Math.PI); // radians
var r = Math.sqrt( (eci.x*eci.x) + (eci.y*eci.y) );
var e2 = constants.f * (2 - constants.f);
var lat = Math.atan2(eci.z, r); // radians
var sin_phi, phi, c;
do {
phi = lat;
sin_phi = Math.sin(phi);
c = 1 / Math.sqrt(1 - e2 * (sin_phi*sin_phi));
lat = Math.atan2(eci.z + constants.eqRearth * c * e2 * sin_phi, r);
} while (Math.abs(lat - phi) > 1e-10);
var alt = r / Math.cos(lat) - constants.eqRearth * c; // kilometers
if (lat > (Math.PI / 2)) {
lat -= (2 * Math.PI);
}
if (lon < -Math.PI) {
lon += 2*Math.PI;
}
return {
latitude: lat * constants.rad2deg,
longitude: lon * constants.rad2deg,
altitude: alt,
theta: theta * constants.rad2deg
};
};
// ** Astronomical, Geodetic & Mathematical Constants ** //
var constants = {
au: 149597870700, // [m] Astronomical unit
deltaUTCTT: 67.184,
deg2rad: 0.017453292519943295,
rad2deg: 57.29577951308232,
omega_E: 1.00273790934,
f: 1/298.257222101, // Earth, reciprocal of flattening IERS 2010
eqRearth: 6378.1366, // Equatorial radius
meanRearth: 6371.0, // Mean radius
meanRsun: 1.392684e8, // Mean radius sun
omega: 7.292115e-5, // Nominal mean angular vel. of Earth rotation
};
// Converts a UNIX timestamp to JD (Julian Date)
var unix2jd = function unix2jd(timestamp) {
return (timestamp / 86400.0) + 2440587.5;
};
var thetaG_JD = function thetaG_JD(jd) {
// Reference: The 1992 Astronomical Almanac, page B6.
var UT, TU, GMST;
UT = (jd + 0.5) % 1;
jd = jd - UT;
TU = (jd - 2451545.0) / 36525;
GMST = 24110.54841 + TU * (8640184.812866 + TU * (0.093104 - TU * 6.2e-6));
GMST = (GMST + 86400 * constants.omega_E * UT) % 86400;
return (2*Math.PI) * GMST / 86400;
};
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment