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; | |
| } |