This interrupted Mollweide projection features the solar terminator for any date and time.
-
-
Save johan/4645873 to your computer and use it in GitHub Desktop.
Sunny side of the Earth, for any date and time.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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