Last active
December 16, 2015 08:59
-
-
Save jgbos/5409595 to your computer and use it in GitHub Desktop.
Time of Flight From North Korea
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"> | |
<title>Time of Flight from North Korea</title> | |
<style> | |
body { | |
background: #fcfcfc; | |
} | |
.background { | |
fill: #fff; | |
} | |
.outline { | |
stroke: #000; | |
stroke-width: 1px; | |
fill: none; | |
} | |
.land { | |
fill: #fff; | |
} | |
.land-glow { | |
fill: #000; | |
fill-opacity: .5; | |
filter: url(#glow); | |
} | |
.border { | |
stroke: #000; | |
stroke-width: .5px; | |
fill: none; | |
} | |
div { | |
width:300px; | |
float: left; | |
} | |
.circle { | |
stroke: #f00; | |
stroke-width: .5px; | |
fill: none; | |
} | |
.major { | |
stroke-width: 1.5px; | |
stroke-dasharray: none; | |
} | |
.graticule { | |
stroke: #99f; | |
stroke-width: .5px; | |
stroke-opacity: .3; | |
fill: none; | |
} | |
.caption { | |
font-style: italic; | |
} | |
p { | |
position: absolute; | |
top: 50px; | |
color: #000; | |
font-family: Arial, Helvetica, sans-serif; | |
font-weight: bold; | |
font-size: 14px; | |
text-align: center; | |
width: 300px; | |
} | |
</style> | |
<body> | |
<div id="map1"><p> Equidistant, Inertial</div> | |
<div id="map2"><p> Equidistant, Rotating</div> | |
<div id="map3"><p> Equal-??, Rotating</div> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script src="http://d3js.org/topojson.v1.min.js"></script> | |
<script> | |
var earth = { | |
Re: 6371000, | |
mu: 3.986004418e14, | |
rotation_rate : 7292115.1467e-11 | |
}; | |
var width = 1200, | |
height = 500; | |
var origin = [127.2565, 40.2012], | |
degrees = 180 / Math.PI, | |
radians = Math.PI / 180; | |
var equidistant = d3.geo.azimuthalEquidistant() | |
.translate([150,250]) | |
.clipAngle(120); | |
var equiflight = d3.geo.projection(function(λ, φ) { | |
var cosλ = Math.cos(λ), | |
cosφ = Math.cos(φ), | |
c = Math.acos(cosλ * cosφ); | |
if (c < 10*radians){ | |
tf = c; | |
} | |
else { | |
tf = c && flightTime(origin,c); | |
} | |
var p = toLLA(rotate([Math.cos(φ)*Math.cos(λ), | |
Math.cos(φ)*Math.sin(λ), | |
Math.sin(φ)], tf)); | |
var c = Math.acos(Math.cos(p[0]*radians) * Math.cos(p[1]*radians)), | |
k = c && c / Math.sin(c); | |
return [ | |
k * cosφ * Math.sin(λ), | |
k * Math.sin(φ) | |
]; | |
}) | |
.translate([300,250]) | |
.clipAngle(120); | |
function northKorea(projection) { | |
return projection | |
.scale(70) | |
.rotate([-origin[0], -origin[1], 0]) | |
.precision(.1); | |
} | |
var path = d3.geo.path().pointRadius(2.5); | |
circle = d3.geo.circle().origin(origin), | |
format = d3.format(",d"); | |
var ellipse = function (data,φ){ | |
data.coordinates[0] = data.coordinates[0].map(function (d){ | |
var tf = flightTime(origin,φ*radians), | |
xeci = toCartesian(d), | |
xecef = rotate(xeci, tf); // rotates earth | |
return toLLA(xecef); | |
}); | |
return data; | |
} | |
var svg = d3.selectAll("#map1, #map2, #map3") | |
.data([equidistant, equidistant, equiflight].map(northKorea)) | |
.append("svg"); | |
svg.each(function (projection){ | |
var t = projection.translate(); | |
d3.select(this) | |
.attr("width", 2 * t[0]) | |
.attr("height", 2 * t[1]); | |
}) | |
svg.append("filter") | |
.attr("id", "glow") | |
.append("feGaussianBlur") | |
.attr("stdDeviation", 3); | |
svg.append("path") | |
.datum({type: "Sphere"}) | |
.attr("class", "background"); | |
var g = svg.append("g").attr("class","map"); | |
svg.append("path") | |
.attr("class", "graticule") | |
.datum(d3.geo.graticule()()); | |
svg.each(function (projection,k){ | |
var angle = projection.clipAngle(), | |
t = projection.translate(), | |
c = function (d){ | |
if (k==0){ | |
return circle.angle(d)(); | |
} | |
else { | |
return ellipse(circle.angle(d)(),d); | |
} | |
}; | |
var g = d3.select(this); | |
var δ = 1e6 / earth.Re * degrees; | |
g.selectAll("path.circle") | |
.data(d3.range(δ, angle, δ)) | |
.enter().append("path") | |
.datum(function(d) { return c(d);}) | |
.attr("class", function(_, i) { return (i + 1) % 5 ? "circle" : "major circle"; }); | |
g.selectAll("text") | |
.data(projection.clipAngle() > 90 ? [5000, 10000] : [5000]) | |
.enter().append("text") | |
.attr("dy", "-.35em") | |
.append("textPath") | |
.attr("xlink:href", function(_, i) { return "#text-" + angle + "-" + i; }) | |
.text(function(d) { return format(d) + "km"; }); | |
}); | |
svg.append("path") | |
.datum({type: "Sphere"}) | |
.attr("class", "outline"); | |
svg.append("path") | |
.datum({type: "Point", coordinates: origin}); | |
svg.each(redraw); | |
d3.json("/d/4090846/world-50m.json", function(error, world) { | |
var land = topojson.feature(world, world.objects.land); | |
g.append("path") | |
.datum(land) | |
.attr("class", "land-glow"); | |
g.append("path") | |
.datum(land) | |
.attr("class", "land"); | |
g.append("path") | |
.datum(topojson.mesh(world, world.objects.countries)) | |
.attr("class", "border"); | |
g.each(redraw); | |
}); | |
function redraw(projection) { | |
d3.select(this).selectAll("path").attr("d", path.projection(projection)); | |
} | |
function rotate(pos,time){ | |
var ω = earth.rotation_rate, | |
ct = Math.cos(time*ω), | |
st = Math.sin(time*ω); | |
return [ | |
ct * pos[0] + st*pos[1], | |
-st * pos[0] + ct*pos[1], | |
pos[2] | |
]; | |
} | |
function flightTime(launch, φ){ | |
// Earth Parameters | |
var μ = earth.mu, | |
Re = earth.Re, | |
γ = 45 * Math.PI/180; | |
// Calculate earth center vector length at latitude, longitude | |
var R0 = toCartesian(launch), | |
r0 = Math.sqrt(R0.reduce(function (a,b){return a+b*b;})); | |
// Velocity of Target | |
var V = Math.sqrt(μ*(1-Math.cos(φ))/ (r0*Math.cos(γ)*(r0*Math.cos(γ)/Re - Math.cos(φ+γ)))); | |
// Constant to calculate flight time | |
var λ = r0*V*V/μ; | |
// Calculate Flight Time | |
var cg = Math.cos(γ), | |
sg = Math.sin(γ), | |
tg = Math.tan(γ), | |
cp = Math.cos(φ), | |
sp = Math.sin(φ), | |
cgp = Math.cos(γ + φ), | |
cot = 1 / Math.tan(φ/2); | |
var first_term = (tg*(1-cp) + (1-λ)*sp) / ((2-λ)*((1-cp)/(λ*cg*cg) + cgp/cg)), | |
sec_term = 2*cg / (λ*Math.pow(2/λ-1,1.5)) * Math.atan2(Math.sqrt(2/λ-1),(cg*cot-sg)); | |
return r0 / (V*cg) * (first_term + sec_term); | |
} | |
function toCartesian(gc) { | |
var lat = gc[1]*radians, | |
lon = gc[0]*radians, | |
R = earth.Re; | |
var x = R*Math.cos(lat)*Math.cos(lon), | |
y = R*Math.cos(lat)*Math.sin(lon), | |
z = R*Math.sin(lat); | |
return [x,y,z]; | |
} | |
function toLLA(position){ | |
var x = position[0], | |
y = position[1], | |
z = position[2], | |
r = Math.sqrt(x*x + y*y); | |
// Longitude | |
var lon = Math.atan2(y, x), | |
lat = Math.atan2(z,r); | |
return [lon*degrees, lat*degrees]; | |
} | |
d3.select(self.frameElement).style("height", height + "px"); | |
d3.select(self.frameElement).style("width", width + "px"); | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment