simplegeo / polymaps

Polymaps is a free JavaScript library for making dynamic, interactive maps in modern web browsers.
http://polymaps.org/
Other
1.6k stars 213 forks source link

Wishlist: Cartesian projections #37

Open jasondavies opened 13 years ago

jasondavies commented 13 years ago

Hello,

I'm sure you've already considered (or are planning) support for other projections/co-ordinate systems, but I just wanted to note that Polymaps is rather good for creating a pan/zoom viewer for giant scanned images (with their physical measurements in inches), and supporting a plain old Cartesian projection could be useful. Perhaps it is already supported well enough, I'll explain some more below!

So far everything works wonderfully and now I'm just working on adding multiple scanned images to the same map, each as a separate po.image() tileset. Each image has a different resolution, so I'll have to apply some kind of transform to ensure that the correct size is displayed.

I think the right starting point is to follow http://polymaps.org/ex/overlay.html and replace proj.locationPoint with a straight mapping from my real-world Cartesian measurements to pixels using an affine transform for the translations (I need to add roughly 200 image tile layers all next to each other at various offsets) and any fractional zooming required so that all the scans appear the same resolution.

I've almost got this working but the tilesets are repeated infinitely on the horizontal axis (I only need each one to appear once) and I'm not 100% sure on the correct way to implement the transform.

As well as this it would be nice to override po.hash() so it uses Cartesian co-ords. Actually, even better than this would be if I can make it show the percentage position relative to the nearest tileset, but that's slightly trickier.

Thanks, Jason

mbostock commented 13 years ago

I would recommend looking at the transform layer attribute:

http://polymaps.org/docs/layer.html#transform http://polymaps.org/ex/tiles.html

jasondavies commented 13 years ago

Thanks for the quick reply! Do you know of a way to prevent the po.image() tilesets from repeating infinitely horizontally?

mbostock commented 13 years ago

Ah, oops, that tiles.html example doesn't use the transform attribute. Not sure I have an example of that on the website, but it's what I would use to transform multiple layers to share the same coordinate space. I have some code for deriving the affine transform from a set of 2-3 point correspondences as well, based on this simple solver:

http://elonen.iki.fi/code/misc-notes/affine-fit/ http://elonen.iki.fi/code/misc-notes/2d-rigid-fit/

The basic idea is that you map pixel locations in the image (defined as coordinates) to a shared coordinate space (such as uniform coordinates, or locations on the Earth for spherical Mercator overlays). Then you apply this transform to the layer.

The transform can also be changed interactively for real-time direct manipulation of the layer, similar to a free transform in Photoshop.

jasondavies commented 13 years ago

Actually, this example uses the transform attribute: http://polymaps.org/ex/transform.html

mbostock commented 13 years ago

Oh, right. To stop the repeating infinitely, you define a template function that maps the tile coordinates to a string URL, rather than using the default po.url. Something like this should work:

function template(c) {
  var max = c.zoom < 0 ? 1 : 1 << c.zoom;
  if ((column < 0) || (column >= max)) return "about:blank";
  … else substitute %Z %X %Y …
}
mbostock commented 13 years ago

Ah, right. Thanks for finding the transform example. :) Haven't had my coffee yet.

jasondavies commented 13 years ago

Cool, I'll give that a go. Thanks for the quick replies :-)

jasondavies commented 13 years ago

In case anyone else wants to stop the infinite repeating, I just used this successfully:

var singleTileset = function(u) {
  var url = po.url(u);
  return function(c) {
    var max = c.zoom < 0 ? 1 : 1 << c.zoom;
    if ((c.column < 0) || (c.column >= max)) return "about:blank";
    else return url(c);
  };
}
mbostock commented 13 years ago

https://github.com/mbostock/polymaps/commit/1754c96066ef45ca37d41de9d53d3c70cbb0a651

jasondavies commented 13 years ago

Still trying to find a simple way to do this: I need to take a hash of the form x/y/z, where x and y are decimal values between 0.0 and 1.0, and use this to position the map at the appropriate center. The first step is trivial, which is converting decimals x and y into pixel positions on my original image. The hard part is turning these pixel positions into appropriate lat/lon coords.

At first I was trying to convert the pixel positions into lat/lon coords directly, but now I'm wondering if I should just override all 8 projection functions e.g. map.locationPoint(l) instead so they use pixel coords instead of Mercator. Any thoughts?

jasondavies commented 13 years ago

Okay, figured it out in fb8fa238ac2bc38dab7abc7a8d5be9e7e4ff6982, turns out po.map.{locationCoordinate,coordinateLocation} was the simplest way to do it.

mbostock commented 13 years ago

Take a look at the node-mappy implementation, which is inspired by Polymaps:

https://github.com/mbostock/node-mappy

When I get a chance, I'll extract the JavaScript part so that it runs in a browser rather than Node. It'll be called pixymaps. :) You can use it in conjunction with d3.geo.mercator to do spherical mercator, or any other projection, as in this example:

https://github.com/mbostock/node-mappy/blob/master/test/test-circles.js

jasondavies commented 13 years ago

Wow, really exciting, thanks!

I couldn't get node-mappy to work due to a segmentation fault in cairo (I think it's this bug: LearnBoost/node-canvas#86) but pixymaps works great!

I'll convert the Voynich Manuscript Viewer to pixymaps as soon as I can get zooming and panning working. Are you planning on using CSS3 transforms for this, or is blitting to the canvas for each frame sufficient?

mbostock commented 13 years ago

Yeah, I was going to look at CSS3 transforms rather than off-screen canvas when running inside the browser. I haven't implemented interaction yet, as you probably noticed. :)

jasondavies commented 13 years ago

I did a quick -webkit-transform panning test in a few lines of D3 using a canvas that's 2x the viewport size in both dimensions: jasondavies/pixymaps@37289c67cf618af71cd67ee304b64997bbcfd990 . Seems more efficient than having lots of small canvas tiles. When the canvas edge is almost reached, the whole canvas can just be reblitted and retranslated so the edge never crosses a viewport boundary. I'm sure you're thinking about this already, I was just curious to see how it felt :-)

jasondavies commented 13 years ago

Okay, I added re-blitting to test this out: https://github.com/jasondavies/pixymaps/tree/experiments.

There's a noticeable delay with this though. I think I need to reuse the existing canvas pixel buffer rather than re-blitting the whole lot again i.e. just re-blit the new portions of the map. Or maybe using small tiles would be better.