ESRI JS API: Custom vs. Out-of-the-Box Services

Posted by Dave Bouwman | Posted in .NET, ASP.NET MVC, Ajax, Javascript, Unit Testing | Posted on 25-11-2008

6

We’ve been having a good time developing applications on the ESRI Javascript API, but we are seeing some limitations to the REST API, and are working around this by creating our own services. Since there are likely others out there who are running into these same issues, I thought I’d share how we are tacking this.

What’s Wrong?

Good question, and the answer is nothing. The REST API works quite nicely for a generic API. In the standard scenario, you create an esri.map on the page, setup some tasks (i.e. a QueryTask, FindTask, or IdentifyTask ) and all is well.

out-of-the-box 

Both the map and the tasks are designed to talk to the REST API, and that’s pretty good.

But there are some limitations. First, I’d like to do more on the server in a single request. Instead of just locating a parcel by it’s PIN, I want to locate the businesses within say 500 yards, and view them on the map and in a results window. Why make multiple requests to the REST API, and write a whole lot of complex javascript, when I can do this in one operation via ArcObjects or <shudder> the WebADF?

Second, given our requirements, I regularly need to access functionality that’s not exposed via the REST API – i.e. linear referencing, or 1:M type reporting.

A related issue is that I’d like to be able to write solid unit tests for the code. While it’s possible to use something like DOH or jsunit to write unit tests for javascript, it’s much easier to write tests in .NET (i.e. TestDriven.netMbUnit & RhinoMocks). So – if I have a choice of making 3 requests to the REST API, and aggregating the results in the browser (in javascript), vs. making a single request to a custom, very testable service, I’ll take the second option every time.

Solution: Custom Services

The idea here is pretty simple – instead of using  the out of the box tasks and the REST API, I create my own “tasks” (not real “tasks” in the ESRI parlance – just classes which request and process data) that make calls back to a custom service that does what I want, how I want it.

custom-services

We’ve done this a number of ways… in all cases we write custom code in one or more “worker classes” that live in a standard class library assembly. Depending on the requirements, these worker classes can be designed to run on a SOC box (i.e. they make direct ArcObjects calls w/o ServerContext.CreateObject), they can run on a web server (i.e. use ServerContext.CreateObject) or if it needs to scale out across a lot of SOC boxes, they can be packaged as Server Object Extensions. Regardless, at the core they are just a set of methods, and so we have another assembly with unit tests that ensures that these methods work as expected.

ASP.NET JSON WebServices (.asmx)

Since all the logic is in the “worker class”, the web service is just a thin veneer of serialization / deserialization, most of which is handled automatically by marking up the service as a Script Service.

ASP.NET Handlers (.ashx)

Handlers are very similar to the web service option, but you don’t get any json serialization for “free”. They are also more difficult to debug and test, so we avoid them. No need to “look for pain” ;-)

ASP.NET MVC Controllers

For a wide variety of reasons, the ASP.NET MVC framework is very attractive – none the least of which is that is was designed to be much more testable than WebForms. We recently did a project that used Controllers as the service tier, and it worked out really really well. As with all things you learn as you go, so I will be doing more active refactoring on the controllers, and taking a more RESTful approach to the URI space in the future.

Summary

I hope this gives you some ideas about how you can “mashup” the ESRI Javascript API with additional back-end services. I have a number of interesting projects that will be doing a lot of this style of interaction, and I’ll post more detailed descriptions of them as they go live.

Setting Spatial Reference on a Folder of MrSID Files…

Posted by Dave Bouwman | Posted in .NET, ArcGIS Devt, ArcGIS Server | Posted on 10-11-2008

4

So I’m getting ready to cook up a tile cache, and I want to use the ESRI ArcGIS Online tile scheme so I can use their tiles for the first 10 or so zoom levels. To do this, you need to have your map’s spatial reference set to WGS84, which was not a problem when I was setting up the “Road Map”, but I ran into a snag with the “Hybrid” (roads + parcels + orthos) – the Ortho images did not have any spatial reference defined – which means that ArcMap could not project them. Doh!

Now – I should point out that there is likely a neat-o way to do this with some Geoprocessing Python, but I’m a .NET guy, so I used my .NET hammer to solve this.

Ive done enough with images over the years to know that the spatial reference will be stored in the .aux.xml files that share the name of the raster. But for one set of images, there were no .aux.xml files. Solution: Open all the files in ArcMap and it will create these files. Sweet.

Next up, for one raster, I manually set the correct spatial reference in ArcCatalog. Easy, but there are several hundred files, so it’s not going to work as a solution. Enter System.Xml…

I then opened up the .aux.xml file (in notepad) for the raster that I manually assigned a spatial reference to, and simply copied the projection information from the <SRS> element. I then cooked up a simple loop over all the files, opened them up in an XmlDocument, injected or updated the <SRS> element, saved the file and shazam! all done. The pertinent function is below.

 

        private void UpdateSpatialRef(string folderPath, string srs)

        {

            //Loop over all the xml files, open as xml doc, inject the SRS node, save

            string[] fileNames = Directory.GetFiles(folderPath, “*.sid.aux.xml”);

            for (int i = 0; i < fileNames.Length; i++)

            {

                string fileName = fileNames[i];

                //Open the file

                XmlDocument xDoc = new XmlDocument();

                xDoc.Load(fileName);

                //find the SRS element if it’s here

                XmlNodeList srsNodes = xDoc.SelectNodes(“//SRS”);

                XmlElement srsEl = xDoc.CreateElement(“SRS”);

                srsEl.InnerText = srs;

                if (srsNodes.Count > 0)

                {

                    //Already set… replace                   

                    if (xDoc.DocumentElement.FirstChild.Name == “SRS”)

                    {

                        xDoc.DocumentElement.ReplaceChild(srsEl, xDoc.DocumentElement.FirstChild);

                        xDoc.Save(fileName);

                    }//if it’s somewhere else in your doc, it’s likely bad & you’ve got other problems!

                }

                else

                {

                    xDoc.DocumentElement.PrependChild(srsEl);

                    xDoc.Save(fileName);

                }

                Console.WriteLine(“Fixed: “ + fileName);

            }

        }

 

While more over the top than may be necessary, it was quick to write and solved my problem… now to kick off the cache creation!