Thursday, July 22, 2010

Pixel Bender For Map Projection

A customer came up to me at the User Conference and asked me if I can apply the same overlay method that I used in my OverlaySymbol application to position a geographic referenced image on a web mercator tiled base map; since esri is migrating most of its published maps on arcgis.com to web mercator. After talking it over with my friend Kerry (who is the final authority when it comes to map projections), he recommended that this should be a pixel by pixel operation due to the exponential latitude elongation nature of web mercator. So basically the algorithm is; for each output mercator pixel, convert it back to geographic, find out in the geographic image source what is the pixel color at that geographic location and assign that color to the output mercator pixel. Straight forward, I mean the web mercator transform is well known. I just have to do it a million time, and... that is when I started panicking. A million time loop in AS3 - that will take forever (well actually half a second) in user space. Then I remembered PixelBender. That is what it does for living very well - massively process pixels at the GPU level. So I downloaded the toolkit, went over the tutorials (all awesome) and I started writing my own filter. PixelBender can operate in a filter, blender or filler mode. It is the last mode that I'm interested in, as it enables me to fill any region in a display object based on a set of parameters. So I approached this overlay method differently than my previous experiment. I created a layer rather than an symbol to overlay the geographic image. The source of the image will be a property of the layer. The layer will load the compiled PixelBender filter code and on an extent change, PixelBender will be invoked to fill the layer graphics context based on the image source and extent as parameters. And it is fast :-). The following is a snippet of the mercator filter source code:
void evaluatePixel()
{
float2 pt = outCoord();
pt.x = pt.x * targetWidthFact + targetXMin;
pt.y = (targetPixelHeight - pt.y) * targetHeightFact + targetYMin;

pt.x = degrees(pt.x / 6378137.0);
pt.y = degrees(2.0*atan(1.0) - (2.0 * atan(exp(-1.0 * pt.y / 6378137.0))));
if( pt.x < -180.0 || pt.x > 180.0 || pt.y < -90.0 || pt.y > 90.0)
{
dst = float4(0.0,0.0,0.0,0.0);
}
else
{
pt.x = (pt.x - sourceXMin) * sourceWidthFact;
pt.y = (sourceYMax - pt.y) * sourceHeightFact;
dst = sampleNearest(src,pt);
}
}

So the evaluatePixel() function is invoked by Pixel Bender on each pixel in the target image. the built-in outCoord() function return the coordinated in the target image that is currently being processed. In the next two lines, I convert the pixel values into mercator values - this is a linear transformation. Then I convert the mercator values into geographic value. If the values are 'outside' the geographic boundaries are return a fully transparent pixel value. Otherwise I linearly convert it to a pixel coordinate based on the image source width and height and I invoke the built-in sampleNearest() function to get me the pixel color value and return that value to pixel bender who will paint the target pixel location with that value. Cool, eh ?
You can see the application in action here. It shows precipitation image data from NOAA. And like usual you can download the source code from here. BTW, this requires the new Flex API for ArcGIS Server.

1 comment:

Harley said...

you know.. when flash player 10 came out I was unclear how the heck pixel bender would be an advantage. I was hoping more for 3d functions to take advantage of opengl... but pixel bender I think is integrated into the flash player now. so, could this be applied to the flash api natively instead of open source download, is that right?

even still, I finally see for the first time how pixel bender can be used. good job!