Skip to content

Instantly share code, notes, and snippets.

@pnavarrc
Last active January 9, 2021 02:39
Show Gist options
  • Save pnavarrc/9730300 to your computer and use it in GitHub Desktop.
Save pnavarrc/9730300 to your computer and use it in GitHub Desktop.
Celestial Sphere

Celestial Sphere

This gist demonstrate how to create a celestial sphere using the orthographic projection.

Data

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
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
<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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment