Category Archives: ArcDeveloper

Adding Drag/Drop Reorder to ArcGIS Flex Viewer TOC

Wow, that is a long, clumsy title.  Google will like it, though.

Anyway, I’ve been neck-deep in the world of Flex, focusing on the ArcGIS Server Flex API the last couple of months.  We delivered a map viewer based on Flex to a client recently, and it was a great learning experience.  ESRI released a Sample Flex Viewer component that includes the Table of Contents (TOC) sample from the code gallery.  The TOC component was originally built by Tom Hill at ESRI.  He and I have traded a couple of e-mails about adding drag and drop reorder functionality, so I thought I’d run through the basics of what it takes to get it working.

Download the Flex Viewer

If you haven’t already, download the flex viewer from here.  Extract the sample SOURCE (there are two zips, binary and source…we are using the source archive)  to your development area.  I am using FlexBuilder, so my life is easy, as I can just import the existing project into the IDE.  You don’t have to have FlexBuilder, but I am going to presume you know how to edit source files and compile the project.

Modify the LiveMapsWidget

The TOC component is used by the com.esri.solutions.flexviewer.widgets.LiveMapsWidget MXML component.  Open the source for that bad boy (the namespace==the directory).   Find the TOC component, which looks like

<toccomp:TOC id="toc" width="100%" height="100%"/>

We want to enable dragging on the component.  The TOC component extends the core Flex Tree component, so enabled drag drop is as simple as adding

dragEnabled=true

to the element.  If you do that and the run the viewer, you’ll notice you can open the Live Maps widget (in the Globe menu)  and drag the “Lousiana Landbase” layer around.  It doesn’t do much, but you can drag it.  In fact, you get a rex “X” while you drag, which means you can’t drop it anywhere.  So, how do we get rid of that pesky red “X”?   We have to tell the DragManager that the TOC will accept a drag/drop.  The DragManager is a Flex class that maanged drag and drop operations.  Google it for more info (sorry, but I have to constrain the post ot it will go War and Peace on me)   The time to tell the DragManager about the openness of the TOC is when something is dragged (?  drug?) over it.  We do this with the dragEnter event.  So, adddragEnter

dragEnter="onDragEnter(event)"

to the toc element.  Now we have to write the onDragEnter  method.  In the mx:Script area of LiveMapsWidget.mxml, copy this code:

			private function onDragEnter(event:DragEvent):void{
				DragManager.acceptDragDrop(event.currentTarget as TOC);
				if ((event.currentTarget as TOC)==null)
				{
					DragManager.showFeedback(DragManager.NONE);
					event.preventDefault();
				}

			}

This function fires when we drag something over the TOC and tells it that we are open for business. Business is, of course, things that can be dragged and dropped. You could put more logic in here to filter out non TOCItems, etc, but that is left as a lesson for the reader.

OK,  so our red “X” is gone over the TOC (but still there if you drag a layer over the map, cool!)  but now we want it to do something when we drop the layer.  Specifically, we want it to reorder the layers.  But we only have 1 layer, so let’s quickly add another.  Open up the config.xml in the src directory and add the following tag to the <livemaps> element:

<mapservice label="Louisville Public Records" type="dynamic" visible="false" alpha="0.4">http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Louisville/LOJIC_PublicSafety_Louisville/MapServer</mapservice>

Now we have two layers, making a drag/drop scenario much more compelling.  If you run the viewer you will see both our layers in the LiveMapsWidget.   Now, just like we had to tell Flex to enabled dragging on the TOC, we have to tell it to enabled dropping.  You guessed it, add

dropEnabled="true"

to the toc element.  Now, when you drag/drop a layer, they will reorder in the TOC, which is nice.  The map layers don’t do anything, but we’ll get there.  We have a problem now, though.  The Tree component makes no distinctions between services (roots, in this case) and the layers that make up those services.  This results in the ability of the user to be able to drop a map service under another map service, which is bad.  You can do a lot of checking when the drop occurs to make sure that the user hasn’t dropped one service as a child of another.  I am gonna go an easier route and collapse all the roots (map services) on drag start so those crafty users can’t do it.  They may not like it, but life is sometimes hard.  So, let’s add a dragStart function:

(on the toc)

dragStart = "onDragStart(event)"

(the function)

			private function onDragStart(event:DragEvent):void{
				//Close the dragged item
				_draggedLayer=toc.selectedItem  as TocMapLayerItem;
				var openItems:Array = toc.openItems as Array;
				for each(var o:Object in openItems){
					toc.expandItem(o,false);
				}
				//setInfoText(resourceManager.getString("resource","toc-layer-reorder"));
			}

K…we’re getting there.  Now to the meat of what needs to happen.  When we drop the service, we need to calculate it’s new index in the map, then tell the map to reorder the services.  It took me awhile to find an algorithm that worked for this, and here is why:

  • The basemaps.  The TOC doesn’t have the basemaps, so you have to take that into account when calculating the new index.
  • If the layer is dragged down, then you have to account for that.
  • The TOC indices are reversed form the map services.  So index 0 in the map is the lowest, but it’s the topmost node in the TOC.

So, nothing overwhelming, but details.    Let’s add the event to the TOC element:

dragComplete = "onDragComplete(event)"

and the code:


private function onDragComplete(event:DragEvent):void{
 if (event.action!="move")
 return;
 var _roots:ArrayCollection = toc.dataProvider as ArrayCollection;
 //Unclear why I have to do this....but I need the selectedIndex later
 toc.selectedItem = _draggedLayer;
 var dropIndex:uint=toc.calculateDropIndex(event);
 // I've seen thie calculated drop index be > than the number of 
 // services.  This usually happens when a service node is expanded,
 // but let's just make sure.  We'll put it at the bottom of the list 
 // in this cse.
 if (dropIndex>_roots.length)
 dropIndex=_roots.length;

 var ind:int=0;
 // Set in onDragStart....if it's null, get outta dogdge
 if (_draggedLayer==null)
 return;
 var delta:int = _roots.length-dropIndex;

 ind = delta + 1;//We have two base layers....HACK...THIS IS BAD, MAKE IT BETTER

 // If the selected item is dragged down, then the index needs to account for that
 if (toc.selectedIndex>dropIndex)
 ind=ind-1;

 toc.map.reorderLayer(_draggedLayer.layer.id,ind);

 }

The comments in that function go through what I am trying to do.  I’ll be the first to admit that it isn’t the prettiest code at the ball, but it’s the one I brought so I am dancing with it (Note to self:  work on better analogies)

So, you’d expect it to work now, woudln’t you?  Well, it doesn’t.  We have to make a minor change to the TOC to handle the layer reorder.   In the TOC.as (in src\com\esri\solutions\flexviewer\components\toc) the onLayerReorder function looks like:


private function onLayerReorder( event:MapEvent ):void
 {
 var layer:Layer = event.layer;
 var index:int = event.index;

 for (var i:int = 0; i < _tocRoots.length; i++) {
 var item:Object = _tocRoots[i];
 if (item is TocMapLayerItem && TocMapLayerItem(item).layer === layer) {
 _tocRoots.removeItemAt(i);
 _tocRoots.addItemAt(item, _tocRoots.length - index - 1);
 break;
 }
 }
 }

Modify the TOC Code (Just a little…)

When you run the viewer now, you’ll get RangeErrors on the addItemAt line above.   So, my approach was to calculate the new TOC index by figuring out the difference between the number of layers and the new index.  Then, make sure somethign didn’t go haywire and we are out of range.  See below:

private function onLayerReorder( event:MapEvent ):void
 {
 var layer:Layer = event.layer;
 var index:int = event.index;
 //How far did we move?
 var addbackind:int=(map.layerIds.length-1) - event.index;
 for (var i:int = 0; i < _tocRoots.length; i++) {
 var item:Object = _tocRoots[i];
 if (item is TocMapLayerItem && TocMapLayerItem(item).layer === layer) {
 _tocRoots.removeItemAt(i);

 // If we are out of range on the high end, rein it in
 if (addbackind>_tocRoots.length)
 addbackind=_tocRoots.length-1;
 // If we are out of range on the low end, rein it in
 if (addbackind<0)
 addbackind=0;
 _tocRoots.addItemAt(item,addbackind);
 break;
 }
 }
 }

May not be the prettiest…etc, etc.  But it works.  You should now have drag/drop reoder working like a champ.  Obviously, this code could still be improved.  The biggest example is accounting for the basemaps in a cleaner fashion.  I will tell you that we used the Specification Pattern to determine if a service was a basemap, allowing the TOC to ask the specification.  I liked that, but didn’t include it here to try and keep this post focused.

Anyway, try it out and see how it goes.  If you have improvments or suggestions, hit me in the comments.  Here is a link to the final LiveMapWidgets.mxml I used for this post.


ArcGIS Server: “Cannot authenticate supplied identity” Error

So, I get this error from time to time in a dev environment or, occasionally, on a client’s server. It used to be very, very frustrating to solve, so I created a checklist of things that worked. I am writing it here to help others, hopefully, and make sure I never forget these steps.  I always try them in the following order.

  1. Make sure the ArcGISWebServices account password has not expired.  If you are comfortable with it, set this password to never expire.  This, more often than not, is the root cause of the issue.
  2. We have seen an issue where having the I_USR account in the “Guests” group on the box caused this error.  Not sure why, but the SA couldn’t tell me why that user was in that group either.Add ArcGISWebServices user to the Power Users group and restart the WWW service.  Again, this may be out of your security comfort zone.  If it is the problem, then you’ll need to mess with the privileges of that account until you find the issue.  Also, I’ve seen this not work until I deleted the user, reran the Post Install and THEN added them to the group.
  3. If it’s still an issue, time to get nasty.  Delete your ArcGISWebServices user and rerun the Web Applications Post Install.  This will force you to create a new ArcGISWebServices user.
  4. Last resort.  Do all the stuff in this thread, as well as the thread that is linked therein.  This is the “throw all the crap against the wall and hope something sticks” step.

Hopefully, that’ll get it done.  If not, and you find something else that works, please let me know.  I don’t run into this issue very often any more (but I did today and step 1 fixed it) but when I do, I am glad I have a checklist.


ESRI Developer Summit Impressions

I am returning to Charlotte in the morning after a nerd-vigorating week in Palm Springs at the ESRI Developers Summit.  If you didn’t get a chance to go, don’t fret as ESRI is posting all of the sessions and code from the week.  Overall, I’ll have to say that this was a great event and the best developers summit yet.   Seeing all of our GIS nerd peers from around the world is often interesting and always informative.

The plenary session highlighted much of what is coming in 9.3.1 and 9.4, not the least of which was ArcExplorer 900.  It’s been Office 2007-ized (ribbon, etc) and is a world away from the previous versions of AGX.  The coolest feature is a Presentation Mode that allows you to create “slides” that include maps and graphics and other things that will likely make it my presentation tool of choice.  It starts an open beta (I think) on April 6th, so get in on it and see what I am talking about.

The ArcGIS Online sharing options that are coming in 9.3.1 are interesting as well.  Fueled by the introduction of “layer packages” which allow you to package data and symbology together.  This data can be uploaded to ArcGIS Online and shared with your friends or the whole planet.  Picture clicking on a hyperlink and having ArcMap open and data being added to the map document.  It was pretty sassy.    ArcGIS Online also has a ton more basemaps, with Virtual Earth basemaps on the way.

Other tidbits:

  • 9.4 and RESTful editing with ArcGIS Server.  I think that will change the game completely, making ArcMap the tool of the serious cartographer while allowing the data owners to change the data.
  • Also, 9.4 comes with major changes to the GDB Schema by reducing the number of tables from 35 to FOUR (that’s right, FOUR).
  • The ArcGIS Server Silverlight API was probably the queen of the ball, with much of the Twittersphere fawning over it.

I went a bit against the grain, forgoing the Silverlight sessions for the Flex sessions.  The Flex sessions were really good, showing how to cluster point data, create custom tool tips for features,  perform thematic mapping. and create geodetically correct circles.   The next version of the api (1.2) will add routing support as it’s being added to the REST api.   I asked about adding automation support to the Flex controls, and was told they are “looking into it”  I hope so.  We are doing more and more with Flex and I plan to harvest more goodies from the summit code samples.

New this year at the summit they added user presentaions, and I attended quite a few:

  • Dave Bouwman did a presentaion on User Testing 101.  Dave’s presentations are always good, and this is a topic that was dear to my heart.
  • Brian Noyle did a bit on ASP.NET MVC, which was so packed that I could only admire it from afar.
  • Chris Spagnuolo on Introduction to Agile Development was incredible.  I am going to try and get everyone at my company to watch this presentation.
  • Vish Uma on Harnessing Server Object Extensions was highly informative.  I think Vish may be the smartest guy I know.

I think my surprise session of the week was OGC Capacities of ArcGIS Server.  AGS has always has WMS, WFS, and (later) WCS support and I have never really paid much attention to them.   Our latest project has some OGC needs, so I decided to attend this session, and I am so glad I did.  Right now, at 9.3, AGS supports WFS Transactions (WFS-T) which is a standard I first heard about in that session.  In a nutshell, this means you can perform edits (insert, update, delete) of GDB features RIGHT NOW.  I had no idea this was possible.   Also, custom styling of features with SLDs means you can easily define multiple styles for your features.  In fact, 9.3.1 or 9.4 will add the ability to to on-the-fly styling using “SLD BODY” (another thing I just learned about)  which is something that we need as well.  I feel like the OGC stuff is the best kept secret of AGS.

Anotehr new item this year was the presence of Twitter.  The #devsummit hashtag was tweet-heavy and, for the first time, Twitter became a very, very useful part of my conference life.  This is where the idea of Twitter really shines and I can finally say that I get it.

So, head on over the the ESRI Developer Summit site, start watching presentations, and downloading code.  Oh, and get on the Twittersphere if you aren’t (I am @ruprictGeek, if you wanna follow….)

Reblog this post [with Zemanta]

Forced to FLEX

I am on a project where we are delivering a web-based mapping application to the client.  The application has gone through every UI scenario you can imagine: Virtual Earth in Sharepoint to ArcGIS Server Web ADF to ArcGIS Server Javascript API to (presently) ArcGIS Server Flex API.  The Flex decision was made against my recommendations.  I don’t have a lot of ammo against Flex, but I was born and raised in JavascriptTown, HTMLandia.  I have read some HTML/JS vs Flex posts and seen all kinds of opinions, and my research has led me to the one true answer to this debate:  It depends.

As with many technical decisions you meet when creating applications for the web, you need to let your context by your driver.  Trust me, I wanted to take HTML Hill and dig in for the victory-or-death battle, but the client liked a couple of Flex sites.   I mean really liked them.  So, rather than just fight the “but that doesn’t look like Flash” battle constantly (even though I think we could win…let it go…let it go…) we are using Flex.

So, I am a lot like my kids eating their spinach.  They get why they have to do it, but they do it begrudgingly and with a nasty look on their faces.   And, much like the vegetable-hating youngsters, I am reaping some benefits:

  • Knowledge is Power. Learning a new language is always a good thing.  One of my favorite books, The Pragmatic Programmer, encourages developers to learn a new language each year.
  • Flex is a good language. Flex has been refreshingly enjoyable to write.  While I still prefer my HTML with a massive heaping of Javascript sauce, Flex (and ActionScript) is a great language (and a ECMAScript standard, like my beloved javascript).
  • Somes things are easier. Some stuff is easier in Flex/Flash.  The best example is probably cross-domain URL access.  If you want to allow a Flash object to access a URL, you put an XML file (crossdomain.xml) in the root of that site.  You can allow access to anyone, or domains, or all kinds of options.  Having struggled mightly with AJAX/REST scenarios involving the same-origin policy, this was really, really great.  Also, the UI is just easier to create.  Sexy, translucent panels and easy drag/drop capabilities come to mind.
  • Flex tries.  Flex does it’s best to use some web paradigms, like CSS stylesheets.  Many of the style properties are specific to Flex, and I don’t profess to understand even half of them, but it made me feel a bit more comfortable.

Now, from the “I told you so” vault, some of the cons:

  • Accessibility (duh).  If we want this web application to be usable on multiple devices (iPhone, screen reader, etc) then we are gonna have to write a totally different interface.  I understand that even in HTML land there is some of that, but it’s easier and allows for more UI code reuse.
  • Unit testing.  While there are a good number of Flex unit testing frameworks (FlexUnit, ASUnit, Fluint, FlexMonkey, and even RIATest (commercial)) they are all difficult to use in my opinion.  Now, part of this reason is because the map is a custom component and is not friendly with the Flex Automation Framework out-of-the-box.  We have struggled finding a unit testing framework that we like, but I think we’re about to settle on Fluint.
  • Continuous Integration.  After unit testing, the next step is continuous integration, and I have no answer for this one yet.  Yes, I know there are things out there like Hudson, Maven, flex-maven, as well as a series of posts on CI with Flex using CruiseControl.  I think my main issue here is my .NET bubble.  We have other services and applications under development for this client, all of which are .NET based.  We have CruiseControl.NET for these .NET based items (thanks CIFactory!) so I have spent much of my time trying to figure out how to integrate our Flex app into that CI setup.  I think this is the wrong approach and we’ll ultimately have to create a separate Flex only CI server.

The best part apart this project has been actually working with a team of developers again.  My projects before this one were cowboy-ish in nature, so I was doing all the nerd stuff.  We have some crazy-smart people where we work, and it’s fun to tackle this new stuff with them.

While I still pine for my HTML/javascript/CSS/C#, writng the Flex stuff has been (and should continue to be)  a learning experience.  Speaking of learning, if anyone reading this has suggestions or approaches to unit testing or CI or anything else to a new bunch of Flex devs, please comment away.

Reblog this post [with Zemanta]

ArcGIS Server Javascript API Gotcha: Identify

I ran into an odd problem after I installed SP1 (actually a few odd problems) with a site using the ArcGIS Server javascript API.  Basically, the identify task broke in IE only.  All was well in Firefox, so I was stumped, as I didn’t receive any error messages.  So, I cranked open Fidder which revealed that the HTTP request was never happening.  Strange, I thought, so I copied the HTTP request that was working in Firefox from the Firebug Net tab and pasted it into the IE address bar, yet it wouldn’t let me paste the whole URL.  So, the URL was too long…but why?  As it turns out the IdentifyParameters use a geometry, and that geometry has a spatialReference, which comes from the map.  The spatialReference we use is WKID 2913, which is one of the Oregon State Plane projected systems.   When the API sets this value on the javascript map object, it uses the Well-Known Text (WKT) version, which is a REALLY long string.  So, I overwrite the spatialReference on the geometry before executing the identify task with a light object that just has the WKID.  Like so:

function doIdentify(evt) {
    taskParameters.mapExtent = this.map.extent;
    taskParameters.geometry = evt.mapPoint;
    // have to do this in order to make this work in IE.
    // otherwise the AGS jsapi will post the WKT which makes the URL too long.
    taskParameters.geometry.spatialReference = { wkid: 2913, toJson: function() { return '{"wkid":"2913"}'; } };

    var task = new esri.tasks.IdentifyTask(serv.URL);
    taskParameters.height = this.map.height;
    taskParameters.width = this.map.width;
    task.execute(this.taskParameters, _identifyResults);

}

Once the URL wasn’t too long, identify started working in IE again.

Anyone else having any issues after installing AGS 9.3 SP1?

Reblog this post [with Zemanta]

ArcGIS Server 9.3 REST API First Looks, Part 1

Esri-logoImage via Wikipedia

You may (or may not) know that I am one of the authors of the ArcDeveloper REST API, which is an open source REST API for ArcGIS Server that can be used against ArcGIS Server (AGS) 9.2, right now, free to all comers, no waiting, etc.  If you know that, you probably know that ESRI is releasing it’s own REST API for AGS at 9.3, due out later this year.  So, I thought I’d take a few posts to show you how the REST API (in beta) is looking.  For the impatient, it looks great.  For others that may not have the beta, read on.

So, first off, we are just gonna check out the very basics of the REST API.  When you install 9.3, there is no real indication of anything all that different.  The AGS Service Properties dialogs look (basically) the same, except for some (hugely exciting, but beyond the scope of this article) changes to the Caching tab.  The real excitement comes when you start messing around with URLs against your map server.  BTW, I am using Fiddler2 with the JSON Viewer plug-in to send requests to the server and look at the results.

AGS 9.0 and later have always exposed a SOAP endpoint for each service it publishes as well as service catalog.  There is a whole SOAP API, which is my preferred method to interact with AGS services, and the URL to the SOAP service catalog looks like:

http://server/arcgis/services

Following this pattern, the REST API brings along a similar service catalog endpoint:

http://server/arcgis/rest/services

The new-kid-on-the-block REST URL is even cooler, though, as it formats the results into a cool “ArcGIS Service Explorer” that looks like:

ArcGIS Services Explorer at 9.3

Which allows you to look at metadata about your services, view them in Google Earth (how cool is that?), ArcMap, ArcGIS Explorer, or the new ArcGIS Javascript API (a subject of posts to come).  Also, you can drill-down into your service and get layer info, spatial reference, unit info, and tiling information.  In fact, on the tiling information, you can actually LOOK at some of the tiles.  Other information includes supported operations that you can execute within the Explorer, like Export Map or Identify.  Here’s a picture of the service page:

Tons of info right out of the box.

Another item the Service page has is a “Supported Interfaces” section, which I would call supported formats.   The ones listed out of the box are REST and SOAP.  Clicking on the REST link will give you all (well, most of) the information just mentioned, but in JSON format .   Here, lemme show you:

Fiddler (w/JSON Viewer) display of REST GET Request of AGS

The only difference between the URL to get to the cool HTML Services Explorer page and the URL that spits out JSON is the addition of a ‘f’ (for ‘format’) querystring parameter, like so:

http://server/arcgis/rest/services/ServiceName/MapServer?f=json

MMMMM….now, THAT’s some good REST.

There is similar love for supported operations, allowing you to crank out a quick image or perform a Find and look at the results.   Also, the (impressively comprehensive) API reference is linked on the Services Explorer pages, which, oh by-the-way, takes you here.

Finally, the last thing I want to cover in this post is the REST admin inteface.  If you go to the following URL in your browser:

http://server/arcgis/rest/admin

you’ll be hit with a login screen for the ArcGIS REST API Admin application.  It’s pretty sparse right now, not even meriting a screen shot.  Currently, you can use the admin site to tell AGS when to recycle the cached service responses and to turn on the REST Services Catalog.  Now, that second option was nowhere to be found in my admin app, but the docs assure me it’s there.

So far, the only disappointment I have had is that ESRI has chosen not to use GeoJSON as their format to return features in (or, for that matter, even as an option).  I don’t profess to know why, but I’ll try to float that to one of my contacts on the mother ship soon.

Summing up, the AGS REST API looks great before you even show a map on a site.  I am hoping to go that in future posts.


JSONP Support Added to ArcDeveloper REST

I have previously blogged about the ArcDeveloper REST Service that a few of us ESRI .NET developers have thrown together. Until today, the only format you could request was GeoJSON. Now, through the miracle of something-I-needed-on-a-project, I have added JSONP support to the project. For those of you who don’t know what JSONP is, it is JSON wrapped in a javascript call. So, if you have JSON that looks like:

{
“type”: “FeatureCollection”,
“features”: [
{
"type": "Feature",
"geometry": null,
"properties": {
"Facility": "SI",
"Shape_Area": "4.93182604749608E-05"}
},
{
"type": "Feature",
"geometry": null,
"properties": {
"Facility":"Test",
"Shape_Area": "2.27729778462692E-05",
}
} ]
}

With JSONP, it looks like:

javascriptFunc(‘{
“type”: “FeatureCollection”,
“features”: [
{
"type": "Feature",
"geometry": null,
"properties": {
"Facility": "SI",
"Shape_Area": "4.93182604749608E-05"}
},
{
"type": "Feature",
"geometry": null,
"properties": {
"Facility":"Test",
"Shape_Area": "2.27729778462692E-05",
}
} ]
}’);

In a nutshell, JSONP was created to get around the pesky same-origin policy that browsers use to keep AJAX calls from making requests to a domain outside of the hosted site. This can be a real bugaboo, especially when you have a distributed application where, for example, ArcGIS Server is on one box, your web server is on another, and Sharepoint is on another. With JSONP, the client call can be wrapped in a <script> tag, which does not have to adhere to the same-origin policy. The <script> tag comes back with your JSON wrapped in a javascript call you specify (more on this is a second) and voila! we are cooking with gas.

In order to successfully call a JSONP service, you have to specify the callback function as a querystring parameter. As such, you have to know which querystring parameter the service is expecting to hold the callback function name. So, if we define a local javascript function called ‘jsonpCallComplete(json)’, then the script block comes back as:

<script type=’text/javascript’ src=”http://otherdomainserver/restservice/query?format=jsonp&callbackFun=jsonCallComplete’ > jsonpCallComplete(‘{“property”:”value” …etc…}’);</script>

Which fires your javascript call, passing in the JSON for you to do with as you please.

As a result of added this to the ArcDeveloper REST API, I had to make some “breaking” changes to the project. Firstly, the format must now be specified using the ‘f’ querystring parameter (i.e., f=json or f=jsonp) which maps to a ‘format’ parameter on the service methods. The value of the ‘f’ querystring parameter must match the name of the formatter, as configured in the Windsor configuration. Secondly, in order to pass the callback function name to the JSONP formatter, I chose to basically pass ALL the querystring paramters to every Formatter. That way, if another formatter needs a specific querystring parameter, it will supplied by the service manager for each request. Check the code for more details, if you are interested.

Dojo (a pretty danged good javascript framework) has native support for JSONP services, allowing you to, in essence, treat a cross-domain AJAX call as a same-origin AJAX call. The Dojo site has decent docs and a overview of Dojo is way beyond the scope of this post, but here is an example AJAX call to the ArcDeveloepr REST API on one box from a client site on another:

(you will need to do a dojo.require(‘dojo.io.script’) for this to work)

dojo.io.script.get({
url:”http://gisdev02/AGSRest/rest.svc/facility/Facilities”,
content: {
query:”name=’Airport’”,
f:’jsonp’ // specify the format
},
callbackParamName:’callback’, //specify the qs param to send the javascript func name
load:dojo.hitch(this,function(resp){
var featurecollection=dojo.fromJson(resp);
dojo.forEach(featurecollection.features,dojo.hitch(this,function(feat){
var opt = createNode(“option”);
opt.value=feat.properties.extent;
opt.innerHTML=feat.properties.Facility;// SEE? It’s JSON!
this.bookmarks.appendChild(opt);
}));
this.element.appendChild(this.bookmarks);})
}

Dojo is kind enough, in this case. to call the function defined in the ‘load’ property of the dojo.io.script.get method above. As far as your code knows, it’s just an AJAX call, but it only works for services that support JSONP.

So, that is JSONP support. I plan on checking in the code after some of the other ArcDeveloper types chime in on my “breaking” changes (mentioned above), which should be tomorrow.

By the way, for anyone developing there own JSONP services, here are a couple of things I ran into:

  • Make sure you strip all whitespace out of the JSON before streaming it back to the client. I used a Regex for this.
  • The trailing semi-colon ( ; ) on the JSONP call is, apparently, very important.

I’ll edit the post if I hit other issues in the testing.


ArcDeveloper REST: Windsor Brings the Party That Rocks the Body

NOTE: Dave has blogged about the REST API, including a demo of it in action! Check it out!

In part III of my series on the ArcDeveloper REST API, I want to focus on how we use Windsor to configure the service. The best way to to that is to take a step back and look at why Windsor exists. Windsor is an Inversion of Control (IoC) container that provides a robust Dependency Injection (DI) framework. Both of those phrases have been blogged about by just about every human in existence, so if the Fowler link doesn’t help, then hit Google.

Background

In a nutshell, when you have classes that depend on other classes (and, really, what project doesn’t have that? Answer: Maintenance nightmare projects) a good pattern to follow is to supply the dependencies as opposed to instantiating them from within the class. The consummate example is:

public class ClassA{

  private ClassB _classB;

  public ClassA()

  {

    _classB=new ClassB();  //This is pure evil

  }

}

The above code violates so many design patterns and principles that I had to have my 7-year old actually type it (he is a TOTAL hacker) b/c I couldn’t bring myself to do it. A better way to do it is:

public class ClassA{

  private ClassB _classB;

  public ClassA(ClassB injectedClassB)

  {

    _classB=injectedClassB;  //This is sunshine and puppy dogs

  }

}

That code is an example of constructor injection, b/c the dependency must be provided to the constructor or the class cannot be instantiated. There is also setter injection, where public properties are exposed and the dependencies are suppled there. Both approaches have their pros and cons, and I am of the opinion that, if you are simply using DI in either form, you are WAY ahead of the game.

Before I leave the background, I would be remiss if I didn’t point you to the Bitter Coder’s Wiki, where the best tutorials on Windsor live and party.

Great, So How’d Does the ArcDeveloper Rest Stuff Use Windsor?

Well, the Windsor container can use an external configuration section, say, you the web.config file.

<configSections>

<section name=castle type=Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor/>

</configSections>

Once that is defined, you have three major configuration areas that you can play with: facilities, properties, and components (NOTE: You’ll find a separate .config file for each of these sections in the web project of our REST stuff.) Facilities are, basically, an extension to the Windsor container. There are many existing facilities, but the easiest to grok is probably the LoggingFacility. You register facilities on the container like so (from our facilities.config file):

<facilities>

<facility

id=logging

type=Castle.Facilities.Logging.LoggingFacility, Castle.Facilities.Logging

loggingApi=log4net

customLoggerFactory=Castle.Services.Logging.Log4netIntegration.Log4netLogger

/>

</facilities>

Here I have registered the Log4Net implemenation of the logging facility, which I could change to a different logging implementation by merely changing the “loggingApi” value (NOTE: I omit the logging.config that is required to configure Log4Net as it’s an, erm, implementation detail.) Once defined, any components registered on the container that have an ILogger public property will automagically get an instance of my Log4Net logger. That is just cool, man.
The rest of our time together will be spent working with components (we aren’t currently using properties right now, but they rock and the tutorials cover them well.) The ArcDeveloper REST API has services that provide the query functionality (like an ArcGIS Server provider) and formatters that take the results from the providers and transform them into the requested format. So, this means that we can register any provider and any formatter we might want to use with Windsor, and then all we have to do is tell the RESTServiceManager (our web service implementation class) about them. Here’s a picture (not quite 1000 words worth, but maybe a quick fin’s worth)

High Level Architecture of ArcDeveloper REST API

So, let’s quickly register the ArcGIS Service provider, which is implemented in the AGSMapService class.

<component id=ags.service lifestyle=pooled initialPoolSize=2 maxPoolSize=2

service=ArcDeveloper.REST.Core.Interfaces.IRESTService, ArcDeveloper.REST.Core

type=ArcDeveloper.REST.ArcGIS.AGSMapService, ArcDeveloper.REST.ArcGIS>

<parameters>

<name>TestService</name>

<description>Base map</description>

<connectionString>http://65.101.234.201/arcgis/services/gains/gains/MapServer</connectionString>

</parameters>

</component>

There you go. The AGSMapService class implements the IRESTService interface, and we provide the pertinent parameters. The “name” parameter (value=”TestService”) will be what is provided in the URI in order to specify that we want to use this service. The “connectionString” parameter is the URL of our ArcGIS Server Map Server object. Look closely at the attributes of the component, and you will see that we are pooling 2 instances of the service, which allows us to connect on first request and keep the object around for other requests. Since connecting to the server is a very expensive operation, we only have to live with it once. Windsor does this all for you! What do YOU do for Windsor? HMMM? That’s what I thought.

Anyway, lets show the formatter:

<component id=geojson.formatter lifestyle=transient

service=ArcDeveloper.REST.Core.Interfaces.IFormatter, ArcDeveloper.REST.Core

type=ArcDeveloper.REST.Core.Services.GeoJSONFormatter, ArcDeveloper.REST.Core>

<parameters>

<name>geoJSON</name>

</parameters>

</component>

So, our GeoJSONFormatter implements the IFormatter interface. We name this one “geoJSON” which, whenever we write a second formatter, will be how the URI will refer to it when requesting the GeoJSON format. Oh, and the lifestyle of this bad boy is “transient”, meaning it’s created on request and disposed after request. This is not an expensive item, so that’s how we roll.

Finally, let’s take a look at the RESTServiceManager:

<component id=rest.service lifestyle=singleton

service=ArcDeveloper.REST.Core.Interfaces.IRESTServiceManager, ArcDeveloper.REST.Core

type=ArcDeveloper.REST.Core.RESTServiceManager, ArcDeveloper.REST.Core>

<parameters>

<services>

<list>

<item>${ags.service}</item>

<!– <item>${other.service}</item>–>

</list>

</services>

<formatters>

<list>

<item>

${geojson.formatter}

</item>

</list>

</formatters>

</parameters>

</component>

You can see that our RESTServiceManager implements our IRESTServiceManager interface and we register our AGSMapService and GeoJSONFormatter with the service manager. The “services” and “formatters” properties of the service manager are lists, so we can add more services and more formatters simply by registering them on the container and adding them as an <item> to the list. Let me say it another way, just to drive the point home: If you wanted to register another ArcGIS Service map service with the RESTServiceManager, you would register with Windsor (so, copy the “ags.service”, give it a new id and change the <connectionString>) and then add an <item> to the <services> parameter with the component id (oh, you may have to restart your web app, as changes to the external config files do not kick off an app unload. If you don’t like it, pull all the config sections into web.config). After that, you can issue REST queries against the map service. It’s just that easy.

One final caveat. The IRESTServiceManager is not only our service manager, but it is also our WCF service contract. When you host a WCF service in IIS, it is created by the WCF Service Host factory, which means there is no way to register it with the Windsor container. That is bad, because if we can’t register the service manager, then we can register the services, and pretty much we have a web service that does bugger all. In order to get around this, the (man and legend) Ayende wrote a component that will allow you to register your WCF services with Windsor. The documentation for it is here, and is so eloquently written that I am stunned that I, er, I mean, the author didn’t get at least a Pulitzer nomination. The long and short of it is;

- Change the .svc file to use a custom ServiceFactory (from our rest.svc file)

<%@ ServiceHost Service="rest.service"
 Factory="Castle.Facilities.WcfIntegration.WindsorServiceHostFactory, Castle.Facilities.WcfIntegration" %> (GRRR....CopyAsHTML doesn't help me in .svc files)

- Instantiate the container at application start up, using the Global.Application_Start method (from our Global.asax.cs file)

protected void Application_Start(object sender, EventArgs e)

{

container = new WindsorContainer(new XmlInterpreter());

WindsorServiceHostFactory.RegisterContainer(container.Kernel);

_log = container[typeof(ILogger)] as ILogger;

_log.Info(“Services app started successfully.”);

}

That’s it. Now the WCF service will use the “rest.service” component we registered in the config file above.

So, this post got a bit wordy on me. I am young in my blogging ways, so I have trouble focusing. Also, I am easily distracted by shiny objects. In future posts, I plan to show how we leverage the Json.NET stuff for the GeoJSON formatter and maybe write a blog about something besides REST.


ArcDeveloper REST: U R I and I R U

As I promised in my (first and) previous post, I am gonna quickly go over the very simple architecture of the ArcDeveloper REST API. This post covers how we expose our REST endpoint(s).

At a high level, we wanted to expose a single REST endpoint that allowed the caller to specify the “service”, “layer”, and query parameters in order to get geographic features returned in a format, which is also specified by the caller. So, attempting to be as RESTy as possible, the URI looks like:

 http://<yourServer>/rest.svc/service/layer/3?format=geojson

It’s clear and easy and other URI endpoints are easy to deduce. Yup, we are RESTing we the best of them. Our main endpoint is the rest.svc, which is WCF’s way of exposing a service endpoint. The first question, then, we had to answer was how we would handle the URI routing. If the map services are registered dynamically (using Castle Windsor, which is arguably the greatest piece of code ever written. Ever. Not that I am prone to hyperbole. Oh, I’ll cover the configuration in the next post) then we have to dissect the various pieces of the URI to:

  1. Get a hold of the service requested.
  2. Query the right layer
  3. Use the right query parameters.
  4. Grab the right formatter.

Luckily for us, .NET 3.5 had recently been released, incorporating some cool changes in WCF, the coolest one being the URITemplate (good background here). The URITemplate allows us to define URI templates (duh) and map them to service methods (or OperationContracts, in WCF-speak) complete with the mapping of parameters of the method. Let’s see how we are using this now:

[OperationContract]

[WebGet(UriTemplate="{serviceName}/{featureType}/{featureId}?g={geom}",ResponseFormat=WebMessageFormat.Json)]

Stream GetSingleFeatureJSON(string serviceName, string featureType, string featureId, bool geom);

You can see how the dealies in the “{}” get mapped to the method parameters. Just to be complete, {serviceName} maps to the serviceName parameter, etc. The “geom” parameter specifies whether or not you want all the coordinates returned, because sometimes that can be a LOT of data. We also specify the response format here and the HTTP verb (WebGet== HTTP Get. I’ll give you 3 guesses how you specify HTTP POST. Wrong. It’s WebInvoke.)

So, I know what you are saying. “WTF? You have hard-coded the format INTO THE BLOODY METHOD NAME! I am not reading any more of this tripe.” Well, that’s your prerogative (Tangent: I had NO idea that was how to spell that word. No wonder Bobby Brown is on drugs) but know that I am adhering to the Last Responsible Minute (LRM) doctrine. We only need (geo)JSON right now, meaning, no one has written any other formatters. Once we have another format, we’ll put in a “format” parameter, remove the ResponseFormat and life will be all good. Weak excuse, you say? Maybe. Let’s, uh, move on.

The URIs the project currently supports are:

  http://yourserver/rest.svc/services

Will return information about all of the services that are available to be queried.

  http://yourserver/rest.svc/serviceName/layerName/OBJECTID?g=true

Will return the feature corresponding to the OBJECTID value from the layer specified by “layerName” in the service configured as “serviceName”.

  http://yourserver/rest.svc/serviceName/layerName?query=whereClause&bbox=xmin,ymin,xmax,ymax&g=true

Will query layer named “layerName” in service configured as “serviceName” using the value in “whereClause” (like height>100 or streetname=’Main’ — don’t forget to escape your querystrings….) inside the bounding box specified by xmin, ymin and xmax, ymax.

As always, g=true returns the full coordinates of each geometry. Also, the serviceName corresponds to the name given in the Windsor configuration, NOT the name of the ArcGIS Server service.
In the next post (hopefully) I’ll cover the service and its configuration, showing you how we leverage the total kick-assness of Windsor to make life easy.


ArcDeveloper ArcGIS Server REST API is Breathing!

So, the angst around the ArcGIS Server Web ADF is well known and I shan’t rehash it here (OK, just a little rehashing: the ADF is a bloated sack of vomit) but, rather, I’ll point you to the beginnings of an open source ArcGIS Server REST API (svn) at ArcDeveloper.NET. The project is very young (and always looking for contributors) and, in it’s current state, has the following capabilities:

  • You can query single features by id (must be OBJECTID, ugh)
  • You can query features with a where clause
  • You can query features with a bbox (AND a where clause, if you want)

In this post, I will walk through what it takes to set it up and point it at one of your ArcGIS Server services.

What You’ll Need

Visual Studio 2008, implying .NET 3.5. You will have to build the solution, as we haven’t made an official release yet.

Optionally, I would have Fiddler, Firebug, and JSONViewer.

Step 1: Get the source

Using TortoiseSVN (or the svn client of your choosing), perform a checkout of the trunk (http://svn2.assembla.com/svn/arcdeveloper/ArcDeveloper.REST/trunk/)

Step 2: Open the solution

In the “Product” directory, you’ll find the VS2008 solution file. Like, open it or something. It consists of 5 projects, 2 of which are test projects. The other projects are the core interfaces and services, with the last one being a web project to show how to publish the REST service with WCF and a demo web page.

Step 3: Build the solution

Uh, in VS2008, select “Build Solution…”

Step 4: Use Dave’s Stuff

So Dave Bouwman, who I think is just the cat’s pajamas, has the demo site using an ArcGIS Server map service that he has been generous enough to provide. If you look in the ArcDeveloper.REST.Web project, you’ll find a config directory. Opening up the components.config file will show where the endpoint of the map services is specified. Looks alot like:

<component id=ags.service lifestyle=pooled initialPoolSize=2 maxPoolSize=2

service=ArcDeveloper.REST.Core.Interfaces.IRESTService, ArcDeveloper.REST.Core

type=ArcDeveloper.REST.ArcGIS.AGSMapService, ArcDeveloper.REST.ArcGIS>

<parameters>

<name>TestService</name>

<description>Base map</description>

<connectionString>http://65.101.234.201/arcgis/services/gains/gains/MapServer</connectionString>

</parameters>

</component>

Do you see the cool <connectionString> parameter? That’s Daves AGS service (I told you he was cool.) Run the site (it uses the VS Dev server now) and click “Load Polygon”. You’ll see a small polygon drawn over Yemen (erm, at least I think that is Yemen). Change the ID and draw some more.

ArcDeveloper REST API Demo Page

Here’s what is happening:

A HTTP GET is issued to the local web service with a URI that looks alot like:

 http://localhost:xxxx/rest.svc/TestService/Flyways/10?g=true

Let’s break down the URI, shall we? Starting with rest.svc, that is the WCF endpoint for the REST service. “TestService” points to our configuration (from above) file and tells the service which map we want to use. “Flyways” is the name of a layer in Dave’s service. “10″ is the id of the feature we want, and “g=true” tells the service to return the geometry (there are cases where you don’t want that, b/c it can make the size of the response baloon pretty quickly.) So, that is pretty RESTy, yes?

The response from that HTTP GET looks like:

{  "type": "Feature","geometry": {"type": "Polygon",

"coordinates": [

[

[ 44.3231, 14.2555], [43.2827,13.6294],[ 43.285, 13.6503],        (...lots more coords...)     ]

},

"properties": {

"Ssp": "maculosus",

"Species": "Burhinus capensis",

"Shape_Length": "4.59630121045653",

"TSN": "0",

"extent": "42.9447173339071,13.6294196034485,44.3230947030963,15.0950604545328",

"SpeciesCod": "BURCA",

"WISDOM_SpeciesID": "557",

"OBJECTID": "10",

"Code": " ",

"Shape.area": "1.28930099595786"

}

}

Which is valid GeoJson. Neat, eh?

I know what you are saying. “Glenn, why would I use this when ArcGIS Server 9.3 will have a REST API?? HMMM?” Well, that is a great question. The only answer I have is that you can use this now, contribute to it, and make the world a better place. Also, you can learn about a lot of stuff, like REST, WCF, GeoJSON, AJAX, the Castle Project stuff, and much, much more. Plus, there are some real brains on this project (I am not one of them) so you can sop up their wisdom as well. Also, I heard a nasty rumor that the AGS REST API wasn’t using GeoJSON, but a proprietary spatial JSON format, which is a bit disconcerting, if not totally expected.

In future posts I’ll break down the ArcDeveloper REST API architecture, showing how you can write your own providers and formatters. We still have a TON of work to do on it, but we’re on our way.


Follow

Get every new post delivered to your Inbox.