d3 geo projection transitions from orthographic to X

Viewed 291

I'm working on an educational map project in which different map projections are displayed. I'd like to implement a morph transition between choosing different projections.

I've found a great example how to implement it, and I've had not much troubles to recreate it. Unfortunately, I also need the capability to clip the projections. This works flawlessly with the target states, but not when morphing the projections.

You can see it in this example when choosing "orthographic" as first projection and for example "equirectangular" as second one: https://bl.ocks.org/alexmacy/082cb12c8f4d5c0d5c4445c16a3db383

The clipping path follows the darker line instead of the current map extent. Is there a way to implement it correctly?

2 Answers

This is a lot trickier to implement than appears, I remember looking at this a few years back. The cleanest solution is to create a new preclipping function that determines which portions of the projected earth should be behind/covered by portions closer to the origin. But it turns out this is relatively hard to define - at least my me - and also hard to use in a new preclipping function.

Instead we could cheat. There are a couple ways, I'll propose one that nearly does the trick - you can still see some overlap though. We'll use d3's antimeridian preclipping to make sure no features stretch over the antimeridian, then we'll use a clip angle to remove portions of the earth that need to be removed.

Setting Clip Angle

When the hybrid projection is purely orthographic, clipping angle is great: the clip angle is the same in all directions. Here it should be 90 degrees.

When the equirectangular is dominant in the hybrid projection, the clipping angle is not needed (I use an angle of 180 degrees, which doesn't clip anything below). This is because the entire earth should still be visible.

But otherwise, the hybrid clip angle is not the same in all directions - this is why this is not a perfect solution. However, it does remove nearly all the overlap. So as we go from the projection being mostly equirectangular to wholly orthogrpahic, we slowly reduce the clip angle.

Example

Starting with an equirectangular projection and transitioning to an orthographic, we'll start transitioning the clipAngle from 180 degrees to 90 degrees only once we get 40 percent of the way trough the transition:

function getProjection(d) {
    var clip = Math.PI;  // Starting with 180 degrees: don't clip anything.
    var projection = d3.geoProjection(project)
        .rotate([posX, posY])
        .fitExtent([[10, 10], [width - 10, height - 10]], {
          type: "Sphere"
        })
        // Apply the two pre clipping functions:
        .preclip( function(stream){
            stream = d3.geoClipAntimeridian(stream) // cut antimeridian
            return d3.geoClipCircle(clip)(stream)   // apply clip angle
        })
        
    var path = d3.geoPath(projection);

    function project(λ, φ) {
      λ *= 180 / Math.PI, 
      φ *= 180 / Math.PI;

      var p0 = projections[0]([λ, φ]), 
          p1 = projections[1]([λ, φ]);

       // Don't actually clip anything until t == 0.4
       if(t > 0.4) {          
        clip = Math.PI/2 + (0.60-(t-0.4)) * Math.PI/2
       }
      
          
      return [
        (1 - t) * p0[0] + t * p1[0], 
        (1 - t) * -p0[1] + t * -p1[1]
        ];
    }

    return path(d)
  }

Here's an example.

Great answer Andrew Reid! I just made one small change. I removed the t > 0.4 if statement and used this clip for transitioning into an orthogrpahic projection:

clip = Math.PI/2 + (1 - t) * Math.PI/2

.. and this clip for transitioning out of an orthogrpahic projection:

clip = Math.PI/2 + t * Math.PI/2

I like this because it's slightly cleaner, is a 'catch-all' for any t value and having the reverse is also useful.

Related