Creating an interactive star map

In this section, we will use the Stereographic projection and a star catalog to create an interactive celestial map. The Stereographic projection displays the sphere as seen from inside. A star map created with the Stereographic projection is shown in the following screenshot:

Creating an interactive star map

Celestial coordinate systems describe positions of celestial objects as seen from Earth. As the Earth rotates on its axis and around the Sun, the position of the stars relative to points on the surface of the Earth changes. Besides rotation and translation, a third movement called precession slowly rotates the Earth's axis by one degree every 72 years. The Equatorial coordinate system describes the position of stars by two coordinates, the declination and the angle from the projection of the Earth's equator to the poles. This angle is equivalent to the Earth's longitude. The right ascension is the angle measured from the intersection of the celestial equator to the ecliptic, measured eastward. The ecliptic is the projection of the Earth's orbit in the celestial sphere. The right ascension is measured in hours instead of degrees, but it is the equivalent of longitude.

Choosing our star catalog

To create our star map, we will use the HYG database, which is a celestial catalog that combines information from the Hipparcos Catalog, the Yale Bright Star Catalog, and the Gliese Catalog of Nearby Stars. This contains about 120,000 stars, most of which are not visible to the naked eye. The most recent version of the HYG database is available at https://github.com/astronexus/HYG-Database.

As we did in the previous sections, we will add targets to Makefile in order to download and parse the data files that we need. In order to filter and process the stars of the catalog, we wrote a small Python script that filters out the less bright stars and writes a GeoJSON file that translates the declination and right ascension coordinates to equivalent latitudes and longitudes. Note that due to the rotation of the Earth, the equivalent longitude is not related to the Earth's longitude, but it would be useful to create our visualization. We can compute a coordinate equivalent to the right ascension using the longitude = 360 * RA / 24 - 180 expression. The generated GeoJSON file will have the following structure:

{
  "type": "FeatureCollection", 
  "features": [
    {
      "geometry": {
        "type": "Point", 
        "coordinates": [-179.6006208, -77.06529438]
      },
      "type": "Feature", 
      "properties": {
        "color": 1.254, 
        "name": "", 
        "mag": 4.78
      }
    },
    ...
  ]  
}

In this case, every feature in the GeoJSON file is a point. We will begin by creating a chart using the Equirectangular projection to have a complete view of the sky while we are implementing the map and change the projection to stereographic later. We begin by loading the GeoJSON data and creating the svg container for the map:

d3.json('/chapter11/data/hyg.json', function(error, data) {

    // Handle errors getting and parsing the data
    if (error) { console.log(error); }

    // Container width and height
    var width = 600, height = 300;

    // Select the container div and creates the svg container
    var div = d3.select('#equirectangular'),
        svg = div.append('svg')
            .attr('width', width)
            .attr('height', height);

    // Creates an instance of the Equirectangular projection...
});

We create and configure an instance of the Equirectangular projection, setting the scale to display the entire sky in the svg container:

    // Creates an instance of the Equirectangular projection
    var projection = d3.geo.equirectangular()
        .scale(width / (2 * Math.PI))
        .translate([width / 2, height / 2]);

Drawing the stars

We will represent the stars with small circles, with bigger circles for brighter stars. For this, we need to create a scale for the radius, which will map the apparent magnitude of each star to a radius. Lower magnitude values correspond to brighter stars:

    // Magnitude extent
    var magExtent = d3.extent(data.features, function(d) {
        return d.properties.mag; 
    });

    // Compute the radius for the point features
    var rScale = d3.scale.linear()
        .domain(magExtent)
        .range([3, 1]);

By default, the path generator will create circles of constant radius for features of the Point type. We can configure the radius by setting the path's pathRadius attribute. As we might use the same path generator to draw point features other than stars, we will return a default value if the feature doesn't have the properties attribute:

    // Create and configure the geographic path generator
    var path = d3.geo.path()
        .projection(projection)
        .pointRadius(function(d) {
            return d.properties ? rScale(d.properties.mag) : 1;
        });

With our path generator configured, we can append the graticule lines and the features for the stars to the svg container:

    // Add graticule lines
    var graticule = d3.geo.graticule();

    svg.selectAll('path.graticule-black').data([graticule()])
        .enter().append('path')
        .attr('class', 'graticule-black')
        .attr('d', path);

    // Draw the stars in the chart
    svg.selectAll('path.star-black').data(data.features)
        .enter().append('path')
        .attr('class', 'star-black')
        .attr('d', path);

We obtain the following celestial map, which shows us the graticule and the stars as small black circles:

Drawing the stars

Celestial map created with the Equirectangular projection

Changing the projection and adding rotation

We will replace the Equirectangular projection with the Stereographic projection and add styles to the elements of the map to make it more attractive. We will use the drag behavior to allow the user to rotate the chart. As we did with the rotating globe, we will create a variable to store the current rotation of the projection:

    // Store the current rotation of the projection
    var rotate = {x: 0, y: 45};

We can create and configure an instance of the Stereographic projection. We will choose a suitable scale, translate the projection center to the center of the svg container, and clip the projection to show only a small part of the sphere at a time. We use the rotation variable to set the initial rotation of the projection:

    // Create an instance of the Stereographic projection
    var projection = d3.geo.stereographic()
        .scale(1.5 * height / Math.PI)
        .translate([width / 2, height / 2])
        .clipAngle(120)
        .rotate([rotate.x, -rotate.y]);

We won't duplicate the code to create the svg container, graticule, and features because it's very similar to the rotating globe from earlier. The complete code for this example is available in the chapter11/03-celestial-sphere file. In this example, we also have an invisible overlay. We create and configure a drag behavior instance and add event listeners for the drag gesture to the invisible overlay:

    // Create and configure the drag behavior
    var dragBehavior = d3.behavior.drag()
        .on('drag', drag);

    // Add event listeners for drag gestures to the overlay
    overlay.call(dragBehavior);

The drag function will be invoked when the user drags the map. For drag events, the d3.event object stores the gesture's x and y coordinates. We will transform the coordinates to horizontal and vertical rotations of the projection with the same method as the one used in the previous section:

    // Callback for drag gestures
    function drag(d) {
        // Compute the projection rotation angles 
        d.x = 180 * d3.event.x / width;
        d.y = -180 * d3.event.y / height;

        // Updates the projection rotation...
    }

We update the projection rotation and the paths of the stars and graticule lines. As we have clipping in this example, the path will be undefined for stars outside the clipping angle. In this case, we return a dummy svg command that just moves the drawing cursor to avoid getting errors:

        // Updates the projection rotation        
        projection.rotate([d.x, d.y]);

        // Update the paths for the stars and graticule lines
        stars.attr('d', function(u) {
            return path(u) ? path(u) : 'M 10 10';
        });
        lines.attr('d', path);

In the style sheet file, chapter11/maps.css, we have included styles for this map to display a dark blue background, light graticule lines, and white stars. The result is a rotating star map that displays a coarse approximation of how the stars look from Earth.

Changing the projection and adding rotation

Rotating star map created with the Stereographic projection

Adding colors and labels to the stars

We will create a fullscreen version of the star map. The source code for this version of the map is available in the chapter11/04-fullscreen file in the code bundle. For this example, we need to set the body element and the container div to cover the complete viewport. We set the width and height of the body, HTML, and the container div to 100 percent and set the padding and margins to zero. To create the svg element with the correct size, we need to retrieve the width and height in pixels, which are computed by the browser when it renders the page:

    // Computes the width and height of the container div
    var width = parseInt(div.style('width'), 10),
        height = parseInt(div.style('height'), 10);

We create the projection and the path generator as done earlier. In this version, we will add colors to the stars. Each star feature contains the attribute color, which indicates the color index of the star. The color index is a number that characterizes the color of the star. We can't compute a precise scale for the color index, but we will use a color scale that approximates the colors:

    // Approximation of the colors of the stars
    var cScale = d3.scale.linear()
        .domain([-0.3, 0, 0.6, 0.8, 1.42])
        .range(['#6495ed', '#fff', '#fcff6c', '#ffb439', '#ff4039']);

We will set the color to the features using the fill attribute of the paths that correspond to the stars:

    // Add the star features to the svg container
    var stars = svg.selectAll('path.star-color')
        .data(data.features)
        .enter().append('path')
        .attr('class', 'star-color')
        .attr('d', path)
        .attr('fill', function(d) { 
            return cScale(d.properties.color); 
        });

We will also add labels for each star. Here, we use the projection directly to compute the position where the labels should be, and we also compute a small offset:

// Add labels for the stars
var name = svg.selectAll('text').data(data.features)
    .enter().append('text')
    .attr('class', 'star-label')
    .attr('x', function(d) {
        return projection(d.geometry.coordinates)[0] + 8;
    })
    .attr('y', function(d) {
        return projection(d.geometry.coordinates)[1] + 8;
    })
    .text(function(d) { return d.properties.name; })
    .attr('fill', 'white'),

The star map visualization in fullscreen is shown in the following screenshot:

Adding colors and labels to the stars

We create the overlay and the drag behavior and configure the callback of the zoom event as earlier, updating the position of the labels in the zoom function. Now we have a fullscreen rotating star map.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset