Monday, July 26, 2010

Merging Table Of Content and Legend Info

Initially, while working on Gulf Of Mexico Response Map, the legend and table-of-content were two separate windows. That took too much real-estate, and a TOC layer was hard to correlate with the legend swatch by name only. So I set upon merging the legend window and the table-of-content into one window. There is plenty of TOC code as a visual tree component, but it is the legend UI that causes us trouble as there is no REST endpoint to get the layer swatch information. Well with the advent of FeatureLayer somebody can argue that this could be done. But you still have to go back and forth for each layer and get the rendering information. BTW, this will be remedied in 10.1, as a new REST endpoint will be introduced to get _all_ rendering information of a map service. In the meantime, I had to do something. As you can imagine the project map services were very dynamic and the layer content was changing constantly, so having a static legend is out of the question. There were attempts to create an ArcObject proxy, where the client application connects to that proxy who issues a SOAP request to an ArcGIS server for the legend info. The SOAP result is converted to JSON and forwarded back to the client for decoding and rendering. The code was written in .Net and have a Java based server. I wanted to be platform independent and have a two tier architecture (at least from a 10,000 feet perspective) and the introduction of a proxy was out of the question. So....I have to issue SOAP calls directly from the client to ArcGIS server. This is where the advent of FlashBuilder with its data connectivity option was a blessing. FlashBuilder can generate correctly stub code for ArcGIS SOAP methods based on a map service WSDL. This was impossible in FlexBuilder. Yes, it does bloat the code base a bit, but it is a small price to pay for such a dynamic system. The TOC code is based on the FlexViewer TOC code. I modified the item renderer to be of variable height to accommodate the renderer swatches as child elements. You can see the application in action here. And like usual, you can download the source code from here. Small note: First time through, it take a few seconds to get the swatches, so if you open the tree, it will look “plain” and somehow AGS returns blank swatches for tiled map services. not sure why ! have to ask the core team about that. Oh one more thing, in addition to adding the Flex API for AGS swc, make sure to include the Adobe provided fiber swcs. Until 10.1 (hopefully soon) hope this can help.
PS: If you have a 10 server - there is a REST way to get all the layers published in a map server that could be used to create swatches from the rendering information.

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.