Saturday, December 28, 2019

D3-Celestial Geolocator: Markers

[Previously: part I Geolocator, part II Sky color]

A new feature for my d3-celestial star map: Celestial.addCallback allows to define a callback function that will be executed in the local client context every time the map is redrawn. This enables other display elements to react to changes, for example the marker position in the geolocator-globe above. Usage is pretty simple: add an anonymous function as the parameter for addCallback:

  Celestial.addCallback(function () {
    var loc = Celestial.location();
    globe.plugins.markers.remove("*");
    globe.plugins.markers.add(loc[1], loc[0]);
  });

It also demonstrates the other new feature, a geo-marker, implemented as a plugin for the geolocator-globe. This takes the position of the last mouse position and puts a marker on it. These can be of different colors and sizes, so can be added in any form or number you like. The remove function takes an asterisk to remove all, an arry index or the exact position to remove one.

  function markers(config) {
    var marks = [];
    config = config || {};

    var addMark = function(lng, lat, options) {
      options = options || {};
      options.color = options.color || config.color || 'white';
      options.size = options.size || config.size || 5;
      var mark = { options: options };
      if (config.latitudeFirst) {
        mark.lat = lng;
        mark.lng = lat;
      } else {
        mark.lng = lng;
        mark.lat = lat;
      }
      marks.push(mark);
    };

    var removeMark = function(lng, lat) {
      if (lng === "*") {
        marks = [];
        return;
      }    
      if (arguments.length === 1 && lng < marks.length) {
        marks.splice(lng, 1);
        return;
      }
      if (arguments.length === 2) {
        for (var i=0; i <= marks.length; i++) {
          if (marks[i].lng === lng && marks[i].lat === lat) {
            marks.splice(i, 1);
            return;  
          }
        }
      }
    }

    var drawMarks = function(planet, context) {
      for (var i = 0; i < marks.length; i++) {
        var mark = marks[i];
        drawMark(planet, context, mark);
      }
    };

    var drawMark = function(planet, context, mark) {
      var color = mark.options.color,
          size = mark.options.size * 5,
          pos = planet.projection([mark.lng, mark.lat]);
          
      context.fillStyle = color;
      context.beginPath();
      context.moveTo(pos[0], pos[1]);
      context.arc(pos[0], pos[1] - size, size/2, Math.PI, 0);
      context.fill();
      context.fillStyle = "#fff";
      context.beginPath();
      context.arc(pos[0], pos[1]- size, size/4, 0, 2 * Math.PI);
      context.fill();

    };

    return function (planet) {
      planet.plugins.markers = {
        add: addMark,
        remove: removeMark
      };

      planet.onDraw(function() {
        planet.withSavedContext(function(context) {
          drawMarks(planet, context);
        });
      });
    };
  };

[Next: part IV Night sky.]

Time zone still needs to be set manually and horizontal refraction isn't considered yet, so the result need not be entirely accurate.

No comments:

Post a Comment