index.html
<html>
<head>
<meta charset="utf-8">
<link href="stars.css" rel="stylesheet">
</head>
<body>
<h1></h1>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="stars.js"></script>
</body>
</html>
18b0ebf01dac2d1e5922.iml
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
stars.coffee
http = require 'http'
fs = require 'fs'
async = require 'async'
_ = require 'underscore'
httpGet = (host,path, callback) ->
data = ""
if debug
console.log "getting", path
httpGet.counter++
http.get(
hostname: host,
port: 80
path: path
method: "GET"
headers: 'user-agent': 'Mozilla/5.0'
, (response) ->
response.setEncoding "utf8"
response.on "data", (chunk) ->
data += chunk
response.on "end", ->
if debug
httpGet.counter--
console.log "got", path, "(" + httpGet.counter + " requests left)"
callback null, data
).on("error", (e) ->
console.log "problem with request, waiting 5s: " + e.message + " on " + path
setTimeout (-> httpGet host, path, callback), 5000
).end()
debug = true
httpGet.counter = 0
extractAllMatches = (data, expr) ->
link = expr.exec data
arr = []
while link?
arr.push link
link = expr.exec data
return arr
getColors = (callback) ->
httpGet "isthe.com", "/chongo/tech/astro/HR-temp-mass-table-bytemp.html", (err,data)->
_.filter data.split('\n'), (line) -> line.indexOf('<TR><TD ALIGN="LEFT">') > -1
expr = /ALIGN="LEFT">([\w\d]+).*FONT COLOR="(#[\w\d]{6})"/g
colors = extractAllMatches data, expr
colors = colors.map (line) -> sc: line[1], color: line[2]
callback null, colors
addLines = (starData, callback) ->
linesTxt = fs.readFileSync "constellations.txt",{encoding: "utf8"}
zodiac = ["Aries", "Taurus", "Gemini" , "Cancer", "Leo", "Virgo", "Libra", "Scorpius", "Sagittarius", "Capricornus", "Aquarius", "Pisces"]
starData = starData.map (constellation) ->
constellation.lines = []
constellation.zodiac = false
name = (constellation.name.split ' ').join ''
lines = extractAllMatches linesTxt, new RegExp('Constellation\\(\"(' + name + ')\",([\\d\\.-]+),([\\d\\.-]+),([\\d\\.-]+),([\\d\\.-]+)\\);', 'g')
lines = lines.map (line) ->
dec1: line[2],
ra1: ((line[3] * 15).toFixed 4),
dec2: line[4],
ra2: ((line[5] * 15).toFixed 4)
constellation.lines = lines if lines
constellation.zodiac = true if _.find zodiac, (zodName) -> zodName is name
constellation
callback null, starData
addBoundaries = (starData, callback) ->
getRaDec = (d) ->
coord = extractAllMatches d, /([\d]+)\s([\d]+)\s([\d\.]+)\|([\s-\d\.]+)\|[\w]{3}/g
coord = coord.map (c) ->
h = c[1]|0
m = (c[2]|0) / 60
s = +c[3] / 3600
ra = h + m + s
ra = ra * 15
dec = +c[4]
[(+ra.toFixed 4), (+dec.toFixed 4)]
coord.push coord[0]
coord
getBoundary = (constellation, callback) ->
constellation.boundary = []
url = constellation.abbr.toLowerCase()
if url is 'ser'
httpGet "www.iau.org", "/static/public/constellations/txt/ser1.txt", (error, d1) ->
constellation.boundary.push(getRaDec d1)
httpGet "www.iau.org", "/static/public/constellations/txt/ser2.txt", (error, d2) ->
constellation.boundary.push(getRaDec d2)
callback null, constellation
else
httpGet "www.iau.org", "/static/public/constellations/txt/#{url}.txt", (error, data) ->
constellation.boundary = getRaDec data
callback null, constellation
async.map starData, getBoundary, callback
async.waterfall [
(callback) ->
httpGet "en.wikipedia.org", "/wiki/88_modern_constellations", callback
(data, callback) ->
expr = /<tr>\n<td><a href="\/wiki\/([\w%_]+)(?:_\(constellation\))?"\stitle="([\w\sö]+)(?:\s\(constellation\))?".*\n.*\n<td>([\w]{3})<\/td>/g
callback null, extractAllMatches data, expr
(constellations, callback) ->
async.waterfall [
getColors
(sclasses, callback) ->
getConstellation = (link, callback) ->
[unused, url, humanReadable, abbr] = link
httpGet "en.wikipedia.org", "/wiki/List_of_stars_in_#{url}", (error, data) ->
expr = /<td>([\d]+)<sup>h<\/sup> ([\d]+)<sup>m<\/sup> ([\d\.]+)<sup>s<\/sup><\/td>\n<td>([\d+−]+)° ([\d]+)′ ([\d\.]+)″<\/td>\n<td>([\d+−\.]+)<\/td>\n<td>[\d+−\.]+<\/td>\n<td>[\d]+<\/td>\n<td>([\w\d\s\.+-:]+)<\/td>/g
stars = extractAllMatches data, expr
stars = stars.map (star) ->
h = star[1]|0
m = (star[2]|0) / 60
s = +star[3] / 3600
ra = h + m + s
ra = ra * 15
deg = star[4]
isNegativeDeg = 0 is deg.indexOf '−'
deg = deg.slice 1
deg = deg|0
min = star[5]|0
sec = +star[6]
dec = deg + min / 60 + sec / 3600
dec *= -1 if isNegativeDeg
mag = +star[7]
sclass = star[8]
thisClass = _.find sclasses, (cl) -> cl.sc is sclass
if thisClass
color = thisClass.color
else
color = "white"
mag: mag, ra: (+ra.toFixed 4), dec: (+dec.toFixed 4), class: star[8], color: color
callback null, name: humanReadable, stars: stars, abbr: abbr
async.map constellations, getConstellation, callback
addLines
addBoundaries
], (err, results) ->
callback null, results
], (err, results) ->
fs.writeFile "starData.json", JSON.stringify results
stars.css
body {
background: #000313;
}
h1 {
position: absolute;
top: 280px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 18px;
text-align: center;
width: 960px;
color: #fff;
}
stars.js
var width = 960,
height = 500;
var projection = d3.geo.stereographic()
.scale(600)
var canvas = d3.select("body").append("canvas")
.attr("width", width)
.attr("height", height)
var c = canvas.node().getContext("2d")
var path = d3.geo.path()
.projection(projection)
.context(c)
var graticule = d3.geo.graticule()
.step([15, 15])
var title = d3.select("h1")
d3.json("starData.json", function(error, data) {
var geoConstellations = []
data.forEach(function (constellation) {
var geometries = []
constellation.stars.map(function (star) {
if (star.mag < 6)
geometries.push({
type: 'Point',
coordinates: [-star.ra, star.dec],
properties: {
color: star.color,
mag: Math.pow(1.3, 4 - star.mag)
}
})
})
var lines = constellation.lines.map(function (line) {
var p1 = [-line.ra1, line.dec1]
var p2 = [-line.ra2, line.dec2]
return [p1, p2]
})
if (!lines.length){
console.log(constellation.name)
}
geometries.push({
type: "MultiLineString",
coordinates: lines
})
if (constellation.name == 'Serpens'){
var bound1 = constellation.boundary[0].map(function (coords) {
return [-coords[0], coords[1]]
})
var bound2 = constellation.boundary[1].map(function (coords) {
return [-coords[0], coords[1]]
})
geometries.push({
type: "LineString",
coordinates: bound1
})
geometries.push({
type: "LineString",
coordinates: bound2
})
} else {
var boundLines = constellation.boundary.map(function (coords) {
return [-coords[0], coords[1]]
})
geometries.push({
type: "LineString",
coordinates: boundLines
})
}
geoConstellations.push({
type: 'Feature',
geometry: {
type: 'GeometryCollection',
geometries: geometries
},
properties: {
name: constellation.name,
zodiac: constellation.zodiac
}
}
)
})
ready(geoConstellations)
})
function makeRadialGradient(x, y, r, color) {
var radialgradient = c.createRadialGradient(x, y, 0, x, y, r)
radialgradient.addColorStop(0.2, color)
radialgradient.addColorStop(0.5,'rgba(0,3,19,1)')
radialgradient.addColorStop(1,'rgba(0,3,19,0)')
c.fillStyle = radialgradient
}
function ready (constellations) {
var i = -1,
n = constellations.length;
(function transition() {
d3.transition()
.duration(1250)
.each("start", function() {
title.text(constellations[i = (i + 1) % n].properties.name)
})
.tween("rotate", function() {
var p = d3.geo.centroid(constellations[i]),
r = d3.interpolate(projection.rotate(), [-p[0], -p[1]])
return function(t) {
projection.rotate(r(t))
c.clearRect(0, 0, width, height)
c.strokeStyle = "#fff"
c.lineWidth = .2
c.beginPath(), path(graticule()), c.stroke()
c.lineWidth = .4
c.beginPath(), path({type: "LineString", coordinates: [[-180, 0], [-90, 0], [0, 0], [90, 0], [180, 0]]}), c.stroke()
c.beginPath(), path({type: "LineString", coordinates: [[0, -75], [0, 0], [0, 75]]}), c.stroke()
constellations.forEach(function(constellation){
constellation.geometry.geometries.forEach(function(geo){
if (geo.type == 'Point') {
makeRadialGradient(
projection(geo.coordinates)[0],
projection(geo.coordinates)[1],
geo.properties.mag + 1.2,
geo.properties.color)
path.pointRadius([geo.properties.mag + 2])
c.beginPath(), path(geo), c.fill();
} else if (geo.type == 'LineString'){
c.strokeStyle = '#05a'
c.lineWidth = .4
c.beginPath(), path(geo),c.stroke()
} else if (geo.type == 'MultiLineString'){
c.strokeStyle = (constellation.properties.zodiac)?'#f2f237':"#fff"
c.lineWidth = .4
c.beginPath(), path(geo), c.stroke();
}
})
})
c.strokeStyle = "#f00"
c.lineWidth = 1.2
constellations[i].geometry.geometries.forEach(function(geo){
if (geo.type == 'LineString'){
c.beginPath(), path(geo), c.stroke()
}
})
};
})
.transition()
.each("end", transition)
})();
}