Ultimate Developer Rig: 2009 Edition

Posted by Dave Bouwman | Posted in Hardware | Posted on 24-02-2009

5

We are bringing in two new developers, which means it time to build out two new boxes!

In December 2007 we built up our original set of workstations based on the famous Scott Hanselman / Jeff Atwood “Ultimate Developer Rig” specifications, and they look like these.

workstation

This go-around, we were aiming for the same price point – ~$2000 per workstation, and not surprisingly our cash went a little further. Here’s the parts list from NewEgg.

($89.99) HD 500G|SEAGATE 7K 32M ST3500320NS – OEM 500Gb Secondary drive

($169.99) MNTR ACER|LCD 22″ 5MS X223WBD R – Retail (3 per workstation)

($124.99) CASE ANTEC|P182 BK RT – Retail (Same cases as last time)

($327.99) CPU INTEL|C2Q Q9650 3G 775 45N RT – Retail  (upgraded to 3.0Ghz Core 2 Quad)

($169.99) MB MSI P45D3 PLATINUM 775 P45 RTL – Retail (More recent MSI Motherboard)

($95.99) VGA MSI N9600GT-T2D512-OC 9600GT RT – Retail MSI GeForce 9600 512Mb Video Card – two per box

($109.99) PSU CORSAIR|CMPSU-520HX RT – Retail Corsair Power Supply

($85.99) MEM KINGSTON 2×2G KVR1333D3N9K2/4G – Retail 4GB DDR3 RAM

($179.99) HD 150G|WD WD1500HLFS % – OEM 150GB Western Digital 10k RPM primary drive

($26.99) KEYBOARD MS | A13-00002S % – OEM MS keyboards

($12.99) MS MS|K80-00034 OEM WIRELS % – OEM Wireless Mice

($26.99) DVD BURN SAMSUNG|SH-S223Q LS % – OEM Litescribe DVD player/burner

All told, we are looking at $1922 shipped. A similarly equipped Dell runs about $5000, so we’re feeling pretty good.

Our current boxes are 3.0Ghz Code 2 Duo’s, and they are blazing fast, so we have high expectations for these new ones. And although I’d like to say that these boxes are going to the new hires, I’m more pragmatic that that – Mike and Jeff are getting the systems, and Brian and I are setting ourselves up with three 22 inch monitors each. So yeah – the new guys are getting hand-me down workstations – I think they will survive though!

We are going to be running Vista 64 on these, and I *hope* to avoid issues with ArcGIS Server that I ran into in the past. I expect that we will be able to run some VM’s on these systems as well as our base development environments – thus allowing every project to have dedicated server configurations. 

According to UPS, Santa arrives Wednesday. I’ll post more about the build out.

Displaying Large Selection Sets with the ESRI REST API

Posted by Dave Bouwman | Posted in Ajax, ArcGIS Server, Google Maps, Javascript, jQuery | Posted on 20-02-2009

2

On my current project, we need to select project locations (points) on a map, create a list of the unique “Projects” which can then be used for reporting. We are using the ArcGIS Server Google Map Extension as that’s the best fit for this project. Overall, this sounds simple enough.

Here’s the wrinkle – there are 1800+ project location points in the dataset. And, these 1800 points actually represent only 33 projects. Ah, 1 to Many’s… this will be “fun”…

500 is the Magic Number

By default, the REST API (which delegates to the SOAP API behind the scenes) only returns the first 500 features when doing a query. This setting can be changed on the server, but it’s there for a reason – you want to limit the number of features that are sent back to the browser. This limits the amount of data on the wire, and keeps your application responsive. In addition, drawing more than a few hundred features on any javascript canvas will make most browsers come to a crawl. Flex and Silverlight have less issues with this, and for applications where you are dealing with large sets of data.

Ok – but how are we going to handle this? We need to select features inside polygon, but we are only going to get 500 features back.

We did some poking at the data, and found that the distribution of project locations to projects is heavily skewed… this graph shows the number of project locations per project.

graph

As we can see, there is one project with nearly 1700 project locations. Most of the rest of the projects have 4 or less locations.

So – even getting just 500 features back, we’ll likely cover off the project in question.  Of course this is not 100% guaranteed – in the long run we may revisit this, and if the client want’s to invest time/money in some back-end ArcObjects development and we can solve the issue that way.

Doing the Query

In any event, in the callback from the GPolygon enableDrawing() method, takes the geometry, and does a query against the project locations layer. Since we don’t want to actually try to draw 500 points, we do not have the geometry returned – just the ProjectID of the features. Remember that returning the smallest set of information is always a good idea.

We then loop over the features, collecting a list of unique the ProjectId’s. At the same time, I’m creating a WHERE clause that can be used in a definition query.

var itemList = new Array();
var defQuery = "PROJECTID IN (";
for(i=0;i<fset.features.length;i++){
    var feature = new esri.arcgis.gmaps.Feature();
    feature = fset.features[i];
    if($.inArray(feature.attributes.PROJECTID, itemList) == -1){
        itemList.push(feature.attributes.PROJECTID);
        defQuery += '\'' + feature.attributes.PROJECTID + '\',';
    }
}

Layer Definitions

Instead of showing the selected project locations as push-pins, I’m using a definition query against a separate “Project Location Selection” layer in the map. Once I have all the PROJECTID’s, I update the layer definition for this selection layer…

defQuery = defQuery.substring(0,defQuery.length -1);
defQuery += ")";
layerDefs[0] = defQuery;
dynamicMap.setLayerDefinitions(layerDefs);

From here, I now use some jQuery to loop over the array of ProjectID’s, make Ajax calls to a controller to get the details of the project to populate the results list area. Since that get’s long, I’m going to skip those details – suffice to say I create a mess of <li> tags and float them so I get a pretty list.

Now, there is some possibly un-expected behavior with this – since the definition query is based on the “one” side of the “one to many”, the selection we end up showing is “all the project locations related to the projects in the polygon”.

This makes more sense when you see it…drawing-poly

This is the map right before we finish the polygon. The Project Locations are the gray dots. The Results area is currently empty, and the reporting tools are disabled.

after-draw

After the polygon is finished, we get the unique list of project ID’s, set the “selection layer” definition (resulting in the green dots), and fetch the project details from the controller and fill up the results area.

As you can see, there are a lot of selected (green) dots outside the selection polygon – this is mainly because of that one project that has ~1700 locations – if we get just one of those back from the spatial query, we’ll be “selecting” all these points.

Regardless of the 1 to Many issues on this project, using a definition query against a dynamic map layer can be a good way to show the selection of a large set of features, or of features with very complex geometries.

jQuery on FindNFollow: Creating the Tag Cloud

Posted by Dave Bouwman | Posted in FindNFollow, Javascript, jQuery | Posted on 17-02-2009

1

The first jQuery code that you run into at FindNFollow.com is the tag cloud that lets you find Twitter users based on the topics they have tagged themselves with.

image

The application flow here is pretty simple:

1) User picks an area of interest

2) Clear and reload the tag cloud.

In this post I’m going to dissect how this works from the jQuery perspective.. but first let’s look at the markup since everything starts there.

        <h2>1) Select an Area of Interest:
        <select id="areaName">
            <% foreach (string areaName in ViewData.Model.Areas)
               { %>
            <option>
                <%=areaName %></option>
            <%} %>
        </select>
        </h2>
        </div>
        <br />
        <h2>2) Click on a tag to find people to follow:</h2>
        <div id="tagList">
            <ul id="cloud">
            </ul>
        </div>

This html is a part of as ASP.NET MVC View – similar to a normal ASP.NET aspx page, but we don’t use server controls. For more information on ASP.NET MVC, visit http://asp.net/mvc.

As we can see, the select list drop down box gets populated on the server, but the actual tag cloud does not. All we have is a div (id=tagList) and a unordered list (id=cloud)

As we can see there are no in-line event handlers here – just plain old html (with some in-line code).

We apply the event handlers, the behavior if you will, in the jQuery “document ready” event. A quick note for those who have not used jQuery before – the “$” character is mapped to the jQuery object.

Ok, so here is all the javascript we need to do this.

$(document).ready(function(){
  $("select").change(function () {
     var str = "";
     $("select option:selected").each(function () {
       currentArea = $(this).text();
       $("#cloud").empty();
       //Get the json from the server
       $.getJSON(baseurl + "/GetTagsWithTweepsForArea?areaName=" +  jQuery.trim(currentArea),
       function(data){
         $.each(data, function(i,item){
           $('<li><a href="' + baseurl + '/item/' + encodeURIComponent(jQuery.trim(item.Name)) + '" class="tag' +
                   item.Rank + '" >' + item.Name + '</a></li>').appendTo("#cloud");
         });
       });

     });
   })
   .trigger('change');
 });

It looks pretty dense when you are first starting out, but it’s pretty easy really… lets dissect this…

First off, we need to attach this behavior when the document is done loading, so we define a function for jQuery to call when this happens.

$(document).ready(function() {
  //stuff to do when the page is loaded
});

This is the basic “do stuff when the page is loaded” construct – basically it’s saying “Hey jQuery, (aka $), when the ready event of the document object fires, do this stuff as well”.

So what do we want it to do? Well, we need to setup an event handler for the select box. Let’s add that in…

$(document).ready(function() {
    //stuff to do when the page is loaded
    $("select").change(function() {
    //Stuff to do when select list changes
    });
});

In this case, we only have one select box on the page, so we can attach this event to the select tag. If we had multiple select elements on the page we can target the event more specifically by specifying the id – like so  $(”#id-of-the-select-box”).change(function(){ //do stuff});

And what do we want to happen when the select box changes? Well, we need to go and get the tags that match the selected area, and load them into the tag cloud. We’ll take this in steps. Lets get the selected item from the list…

$(document).ready(function() {
    //stuff to do when the page is loaded
    $("select").change(function() {
        //Stuff to do when select list changes
        $("select option:selected").each(function() {
           var currentArea = $(this).text();
        });
    });
});

In preparation for reloading the tag cloud, lets clear it out… this is very simple – we just select the tag cloud element, and tell jquery to empty it – with $(”#cloud”).empty

$(document).ready(function() {
    //stuff to do when the page is loaded
    $("select").change(function() {
        //Stuff to do when select list changes
        $("select option:selected").each(function() {
            var currentArea = $(this).text();
            $("#cloud").empty();
        });
    });
});

Ok, now we need to make an ajax call to a Controller method that will get us the list of tags in this area. For simple “GET” requests, jQuery has a nice simple syntax… ok, it’s not that simple the first time you use it, but it’s still pretty simple…

$(document).ready(function() {
    //stuff to do when the page is loaded
    $("select").change(function() {
        //Stuff to do when select list changes
        $("select option:selected").each(function() {
            var currentArea = $(this).text();
            $("#cloud").empty();
            $.getJSON(baseurl + "/GetTagsWithTweepsForArea?areaName=" + jQuery.trim(currentArea),
            function(data) {
                //stuff to do with the returned data
            });
        });
    });
});

To dissect this a little more, $.getJSON is the jQuery function that will make a XmlHttpRequest (aka XHR) to a specified Url, and then handle the returned data as Json. For this site, we call the GetTagsWithTweepsForArea method on the Tag controller. According to the routing setup in my application, this is on the TagContoller. Instead of hard coding the url, I’m using the baseurl variable to hold the relative part of the url – this allow me to run it on localhost and the live server without changing any code. The actual url is http://www.findnfollow.com/Tag.aspx/GetTagsWithTweepsForArea?areaName=GIS and you can follow that link to see the Json. Here’s a quick capture from FireBug…

image

We can see that this is an array of “tag” items. Now that we have the data, we need to create the tag elements, and add them into the page.

Conveniently, getJSON does the dirty work of creating objects from the raw json – we just need to iterate over it. And again, there is an easy construct for this.

$(document).ready(function() {
    //stuff to do when the page is loaded
    $("select").change(function() {
        //Stuff to do when select list changes
        $("select option:selected").each(function() {
            var currentArea = $(this).text();
            $("#cloud").empty();
            $.getJSON(baseurl + "/GetTagsWithTweepsForArea?areaName=" + jQuery.trim(currentArea),
            function(data) {
                //stuff to do with the returned data
                $.each(data, function(i, item) {
                    //stuff to do with each item
                });
            });
        });
    });
});

So – our tag cloud is housed in an unordered list (<ul></ul>) so we need to create list items (<li></li>) for each tag, and then append them to the un-ordered list. jQuery uses “chaining” to build up complex expressions, and we’ll use that here…

$(document).ready(function() {
    //stuff to do when the page is loaded
    $("select").change(function() {
        //Stuff to do when select list changes
        $("select option:selected").each(function() {
            var currentArea = $(this).text();
            $("#cloud").empty();
            $.getJSON(baseurl + "/GetTagsWithTweepsForArea?areaName=" + jQuery.trim(currentArea),
            function(data) {
                //stuff to do with the returned data
                $.each(data, function(i, item) {
                //stuff to do with each item
                    $('<li><a href="' + baseurl + '/item/' + encodeURIComponent(jQuery.trim(item.Name)) + '" class="tag' +
                        item.Rank + '" >' + item.Name + '</a></li>').appendTo("#cloud");
                });
            });
        });
    });
});

So we now have the list items in the list. We can see that we are constructing an anchor (link) inside each tag – these will take the user to the page for that tag. This is made possible because I designed the urls to be meaningful. I use server side code to calculate the relative size of the items by getting the number of users for each tag, then normalizing that, and ranking on a scale of 1 to 10. I then add a CSS class based on the rank.

So that’s about it to handle the change event of the select list. But we want the tag cloud to be loaded up as soon as the user hits the page.

Recall that we are setting all of this up in the $(document).ready event, and that jQuery supports chaining. So, all we need to do is ask jquery to fire the change event…

$(document).ready(function() {
    //stuff to do when the page is loaded
    $("select").change(function() {
        //Stuff to do when select list changes
        $("select option:selected").each(function() {
            var currentArea = $(this).text();
            $("#cloud").empty();
            $.getJSON(baseurl + "/GetTagsWithTweepsForArea?areaName=" + jQuery.trim(currentArea),
            function(data) {
                //stuff to do with the returned data
                $.each(data, function(i, item) {
                //stuff to do with each item
                    $('<li><a href="' + baseurl + '/item/' + encodeURIComponent(jQuery.trim(item.Name)) + '" class="tag' +
                        item.Rank + '" >' + item.Name + '</a></li>').appendTo("#cloud");
                });
            });
        });
    }).trigger('change');
});

So – there you have it. The code appears dense, but when you take it in pieces, it’s not too bad.

Talking Tech: Fonts

Posted by Dave Bouwman | Posted in Talking Tech | Posted on 10-02-2009

0

What? Really… Fonts?

If we are going to put effort into developing high-impact slides, and we need to have some text on our slides, it’s worth knowing a thing or two about fonts.

of Serifs…

Fonts basically come in two types – Serif and Sans-Serif. The “serifs” are the little flourishes that are added to various letters as shown in red on this graphic from the Wikipedia entry for Sans-Serif fonts.

wiki-pedia-serif

The back story is that these elements area added to help our eyes track along a line of text on a page packed with text. This is why most paper books and news papers are printed with serif fonts, and this is a good thing.

But your slide deck (hopefully) is nothing like a novel, so there is no need to help the audience read long lines of text, so we stick with non-Serif fonts, such as Arial, Helvetica Trebuchet Sans or any of the zillion or so other fonts that can be found.

 

Consistency

Ok, I’m about to drop some links to font archives, but I have to preface that with a request: be consistent. For 99% of the text in your slides, use a readily available  Sans-Serif font. Use these other font’s if you are adding “word-art” for some effect beyond what you can do with a more “standard font”

Find some Fonts

Fonts.com

Possibly the mother load, with 157,939 fonts available as of writing this article. This is a commercial site, and it really shows. Very clean design, with a smooth interactive “find a font” experience. They also have some useful links such as this article on Type 101.

fonts.com

 

DaFont.com

This site has a tremendous selection of fonts – 8675 currently listed – with various licensing requirements. I really like this site – it’s clean, and looks like it’s professionally run. Each font is shown at a pretty large size. They are also well tagged to it’s easy to find what you are looking for.

defont

AbstractFonts.com

This site specializes in… well… abstract fonts. These are good for adding particular artistic touches to images.

abstract.com

WebPagePublicity.com

This site has a list of 6500 fonts, but they simply organized by name – not very handy when you are looking for something specific.

fonts2

Under the Covers: Building a Simple Little Site…

Posted by Dave Bouwman | Posted in ASP.NET MVC, FindNFollow | Posted on 09-02-2009

0

Last Friday I launched a little site called FindNFollow.com – besides my blog, this is the first non-work related site I’ve conceived, designed and launched. It a very simple site (described below), and I threw it together in about 2 weeks of evenings. Here is the first post going behind the scenes…

Genesis of the Idea

While working with Al Laframboise on ideas for my talk at the ESRI Developer Summit, we decided to work social networking into the mix, and specifically we’d like to encourage the audience to get involved on Twitter.

In my opinion, Twitter has the quickest return on investment of all the Social Networking sites. Simply sign up for an account – lets say 2 minutes if you have dial up, and then follow people. Boom. Instant network. Of course the problem is finding the right people to follow.  After some googling, it became clear that most of the existing services use heuristics to determine who you may be interested in following, based on some sampling of tweets or use of the Twitter Search API. Since we all tweet some rather random stuff, I thought that was a pretty hit-or-miss proposition.

If we were going to make it easy for the Developer Summit attendees to find people to follow, we needed a way to get better information. It occurred to me that tagging your own twitter stream would be the best – who knows better than you, what you care about! Since the problem is not unique to GIS, I created the site to handle this broader set of interests. Interestingly, it’s all namespaced “DevConnectr”, reflecting the more limited initial vision…

Idea –> Paper

Having just read Back of the Napkin, of course I had to scribble some stuff down.

Version 1 was on a scrap of paper – literally. I had the idea while eating sushi, and I grabbed an un-used sushi ordering form, and scribbled the basic thing down on there. I was intending to scan and show it on this post, but like most scraps of paper, I lost it somewhere between here and Redlands.

Version 2 is shown below – 3 sheets of paper with notes and a few post-its. You can see where the “logo” came from – scribble and scan. ;-)

design-notes

Since this was going to be a “simple” site, I figured that this was ample in terms of design documentation, and work item tracking. Besides – I was doing this after hours, with the dual purpose of learning jQuery, and building a tool. I wanted to spend my evenings playing with code, not fiddling with TRAC or Rally

Reality Strikes

I *should* have seen this coming, but skipping a more detailed design resulted in a fair bit of wasted work. Keep in mind we are talking about maybe 40 hours total, so back-tracking or re-designing for even a few hours is a pretty big chunk on a project this small. Moral of the story – spend time on design, no matter how small or simple the project. Secondary Moral: Use a tool to manage work items. Assembla.com (no longer free, but really cheap!) comes with TRAC – I now have a backlog of items managed there.

Paper –> Code

Ah code, how I love thee. This was really the point of the exercise. For regular readers,  it should come as no surprise that I chose to use Microsoft ASP.NET MVC for this project. In my humble opinion, ASP.NET MVC has totally changed the nature of web development on the Microsoft platform. No more black-box server controls that emit vile smelling, non-standard HTML. But this also means that you have to take on more of the “work” – so it’s a double edged sword and not for everyone. But, if you want to play fast and loose like the PHP & Rails folks, it’s great. I tend to throw in a bit about how ASP.NET MVC is also highly testable, but since I mostly skipped unit testing on this project, I’ll give that a pass for now.

Since this needed to be very “Web 2.0″ if it was going to work, I needed a good Ajax toolkit. I have been a big Dojo fan, and it is great for building out sites that have complex client side interactions, but since this was a “simple” app, I did not want to tote Dojo into the mix. ThuxjQuery. Color me fan-boy, but it rocks. Sure it’s not a full-blown Rich-Internet-Application platform like Dojo, or EXTjs, but it rocks for simple ajaxy, DOM manipulation goodness. In future posts I’ll go into more about how various aspects of the site are coded, but suffice to say everything on there was easy to write, and for the most part “just worked”. And with all the hell that is cross browser CSS (IE6 I’m looking at you), it’s very nice when things “just work”.

Where next?

Last week’s launch was somewhat arbitrary – the core functionality is there, but it’s not “final final” by any means. But I’ve got a lot of other “side project” related things on my plate, so I figured I’d get it out there. Some items on the current backlog for the site are:

1) Ability to select who to follow, instead of just “Follow All”. This is thing 1 and I would have hit it this weekend had I not been sidelined by a touch of the flu.

2) Manage API hits better – since the site acts as “you”, and “you” get 100 Twitter API hits per hour, I need to see how many hits you have left before I try to send 100 “follow <username>” requests.

3) Add Spell Checker into the Add Tag dialog. I would not have guessed that this was important, but evidence show otherwise ;-)

4) Add more anti-spam capabilities. I’m all for anyone adding whatever tags they want, but please put them in a reasonably related category! Ideally this would be some sort of community policing sort of thing.

5) Document the API. Heck – it’s (almost) all ajax calls anyhow, may as well document it.

A number of the beta testers asked if there will be any map integration. I’m still thinking about this. It would be easy to do – assuming users have geocodable info in their Twitter location, but I’m not seeing the “value” of it. I guess it would be good to know if they were on the other side of the world, as they would not be as helpful for immediate “I need to know…” type questions, but… we’ll see.

As for now, go check out FindNFollow.com, if you find it useful, tell your friends.

Launching a Twitter Tool: FindNFollow.com

Posted by Dave Bouwman | Posted in ASP.NET MVC, FindNFollow, jQuery | Posted on 06-02-2009

0

Things have been slow on the blog for the last few weeks, mainly because I’ve been working on a little side project, which is now live – http://www.findnfollow.com

FindNFollow is a simple little site that lets Twitter users assign themselves tags, based on what they “tweet” about. There are some other sites that help you find people to follow, but they try to use search or heuristics to figure out compatible interests. However, I felt that we’d get much better results if we have people tag themselves. That way you are more likely find people who have an actual long-term interest in a subject. Anyone can tag their own twitter feed – you just need to enter your twitter username and password to validate that you control the account.

 

tag

 

The site holds an index of these tags and allows other Twitter users to find people to follow based on these tags. Of course it uses a tag cloud to show areas of interest.

 tagcloud

 

Click on a tag to see a list of people who have told us they are interested in that topic…

by-tg

Then enter your twitter credentials, and we’ll automatically follow all of them for you. In an upcoming release I will be adding the ability to subset the list of users to follow.

 

I’ve tied the site into GetSatisfaction.com, so if you have problems or have ideas, just use the Feedback badge on the left side of the site.

Moving Forward

I’ll be writing up a bunch of posts about how this was built, and how it works – particularly related to the repository, ASP.NET MVC and jQuery, as well as the inevitable re-factoring that comes with something like this. And yes, there will be a GIS angle… think Developer Summit timeframe.

Windows Vista Disk Clean Up…

Posted by Dave Bouwman | Posted in General, doh! | Posted on 06-02-2009

1

While installing Visual Studio 2008 Service Pack 1 on my notebook,I noticed I was running low on disk space. So I fired up the Disk Cleanup tool to see what that could do for me, and the results were interesting…

image

Check that out – 3.1GB of files discarded by Windows upgrade, 1.6Gb of user Queued Windows Error Reports, and yet another 1.34GB of System queued Windows Error Reports. Wow.

So – while it is nice that Vista has a tool that can locate these files and get rid of them, but, I just gotta think that *maybe* it should just get rid of these things? I mean – why is it keeping 3GB of files it discarded when I upgraded from Home Premium to Vista Ultimate? And what’s with the queued Error Reports? This thing is connected to a network 99.9% of the time it’s on, so it’s not like these things could not be sent.

Anyhow – it could be worth your time to run this tool, just to see what Vista has been stashing away for a rainy day.

"Notes from the Field" Talk at ESRI Developer Summit

Posted by Dave Bouwman | Posted in Dev Summit, Presentations | Posted on 05-02-2009

0

presentJust a quick note that the EDN team has invited me to give a talk at the ESRI Developer Summit, titled “Developing for the GeoWeb: Notes from the Field”.

I’m really excited to have this opportunity to add some “real-world” spice into the mix. Basically I’m going to discuss some of the recent projects that my team and I have been working on, and look at what has worked, and what as not.

On the “fluffy-zen” side of things I’ll discuss how to factor usability into your development process. On the technical side of things, I’m going to talk about why we use ASP.NET MVC, how and when we leverage unit testing, how we select a javascript map canvas and how we choose which javascript framework to use.

Finally, in conjunction with Al Laframboise (twitter: Al_Laframboise) on the EDN team, we are going to discuss how we as a community can better leverage our collective skills. Stay tuned for more pre-conference info on this… 

Although I’ve also submitted some topics for the Share Your Work talks, but the “Notes from the Field” talk is during a normal session, Thursday March 26th 8:30-9:45am in the Pasedena/Ventura/Sierra Room (apparently this is one the the 500 seat rooms, so I think there will be plenty of space!)

Full details can be found at the the ESRI Developer Summit online agenda (which is deep-linkable this year!)

Hope to see many of you there!