d3 / d3-plugins

[DEPRECATED] A repository for sharing D3.js V3 plugins.
https://github.com/d3/d3/wiki/Plugins
Other
1.81k stars 685 forks source link

Satellite projection needs clip-to-horizon. #52

Open mbostock opened 11 years ago

jasondavies commented 11 years ago

Should this happen automatically when the distance is set? It can be easily computed like this:

projection.clipAngle(Math.acos(1 / projection.distance()) * 180 / π - ε);
mbostock commented 11 years ago

Does that work in conjunction with rotation and tilt? That's a neat trick!

jasondavies commented 11 years ago

Yes, I was surprised at the simplicity, but in fact the satellite projection is the view of a point at a given distance above the sphere’s surface, which you can reason about in 2D, so the sphere becomes a circle:

Draw a line from the satellite to the circle centre (with length D). Then draw a line from the satellite to its horizon. This line will be tangent to the circle at the horizon point. Hence it will be at right-angles to the circle radius at this point, making a right-angle triangle. We assume the radius is 1 (because D is a multiple of the radius), hence cos θ = 1 / D.

I’ve tested this with different tilt angles, and it works quite well, but it looks like tilt angles above a certain amount (around 20° for my particular test) result in large values that cause the browser to stop drawing everything properly, and when you clamp the pixel values the projection appears to loop back on itself (above the horizon so not overlapping). But I think it’s safe to say you wouldn’t actually want to use such angles as the distortion is extremely high. As you can see, the horizon calculation still looks correct though. :)

Screen Shot 2013-03-01 at 19 04 54

jasondavies commented 11 years ago

I’m tempted to say that we should automatically set the clip angle by default. If a clip angle is manually set, then we stop automatically setting it when the satellite distance is set. What do you think? Alternatively, we could always set it automatically (and `delete projection.clipAngle), but I’m a bit wary of taking away the ability to set a smaller clip angle.

mbostock commented 11 years ago

I think tilt angles of 25°-30° are actually not that uncommon, but you do have to crop and zoom in to avoid the high-distortion areas. The U.S.-Mexico border graphic we published yesterday uses a tilt angle of 30°, and the earlier satellite projection of the U.S. Eastern seaboard uses a tilt angle of 25°.

Here's a test case of the Satellite projection using your proposed clipAngle definition. It works for U.S. geometry, but when clipping the entire world, nothing displays:

http://bl.ocks.org/mbostock/6b676736544a8768aaed

jasondavies commented 11 years ago

Yes, as I mentioned above I think this is due to extremely large values causing problems for browsers, rather than clipping per se (the example above was generated after clamping return values inside the projection definition).

I’ll try and think of a solution for this, e.g. we could clamp to “sane” values inside the projection as I did above. That seems like a bit of a hack though; I’d prefer to clip such large values in pixel-space, but of course this is more effort.

mbostock commented 11 years ago

Right… the distortion is out of view. There's actually no problem if you prefilter the geometry beforehand to the countries that are visible:

http://bl.ocks.org/mbostock/2b977f1a962b0eb48f69

Of course, that doesn't help much with the land case, if you have a single multipolygon that's got everything. But I like the idea of optionally being able to pass geometry though pixel-space clipping. I wonder how hard it would be to hook d3.geom.polygon's clip method up to a projection?

mbostock commented 11 years ago

FWIW, you could use ogr2ogr's -clipdst argument to clip the geometry to the viewport (assuming you figure out how to express the projection in georeferenced coordinates), which is probably the best option for static maps since the TopoJSON file would be as-small-as-possible for the given viewport. But it'd still be good to display these difficult projections correctly, say if you wanted to fly around the earth with the satellite projection.

jasondavies commented 11 years ago

Nice georeferencing tool. :) I agree using ogr2ogr is probably the most straightforward option for static maps, although of course it would be quite nice if we could reuse D3’s clipping for this even for static maps.

Aside from pixel-space clipping, another fun possibility could be to take a viewport and project this back to the sphere using projection.invert, and use that for projection.clipPolygon (when it lands!). Another idea is to take the d3.geo.bounds of the viewport (in spherical coordinates) and use for projection.clipExtent (which should be a little faster, at least until I optimise clipPolygon!), and use in conjunction with clipPath. These would only really work if the viewport coordinates are all invertible i.e the view is entirely filled, so I still think pixel-space clipping is the better approach (and it may also give us a speed boost, too).

jasondavies commented 11 years ago

I’m experimenting with mbostock/d3#1116. I think I know how to fix the satellite projection. Larger tilt values currently still lead to the hourglass effect seen in the screenshot above. This will occur when points are projected that are greater than 90° away from the projection’s tilt. So this is effectively when the tilt is larger than the clip angle.

This can be fixed by clipping a second time (!), and fortunately these points all lie in a small circle. However, the small circle has a different origin so some rotations need to be applied (this will be simplified when we have arbitrary origins for small circles).

jasondavies commented 11 years ago

http://bl.ocks.org/mbostock/fde1a65845aef4fe33d1 has the same problem. If the tilt is less than the clipAngle, then it works fine. Experimental fix coming soon.

jasondavies commented 11 years ago

Here is a possible solution, which clips twice: once for the horizon (this is done via the satellite projection’s clipAngle), and for large enough tilts, a second small circle positioned perpendicularly to the first.

The second small circle is implemented using an “identity” projection whose output is in degrees (note the negative latitude to counteract the default flipped y-coordinate). In fact, features are clipped against this first, prior to the usual satellite projection clipping.

mbostock commented 11 years ago

:boom: