User presentations at the ESRI Developer Summit 2010
ESRI has expanded the number of user presentations and the space available to these presentations at the ESRI Developer Summit 2010 compared to 2009. Given the large number of people who attended some of the user presentations last year and were left standing throughout the presentation, this will be a very well appreciated move on the part of the ESRI team running the developer summit this year. I had the privilege/pleasure of presenting at the devsummit last year at one of the user presentation sessions. My presentation last year was titled ‘Harnessing Server Object Extensions’ and I had a blast doing it. This year, people attending the event have been asked to vote on abstracts to select user presentations for the devsummit. I have submitted a couple of abstracts this year also and if you are interested in attending the presentation at the devsummit or in watching the recorded media after the devsummit, please vote for the abstracts below. You will have to create a new user account on the site before you can vote. Your ESRI global account id will not work there.
Building a Map Printing service for Web Clients – Recalling the journey
Continuous Integration 101: Streamline your software development process
Also, check out my colleagues presentation about another VERY exciting project we collaborated on.
Notes from Silverlight unit testing with the ESRI Silverlight API
I have been using the ‘Silverlight Unit Test Framework’ to do unit testing for our Silverlight applications. Unit testing applications using the ESRI Silverlight API throws a monkey wrench into the Silverlight unit testing process. The Silverlight unit testing libraries can normally be run with the automatically generate HTML page option in VisualStudio 2008. But unit tests with the ESRI Silverlight API map control will not run on the auto-generated HTML pages since the map control cannot load its layers when run from a web page on disk. So, to test the mapping applications, the unit tests should be run with a web application project which hosts the page over HTTP.
Well, that wasn’t a very painful solution was it? But the pain enters the picture when we have to run these Silverlight mapping unit tests on the build server. We don’t necessarily want to publish a web application just to run the Silverlight mapping unit tests. Ideally, we would just like to run the unit tests in the XAP file by themselves without publishing a web application with IIS on the build machine. In fact, our build machine does not run IIS. the ‘Silverlight Unit Test Framework’ doesn’t include any built-in support for such use cases.
But we can solve the problem above using the Statlight Silverlight testing automation tool. Statlight also provides support for integration with TeamCity, our build server of choice. The Statlight tool runs the test library XAP file over HTTP using a web server that it hosts when running. The tool is pretty simple to use and more details can be found at the ‘Getting Started’ page. I did run into some issues with running my tests over Statlight because of the fact that the Statlight tool actually creates it own XAP file when running the tests and copies over only the DLLs from the XAP file being tested. I had made use of some ‘Content’ files in my unit tests that didn’t get copied over and caused the issue. But I was able to solve the problem easily by switching the ‘Content’ files into embedded file in the DLL. Statlight’s creator was also kind enough to give me more information about the inner workings of the Statlight tool. Here it goes
- Creates a webserver in the background. (hosted in the console)
- Creates a browser in the background. (WinForm thread with a WebBrowser control)
- The browser loads up StatLight silverlight application. (http://localhost:<port discovered>/…)
- The silverlight application connects to the webserver and retrieves the xap to test via REST.
- The SL app loads and exectues the tests in the xap.
- The SL app reports results back to the webserver via WCF.
- The webserver reports results back to the Console / CI server.
So, with Statlight, we can avoid unnecessary complexities with running Silverlight unit tests on the build server and report Silverlight mapping unit test results with the rest of the test results. If you are looking for solutions to running your Silverlight unit tests on your build server, i recommend looking into Statlight.
Quick lesson learnt implementing continuous zoom operations for the ESRI Silverlight API
One of the nicer navigation features that enhance suser experience on mapping applications is the continuous zoom in & out and pan operations. Also, users that use Google and Bing maps frequently expect the continuous zoom and pan experience in all the mapping applications 🙂 The ESRI Silverlight API Toolkit does not include support for continuous zoom and pan operations. But implementing the continuous navigation operations for the ESRI Silverlight API is real easy with the DispatcherTimer class that was introduced with Silverlight version 2.0. The DispatcherTimer class calls the ‘Tick’ event listeners on the UI thread so developers don’t have to worry about accessing UI controls from across threads. Implementing it is as simple as setting the map’s extent very frequently with new extent calculated for zoom in/out/pan operations. To provide a good experience, I had to reset the map’s extent every 10 milliseconds.
I had initially overlooked the fact that every time I reset the map’s extent every 10 milliseconds a map image request was being made for every DynamicLayer that was loaded on the map. The problem doesn’t occur with the TiledMapServiceLayer because requests for tile images only occur after the zoom/pan operation has caused the map’s extent to exceed the extent of the tile map images already loaded on to the map and the browser also caches the tiles client-side. The excessive number of map image requests (1 request every 10 milliseconds) generated by the continuous zoom operation would bring any GIS server (and did bring our server) to its knees pretty soon. There is no perfect solution to the problem here in my humble opinion. But an acceptable solution to the problem is to hide (change visibility to false) all the DynamicLayer(s) on the map when the continuous navigation operation begins and to reset the layers back to their previous state when the continuous navigation operation ends. Since the base map layers are TiledMapServiceLayer(s), they don’t pose the problem with the excessive number of requests and we can leave them visible. This actually provides an acceptable user experience during the continuous navigation operations.
The code snippet below illustrates the implementation of a sample continuous zoom in & out operations.
Note – In Silverlight any control that inherits from ButtonBase like Button, CheckBox, RadioButton etc will not raise the ‘MouseLeftButtonDown’ and ‘MouseLeftButtonUp’ events even though the events are available on the classes.
public partial class MainPage : UserControl
{
private DispatcherTimer _timer;
Dictionary<Layer, bool> _mapLayersVisibilityState = null;
public MainPage()
{
InitializeComponent();
IDisposable subscription = null;
subscription = myMap.Layers.GetLayersLoadCompleted().Subscribe
(
( args ) =>
{
txtStatus.Text = "All layers have loaded.";
subscription.Dispose();
}
);
txtStatus.Text = "Loading Layers onto map …";
}
void zoomOut_MouseLeftButtonUp( object sender, MouseButtonEventArgs e )
{
if( _timer != null )
_timer.Stop();
EndContinuousOperation();
}
void zoomOut_MouseLeftButtonDown( object sender, MouseButtonEventArgs e )
{
BeginContinuousOperation();
_timer = new DispatcherTimer();
_timer.Interval = new TimeSpan( 0, 0, 0, 0, 10 );
_timer.Tick += _zoomOutTimer_Tick;
_timer.Start();
}
void zoomIn_MouseLeftButtonUp( object sender, MouseButtonEventArgs e )
{
if( _timer != null )
_timer.Stop();
EndContinuousOperation();
}
void zoomIn_MouseLeftButtonDown( object sender, MouseButtonEventArgs e )
{
BeginContinuousOperation();
_timer = new DispatcherTimer();
_timer.Interval = new TimeSpan( 0, 0, 0, 0, 10 );
_timer.Tick += _zoomInTimer_Tick;
_timer.Start();
}
void _zoomInTimer_Tick( object sender, EventArgs e )
{
ZoomMapByFactor( 0.99 );
}
void _zoomOutTimer_Tick( object sender, EventArgs e )
{
ZoomMapByFactor( 1.01 );
}
private void BeginContinuousOperation()
{
_mapLayersVisibilityState = GetMapLayersVisibilityState();
HideAllDynamicMapLayers();
}
private void EndContinuousOperation()
{
SetMapLayersVisibilityState( _mapLayersVisibilityState );
}
private Dictionary<Layer, bool> GetMapLayersVisibilityState()
{
Dictionary<Layer, bool> visibilityStates = new Dictionary<Layer, bool>();
myMap.Layers.ForEach<Layer>( lyr => visibilityStates.Add( lyr, lyr.Visible ) );
return visibilityStates;
}
private void SetMapLayersVisibilityState( Dictionary<Layer, bool> visibilityStates )
{
if( visibilityStates == null )
return;
foreach( var entry in visibilityStates )
{
entry.Key.Visible = entry.Value;
}
}
private void HideAllDynamicMapLayers()
{
myMap.Layers.ForEach<Layer>( lyr =>
{
if( lyr is DynamicLayer )
lyr.Visible = false;
} );
}
void ZoomMapByFactor( double factor )
{
if( myMap != null )
{
Envelope env = myMap.Extent;
env.Expand( factor, myMap.RenderSize );
myMap.Extent = env;
}
}
}
The code above makes use of the ‘Expand’ extension method for the ‘Envelope’ type provided below.
public static void Expand( this Envelope extent, double factor, Size mapSize )
{
if( extent == null )
throw new ArgumentNullException( "extent" );
if( mapSize.Width <= 0 )
throw new ArgumentOutOfRangeException( "mapSize.Width" );
if( mapSize.Height <= 0 )
throw new ArgumentOutOfRangeException( "mapSize.Height" );
if( extent.XMax == extent.XMin )
{
double resolution = extent.Width / mapSize.Width;
double xVal = extent.XMin;
//using a 10 pixel buffer
extent.XMin = xVal – 10 * resolution;
extent.XMax = xVal + 10 * resolution;
}
if( extent.YMax == extent.YMin )
{
double resolution = extent.Height / mapSize.Height;
double yVal = extent.YMin;
extent.YMin = yVal – 10 * resolution;
extent.YMax = yVal + 10 * resolution;
}
MapPoint center = extent.GetCenter();
double dX = ( extent.Width / 2 ) * factor;
double dY = ( extent.Height / 2 ) * factor;
extent.XMin = center.X – dX;
extent.XMax = center.X + dX;
extent.YMin = center.Y – dY;
extent.YMax = center.Y + dY;
}
3 comments