On Wed, Oct 12, 2011 at 08:28:06AM -0700, Iain wrote:
> - The scale method. What does this refer to? Yes, I know a bigger value
> results in a bigger map. But it would be nice to know more.
I need to review the other projections to answer this fully, but at
least for d3.geo.mercator, the idea is that scale(1) projects the full
world onto a 1x1 square, with longitude, latitude [0, 0] at the centre
of the square, which is [0, 0].
This doesn't necessarily apply to projections that don't project the
whole world onto a square, but I think the 1x1 idea for mercator
*should* be maintained for the "standard parallels" for each projection.
That is, along the standard parallel for any projection, scale(1) should
result in each degree being projected to a size of 1/360. Notice I said
*should*, as I haven't checked that each projection does this! The
origin should probably always be projected to [0, 0].
We're working on some d3.geo updates at the moment, so there may be some
updates soon to ensure this consistent behaviour for scales (subject to
backwards compatibility constraints).
> - The translate method. You pass it an array of two values; these are
> horizontal and vertical 'offsets' which I guess are measured in pixels.
> However, where is the origin? It seems to be somewhere towards the top-left,
> but it's not the top-left of the svg element that contains the map. How and
> from where is 'projected' space measured?
The translate offset defines where the origin (typically longitude,
latitude of [0, 0]) should be projected to in pixel space. It is
measured in pixels from the top left. The default is [480, 250], which
is the centre of a 960x500 rectangle.
Internally, what happens is the spherical coordinates are projected onto
the 1x1 square, then multiplied by the scale value, and finally
translated by the pixel-based offset.
> - Conversion. Clearly it's possible to go from geographic space (lon/lat)
> to 'projected' space (x/y). However, is it possible to do the reverse?
> Having looked at d3.geo.js I see the three documented projections have
> invert methods that accept x and y coordinates. I guess this answers my
> question: Yes. But coordinates from the map's origin, or from the svg
> element that contains the map? (In other words, is *invert* consistent
> with *translate*?)
Yes, the d3.geo.*.invert functions take into account the translate and
scale values. So projection.invert(projection(x)) === x should hold for
any translate and scale.
> - I've combined d3.geo with d3.behavior.zoom to create an map that you
> can zoom and pan with the mouse. But what if I want to zoom and pan another
> object, say a rectangle at the same time? The rectangle isn't a geographic
> object, so I cant update its position with the path generator. I wanted to
> use d3.event.scale and d3.event.translate in combination with the transform
> attribute. But without knowing more about the coordinate system, this is
> hard.
As translate is specified in pixels, it should be consistent with the
pixel-space translation vector of d3.behavior.zoom. I'm not sure how
easy it is to do zooming as well though, I'll need to get back to you on
that! I imagine you could do this by taking d3.event.scale and
multiplying it by your initial projection scale value to get the new
projection scale value, and everything should stay consistent.
--
Jason Davies, http://www.jasondavies.com/
It's mostly arbitrary at the moment. As Jason pointed out, it means
something for d3.geo.mercator, but I'm not sure it means anything for
the other projections. It would be nice to come up with a consistent
meaning, but it's a bit tricky as projections have wildly different
behaviors. One possibility might be a mapping between surface area to
pixels on screen, but since some projections are not equal-area you'd
have to make a restriction, such as (an infinitesimally small) area at
the origin.
> The translate method. You pass it an array of two values; these are
> horizontal and vertical 'offsets' which I guess are measured in pixels.
> However, where is the origin?
The translate method specifies where the projection's origin should
appear on-screen. For example, if you use the default d3.geo.mercator,
the default origin (longitude 0, latitude 0) will be displayed in the
center of a 960x500 rectangle (x 480, y 250).
> Conversion. Clearly it's possible to go from geographic space (lon/lat) to
> 'projected' space (x/y). However, is it possible to do the reverse?
Yep, projection.invert(point), where supported. And yes, it's the
inverse of projection(location), so it is consistent with translate
(and scale).
Mike
Sorry if I wasn't clear: it's a 1x1 pixel square centred at [0, 0] (so
the top side of the square goes from [-.5, -.5] to [.5, -.5]. The
origin is mapped to [0, 0].
> d3.geo.mercator.translate() moves the geographic origin (lon 0, lat 0) to a
> given point in pixel space. By default, this point is (x 480, y 250),
> measured from the top-left of the parent SVG element.
Yes, typically a projection will map the origin (usually lon 0, lat 0)
to [0, 0] in pixel space (see above), so the translate just offsets this
[0, 0] by some number of pixels to move the origin to a particular point
in screen coordinates. In SVG, this is indeed measured from the
top-left corner.
> This might not be the case for the other projections: Understood.
This translation works in the same way for all projections. However,
I'm not sure the origin is necessarily always projected to [0, 0] by
default for all projections right now, although it should do for
consistency.
> d3.geo.*.invert() converts from pixel space to geographic space, accounting
> for any translate and scale. This is great.
Yes.
> Zooming and panning, then. I've cooked up an example here
> <http://bl.ocks.org/1283960>. Panning works well, for both geographic
> and non-geographic objects. Zooming, works less well: Although the
> code is simple, and as Jason suggested, the problem is that the map
> doesn't zoom onto the mouse point. Is this because the map has been
> translated? When the translation is set to (0, 0)...
>
> var proj = d3.geo.mercator().translate([0, 0]);
>
> ...then the map zooms as expected. However, this is less flexible. Any
> suggestions would be greatly appreciated.
The issue is occurring because the behaviour automatically adjusts its
translation vector to match the current scale. However, when you were
adding this vector to your original offset, you weren't correcting the
original offset to match the scale. So, simply multiplying by
d3.event.scale fixes this issue:
Nice to see a map of the UK for a change. :)