Skip to content

Satellite geo projection #4781

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
disberd opened this issue Apr 22, 2020 · 6 comments · Fixed by #5801
Closed

Satellite geo projection #4781

disberd opened this issue Apr 22, 2020 · 6 comments · Fixed by #5801
Labels
feature something new
Milestone

Comments

@disberd
Copy link

disberd commented Apr 22, 2020

Hello,

I recently started using plotly.js and would like to congratulate with the amazing works that had gone into this nice plotting tool.

In my work I often have to deal with plots involving earth as viewed from a satellite, for which the general perspective projection would be very useful to have among the already available ones (it is basically an ortographic projection from an arbitrary distance instead of an infinite one).

I am relatively new to javascript but by looking at the code of plotly.js, given that projections seems to be handeld by the d3-geo package, it seems that such addition would be quite straightforward to include.

Specifically, based on how the other projections seem to be implemented, it seems it would be sufficient to add the new projection to src\plots\geo\projections.js according to the code in this page of the d3-geo-projections repo.

This is the code I tried to modify from the repo to fit the style and definitions found in the projections.js file (not so sure about the scale and clipAngle children of p at the end of this code as they don't seem to be defined in other projections in plotly.js)

function satelliteVertical(P) {
  function forward(λ, φ) {
    var cosφ = Math.cos(φ),
        k = (P - 1) / (P - cosφ * Math.cos(λ));
    return [
      k * cosφ * Math.sin(λ),
      k * Math.sin(φ)
    ];
  }
  forward.invert = function(x, y) {
    var rho2 = x * x + y * y,
        rho = asqrt(rho2),
        sinc = (P - asqrt(1 - rho2 * (P + 1) / (P - 1))) / ((P - 1) / rho + rho / (P - 1));
    return [
      Math.atan2(x * sinc, rho * asqrt(1 - sinc * sinc)),
      rho ? asin(y * sinc / rho) : 0
    ];
  };
  return forward;
}
function satellite(P, omega) {
  var vertical = satelliteVerticalRaw(P);
  if (!omega) return vertical;
  var cosOmega = Math.cos(omega),
      sinOmega = Math.sin(omega);
  function forward(λ, φ) {
    var coordinates = vertical(λ, φ),
        y = coordinates[1],
        A = y * sinOmega / (P - 1) + cosOmega;
    return [
      coordinates[0] * cosOmega / A,
      y / A
    ];
  }
  forward.invert = function(x, y) {
    var k = (P - 1) / (P - 1 - y * sinOmega);
    return vertical.invert(k * x, k * y * cosOmega);
  };
  return forward;
}
function satelliteProjection() {
  var distance = 2,
      omega = 0,
      m = projectionMutator(satelliteRaw),
      p = m(distance, omega);
  // As a multiple of radius.
  p.distance = function(_) {
    if (!arguments.length) return distance;
    return m(distance = +_, omega);
  };
  p.tilt = function(_) {
    if (!arguments.length) return omega * degrees;
    return m(distance, omega = _ * radians);
  };
  return p
      .scale(432.147)
      .clipAngle(acos(1 / distance) * degrees - ε);
};
(d3.geo.satellite = satelliteProjection).raw = satellite;

And make the projection selectable by appending its name at the end of params.projNames in the file src\plots\geo\constants.js

// projection names to d3 function name`
params.projNames = {
    // d3.geo.projection
    ...      
    'sinusoidal': 'sinusoidal'   
    'satellite': 'satellite'
};

Is this all that is needed to implement a new projection or are there other parts of the code that might be impacted that I did not realize?

Provided this is really all that is necessary, I will try to implement and test these changes myself if needed in the next few days but having never used node or really coded in javascript before, it might take a while before I am eventually able to test and learn how to do a pull request.

@alexcjohnson
Copy link
Collaborator

Hi @disberd! Yes, we'd be happy to accept a PR to include this projection. I think you've covered all or nearly all the changes that will be required, but from the looks of it projections.js is a code-generation output:

* Generated by https://github.com/etpinard/d3-geo-projection-picker
*
* which is hand-picks projection from https://github.com/d3/d3-geo-projection

Which makes it look as though all that should be needed for that part of the update is to run d3-geo-projection-picker with the correct arguments. @etpinard can correct me if I'm wrong, but it seems like that would be

d3-geo-projection-picker <all the existing projections plus satellite> -o projections.js

Then as a final step the new projection would want to be included in one or two test images - but we can help once it gets to that point.

@disberd
Copy link
Author

disberd commented Apr 22, 2020

Hi @alexcjohnson!

Thanks for the reply.
I missed the comment about the autogeneration and tried downloading the d3-geo-projection-picker from npm as suggested in the github repo.
I tried to run it but the output is formatted differently from the one in projections.js.
The way the satellite function was generated does seem to be in-line with the code I posted in the original post.

I will start trying modifying the code as mentioned originally and check if I manage to build plotly with the modification to test functionality while we wait from a feedback from @etpinard.

I'll ask for help with generating the test images when I manage to get some results!

@disberd
Copy link
Author

disberd commented Apr 27, 2020

I started working on the issue and applying the modification to projections and constants as suggested in my first post does indeed create the correct projection.

Now there are some other few issued that arised and should be fixed to make the projection work as intended. For the moment there are 3 that I identfied:

  1. The satellite projection has a clipAngle that depends on the altitude of the satellite (the distance property of the projection), which is different from every other projection currently specified. At the moment the clip angle of various projections is stored in a constant lonaxisSpan defined in src/plots/geo/constants.js.
    To take into account this variable clipAngle, for the moment I modified the file /src/plots/geo/geo.js as follows:
@@ -671,9 +671,13 @@ function getProjection(geoLayout) {
 
     var projection = d3.geo[constants.projNames[projType]]();
 
-    var clipAngle = geoLayout._isClipped ?
-        constants.lonaxisSpan[projType] / 2 :
-        null;
+    if (projType == "satellite") {
+      clipAngle = (Math.acos(1 / projection.distance()) * 180) / Math.PI;
+    } else {
+      var clipAngle = geoLayout._isClipped
+        ? constants.lonaxisSpan[projType] / 2
+        : null;
+    }

The change correctly handles the clipAngle but I am not sure whether this is the best way it could be done or if it goes against some coding guidelines here.

  1. The earth in the plot correctly changes the longitude viewpoint when dragging the plot from left to right, but does not change the latitude when dragging the plot from up to down as in the ortographic projection. Instead, the full earth is simply moved up and down as if panning in a normal plot.
    Can someone point me to the function that handles this up-down interaction in the plot so that I can go and check how to make it rotate along the latitude instead?

  2. There are some parameters of the satellite projection that one might want to change after the plot has been generated (e.g. the distance or satellite altitude).
    Is there a preferred way to expose the distance property of the d3 projection in order to be able to edit it in plotly after the plot has been generated?

While waiting for some feedback, I'll keep try and finding an answer to point 2 and 3.

@archmoj archmoj added the feature something new label Jul 29, 2020
@archmoj
Copy link
Contributor

archmoj commented Jul 29, 2020

Upgrading to use d3-geo & d3-geo-projection could enable this feature.

@archmoj archmoj added this to the v2.3.0 milestone Jun 26, 2021
@disberd
Copy link
Author

disberd commented Jul 2, 2021

Cool to see that with the plotly update this kind of projection (together with others) might be implemented soon.
Is there any help in testing or anything else to support this feature?

@archmoj
Copy link
Contributor

archmoj commented Jul 5, 2021

Cool to see that with the plotly update this kind of projection (together with others) might be implemented soon.
Is there any help in testing or anything else to support this feature?

@disberd Yes please play with #5801. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature something new
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants