This gist demonstrate how to create a celestial sphere using the orthographic projection.
This gist uses a subset of the stars visible to the naked eye from the combined catalog HYG Databse. To generate the subset, run the command:
make hyg.json
This gist demonstrate how to create a celestial sphere using the orthographic projection.
This gist uses a subset of the stars visible to the naked eye from the combined catalog HYG Databse. To generate the subset, run the command:
make hyg.json
<html> | |
<head> | |
<title>Celestial Sphere</title> | |
<link rel="stylesheet" type="text/css" href="styles.css"> | |
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> | |
</head> | |
<body> | |
<div id="map"></div> | |
<script type="text/javascript"> | |
// Set the width and height of the SVG container | |
var width = 400, | |
height = 400; | |
// Select the container div and append the SVG element | |
var div = d3.select('#map'), | |
svg = div.append('svg').attr('width', width).attr('height', height), | |
grp = svg.append('g').attr('class', 'gmap'); | |
// Add a lighting effect to give the circle a spherical aspect | |
var filter = svg.append('filter').attr('id', 'lightMe'); | |
filter.append('feDiffuseLighting') | |
.attr('in', 'SourceGraphic') | |
.attr('result', 'light') | |
.attr('lighting-color', 'white') | |
.append('fePointLight') | |
.attr('x', 0.85 * width) | |
.attr('y', 0.85 * height) | |
.attr('z', 50); | |
filter.append('feComposite') | |
.attr('in', 'SourceGraphic') | |
.attr('in2', 'light') | |
.attr('operator', 'arithmetic') | |
.attr('k1', '1') | |
.attr('k2', '0') | |
.attr('k3', '0') | |
.attr('k4', '0'); | |
// Projectioon and Path Generator | |
// ------------------------------ | |
// Store the current rotation | |
var rotate = {x: 0, y: 90}; | |
// Create and configure an instance of the orthographic projection | |
var projection = d3.geo.orthographic() | |
.scale(width / 2) | |
.translate([width / 2, height / 2]) | |
.clipAngle(90) | |
.rotate([rotate.x / 2, -rotate.y / 2]); | |
// Create and configure the geographic path generator | |
var path = d3.geo.path().projection(projection); | |
// Overlay | |
// ------- | |
var overlay = svg.selectAll('circle').data([rotate]) | |
.enter().append('circle') | |
.attr('transform', 'translate(' + [width / 2, height / 2] + ')') | |
.attr('r', width / 2) | |
.attr('filter', 'url(#lightMe)') | |
.attr('class', 'overlay'); | |
// Globe Outline | |
// ------------- | |
var globe = grp.selectAll('path.globe').data([{type: 'Sphere'}]) | |
.enter().append('path') | |
.attr('class', 'globe') | |
.attr('d', path); | |
// Graticule | |
// --------- | |
var graticule = d3.geo.graticule(); | |
// Draw graticule lines | |
grp.selectAll('path.graticule').data([graticule()]) | |
.enter().append('path') | |
.attr('class', 'graticule') | |
.attr('d', path); | |
// Load the stellar catalog | |
d3.json('hyg.json', function(error, data) { | |
// Handle errors getting and parsing the data | |
if (error) { return error; } | |
// Compute the radius scale. The radius will be proportional to | |
// the aparent magnitude | |
var rScale = d3.scale.linear() | |
.domain(d3.extent(data.features, function(d) { return d.properties.mag; })) | |
.range([3, 1]); | |
// Compute the radius for the point features | |
path.pointRadius(function(d) { | |
return d.properties ? rScale(d.properties.mag) : 1; | |
}); | |
// Stars | |
// ----- | |
grp.selectAll('path.star').data(data.features) | |
.enter().append('path') | |
.attr('class', 'star') | |
.attr('d', path); | |
// Drag Behavior | |
// ------------- | |
var dragBehavior = d3.behavior.drag() | |
.origin(Object) | |
.on('drag', function(d) { | |
projection.rotate([(d.x = d3.event.x) / 2, -(d.y = d3.event.y) / 2]); | |
svg.selectAll('path').attr('d', function(u) { | |
// The circles are not properly generated when the | |
// projection has the clipAngle option set. | |
return path(u) ? path(u) : 'M 10 10'; | |
}); | |
}); | |
// Add the drag behavior to the overlay | |
overlay.call(dragBehavior); | |
}); | |
</script> | |
</body> | |
</html> |
hygfull.csv: | |
curl -LO 'https://github.com/astronexus/HYG-Database/raw/master/hygfull.csv' | |
hyg.json: hygfull.csv | |
python parse-catalog.py |
import csv | |
import json | |
import os | |
class CSVReader: | |
"""Iterate through the rows of the CSV file. | |
The iterator returns a dictionary with the column names as keys. The | |
values of the dictionary are strings, they may need to be casted. | |
""" | |
def __init__(self, csvpath): | |
self.csvfile = open(csvpath, 'r') | |
self.reader = csv.reader(self.csvfile, delimiter=',') | |
self.header = self.reader.next() | |
def __iter__(self): | |
return self | |
def next(self): | |
"""Returns a dictionary with column names as keys and the cell contents | |
as values. The values are strings. | |
""" | |
# Retrieve the next row from the file. | |
try: | |
row = self.reader.next() | |
except StopIteration: | |
self.csvfile.close() | |
raise | |
item = dict() | |
for ncol in range(len(row)): | |
item[self.header[ncol]] = row[ncol] | |
return item | |
if __name__ == '__main__': | |
# Create the CSV reader | |
reader = CSVReader('hygfull.csv') | |
# The data will be stored as a feature collection | |
feature = {'type': 'FeatureCollection', 'features': []} | |
for row in reader: | |
# Compute the magnitude and equivalent (ish) longitude and latitude | |
mag = float(row['Mag']) | |
lon = 360 * float(row['RA']) / 24 - 180 | |
lat = float(row['Dec']) | |
# Store only the stars visible to the naked eye | |
if (-1 < mag) and (mag < 5): | |
feature['features'].append({ | |
'type': 'Feature', | |
'properties': {'mag': mag}, | |
'geometry': {'type': 'Point', 'coordinates': [lon, lat]} | |
}) | |
# Store the stars as a GeoJSON file | |
jsonfile = open('hyg.json', 'w') | |
json.dump(feature, jsonfile) | |
jsonfile.close() | |
body { | |
background-color: #eee; | |
} | |
#map { | |
width: 400px; | |
height: 400px; | |
display: block; | |
margin-left: auto; | |
margin-right: auto; | |
margin-top: 40px; | |
margin-bottom: 10px; | |
} | |
.graticule { | |
fill: none; | |
stroke: #0053ad; | |
stroke-width: 1px; | |
} | |
.globe { | |
fill: #060061; | |
} | |
.star { | |
fill: #fff; | |
fill-opacity: 0.9; | |
} | |
.overlay { | |
fill: #005fc7; | |
fill-opacity: 0.4; | |
} |