Esri Mobile 10 SDK and WPF Beginner’s Tutorial

WPF stands for Windows Presentation Framework and we are going to see how quick it is to utilize Esri’s Mobile 10 SDK’s new ability to work in WPF. In this blog I am not going to go into depth about WPF except to say it is Microsoft’s (MS) equivalent to Adobe AIR. Say goodbye to WinForms and welcome to a whole range of slick animations, affects and all that other jazzy stuff you now expect from any Silverlight or Flex web applications. But with this blog we are just going to focus on the nuts and bolts of quickly standing up a mobile application designed for a tablet that runs a Windows operating system. Plus I will point out some gotchas I stumbled across along the way.

Prerequisites:

  • MS Visual Studio 2008 sp1 or higher (I personally prefer 2010- more built in features I make use of)
  • Esri Mobile 10 SDK (make sure and run the uninstaller application first to identify any conflicts with prior installed versions of ArcGIS products)
  • ArcGIS 10 (optional- but need if you want to use geoprocessing tools to create mobile caches)
  • Created mobile caches
  • Running mobile map service (unless you want to skip the sync part)

Map Tutorial Application

Once the prerequisites are installed, open Visual Studio and create a new project.  In the new project dialog, select the WPF Windows Application template. For this tutorial, I chose to go down the path of C# (sorry you VB.NET programmers- but just Google a translator. None of the code snippets will be too difficult to translate). Name your new project WpfMobileTutorial (or whatever you want) and click OK. When your new project solution opens you will see two files- App.xaml and MainWindow.xaml. Expanding them and you see their corresponding cs files. For this tutorial we will ignore the App.xaml file. The only thing I will point out is in App.xaml you can set what xaml file in your project is the start up window-  StartupUri=”MainWindow.xaml”

Now let’s pull in our Mobile SDK. Right click References folder and select to Add References. Add ESRI.ArcGIS.Mobile. If you had used Mobile 9.3.1 SDK or earlier you are probably used to going to the Toolbox window and seeing your Esri controls like map. There is a bug in the current release of this SDK and you will not see them in Toolbox but don’t despair- there are there. We just need to add them the old fashion way before we had fancy dandy IDEs that did all that work for us. First add the reference to the SDK to the Window header tag like so:

<Window x:Class="WpfMobileTuturial.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:esrimap="clr-namespace:ESRI.ArcGIS.Mobile.WPF;assembly=ESRI.ArcGIS.Mobile"  
        Title="MainWindow" Height="450" Width="650">

Add the following line inside your Grid container.

<Grid>
<esrimap:Map x:Name="map1"  />
</Grid>

You could compile and launch but nothing fun will happen- we need to add the cache to the map. Now, I have done Flex development and I love the config files they use so things can be changed without recompiling. So, I mimic that by adding an xml file to my project that I call Settings.xml. You can setup how you want but this is the format I typically use to store my cache information:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <cacheLocations>
    <operationalCacheLocation>C:\Caches\OperationalCache\MobileCache</operationalCacheLocation>
    <baseMapCacheLocation>C:\Caches\BaseMapCache\Layers</baseMapCacheLocation>
  </cacheLocations>
  <mobileServiceInfo>
    <mobileServiceURL>http://serverTutorial/ArcGIS/services/Panhandle/tutorial/MapServer/MobileServer</mobileServiceURL>    
  </mobileServiceInfo>
</configuration>

For now let’s focus on what is between the cacheLocations tags. Most mobile applications will have separate caches. One for the stuff they plan to edit and sync- operationCacheLocation tag. And the base stuff used for reference and not typically synced- baseMapCacheLocation tag.  OK- back in MainWindow.xaml add the following event to the Window header tag Loaded=”Window_Loaded”. Switch over to MainWindow.xaml.cs and add the following code:

MobileCache mobileCache;
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {            
                       string xmlPath = getAppPath() + "Settings.xml";
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(xmlPath);
            XmlNodeList elemList;
        }
        public static bool IsDesignMode()
        {
            return (System.Diagnostics.Debugger.IsAttached);
        }
        public static string getAppPath()//make sure return with trailing \
        {
            bool check = IsDesignMode();
            if (check)
            {
                return @"C:\Projects\WpfMobileTuturial\WpfMobileTuturial\";
            }
            else
            {
                return System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + @"\";
            }
        }
      private MobileCache getMobileCache(String cachePath)
        {
            MobileCache m_mobileCache = new MobileCache();
            m_mobileCache.StoragePath = cachePath;
            m_mobileCache.Open();
            return m_mobileCache;
        }

Let’s go over what is going on here. First, if you had done mobile development prior to 10 MobileServices was replaced with MobileCache. But be warned- it is not an exact switch out between them. So, I would try and forgot how MobileServices worked in 9.3.1 and treat MobileCache as something new you are learning. The helper methods getAppPath() and IsDesignMode() just do some of the leg work of finding where Settings.xml is- if debugging I did go ahead and hardcode the path but if it is installed I just pull out it of Assembly.  I will discuss getMobilCache later. Let’s now add the code to our Window_Loaded method for adding the caches after the line XmlNodeList elemList;

elemList = xmlDoc.GetElementsByTagName("baseMapCacheLocation");
            string streetCache = elemList[0].InnerXml;
            if (streetCache != null && streetCache.Length > 0)
            {
                //rasterdata- local Tile Map Cache
                TileCacheMapLayer mpLayer = new TileCacheMapLayer(streetCache);
                mpLayer.Name = "Base Tile Cache";
                mpLayer.Open();
                map1.MapLayers.Add(mpLayer);
            }
            elemList = xmlDoc.GetElementsByTagName("operationalCacheLocation");
            string localCache = elemList[0].InnerXml;
            if (localCache != null && localCache.Length > 0)
            {
                mobileCache = getMobileCache(localCache);
                //update the cache
                elemList = xmlDoc.GetElementsByTagName("mobileServiceURL");
                string mapurl = elemList[0].InnerXml;
                int index = 0;
                //map1.MapLayers.AddRange(mobileCache);
                //this method AddRange doesn't always behave as expected so here is my work around- make a renderable mobilcachemaplayer for each layer
                //and then add to WPF map
                ReadOnlyLayerCollection clc = mobileCache.Layers;
                int iCnt = mobileCache.Layers.Count;
                for (int i = 0; i < iCnt; i++)
                {
                    Layer layer = mobileCache.Layers[i];
                    MobileCacheMapLayer mobileCacheMapLayer = new MobileCacheMapLayer((MobileCacheLayer)layer);
                    map1.MapLayers.Insert(index, mobileCacheMapLayer);
                    index++;
                }               
            }

So, all this code snippet does is first get the value from our tag baseMapCacheLocation and uses one of the new features of 10;using Tile Caches created from a Tile Map Service. You just need to copy _alllayers folder, conf.cdi and conf.xml locally and just have your Settings.xml store where they  are saved locally.

The next part adds the operational layers to the MobileCache. And now you see us utilize the getMobileCache helper method. Just pass in the path location grabbed out of the Settings.xml and all this method does is initialize the mobile cache. Next we add these layers to the map. Here you will notice some comments in the code. I have had very inconsistent luck with using the map’s method AddRange. To be safe I always do the long way of casting the layers into MobileCacheMapLayer and add that to the map. Feel free to do whichever.

At this point you can compile and run and see a new window open showing your cache data..

Cool. Now let’s add some navigation. In MainWindow, drag three button controls onto your window and position where you would like them. Here you could get real fancy and add in some wicked style/animation affects. What I will do is just change text to Pan, Zoom In, and Zoom Out respectfully. I know, lame, but you can stop here and play with styling and if you have Expression Blend it is even easier. Here is a cool one to try- http://www.codeproject.com/KB/WPF/glassbuttons.aspx.

But back to the boring stuff let’s add the tiny bit of code snippets for adding in basic navigation:

private void btnPan_Click(object sender, RoutedEventArgs e)
        {
            map1.CurrentNavigationMode = ESRI.ArcGIS.Mobile.WPF.NavigationMode.Pan;
        }
        private void btnZoomIn_Click(object sender, RoutedEventArgs e)
        {
            map1.CurrentNavigationMode = ESRI.ArcGIS.Mobile.WPF.NavigationMode.ZoomIn;
        }
        private void btnZoomOut_Click(object sender, RoutedEventArgs e)
        {
            map1.CurrentNavigationMode = ESRI.ArcGIS.Mobile.WPF.NavigationMode.ZoomOut;
        }

And hooking our WPF button controls to our methods add Click events to each of your buttons and point to corresponding method (e.g. Click=”btnPan_Click”). Now compile and run and you can navigate around your map by panning and zooming in and out.

Since this is a mobile application the last thing I will cover here is synchronization- especially since the process changed in 10. Let’s add a fourth button to our application. Again, I am not going to utilize any wicked cool affects and just change the text to ‘Sync’.  And put the following method in MainWindow.xaml.cs:

private void btnSync_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                string xmlPath = getAppPath() + "Settings.xml";

                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.Load(xmlPath);

                XmlNodeList elemList;

                elemList = xmlDoc.GetElementsByTagName("mobileServiceURL");
                string url = elemList[0].InnerXml;

                MobileServiceConnection mobileServiceConnection1 = new MobileServiceConnection();
                //link the mobile classes to put the data into the display 

                mobileServiceConnection1.Url = url;

                ReadOnlyLayerCollection clc = mobileCache.Layers;
                int iCnt = mobileCache.Layers.Count;
                for (int i = 0; i < iCnt; i++)
                {
                    //synch just be feature test
                    FeatureLayer featureLayer = mobileCache.Layers[i] as FeatureLayer;
                    FeatureLayerSyncAgent featLayerSync = new FeatureLayerSyncAgent(featureLayer, mobileServiceConnection1);
                    FeatureLayerSyncResults featLayerResults = new FeatureLayerSyncResults();
                    featLayerResults = featLayerSync.Synchronize() as FeatureLayerSyncResults;
                    IDictionary<int, string> synErrors = featLayerResults.UploadedFeaturesErrors;
                    if (synErrors.Count > 0)
                    {
                        foreach (KeyValuePair<int, string> kvp in synErrors)
                        {
                            int v1 = kvp.Key;
                            string v2 = kvp.Value;
                            System.Windows.MessageBox.Show("Error uploading " + featureLayer.Name + ". Contact administrator. " + "Key: " + v1.ToString() + " Value: " + v2);
                        }
                    }
                    if (featLayerResults.Exception != null)
                    {
                        Exception e = featLayerResults.Exception;
                        if (e.Message != null && e.Message.Length > 0)
                        {
                            System.Windows.MessageBox.Show("Error uploading " + featureLayer.Name + ". Contact administrator. " + "Message: " + e.Message);
                        }
                    }
                }
                return;
            }
            catch (Exception e)
            {
                System.Windows.MessageBox.Show(e.Message, "Error in OpenCacheConnection", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
                return;
            }

Looking at the code you now see the mobileServiceURL tag come into play. The URL to the mobile map service I also like to put in the Settings.xml. This, makes moving things to another environment much easier. Once we pull out the URL we assign to MobileServiceConnection. Now there was something I noticed about the sync process that annoyed me- it will run and not return any errors automatically. You actually have to dive into either FeatureLayerSyncResults or MobileCacheResults. In the above code snippet I actually show how to loop through cache and sync each layer. Then pull out if there were any errors in UploadedFeaturesErrors or in Exception. Makes debugging sync issues so much easier. So, go ahead and compile again and if you have your mobile service set up go ahead and sync. If there are any errors you will get an alert stating the problem.

So, that ends the tutorial for getting up and running with Mobile 10 and WPF. I will be following this with a blog extending this tutorial to start incorporating aspects of the design framework MVVM and utilizing WPF binding capabilities. Also, if you would like to see how cool WPF can look check out our project showcase (http://showcase.gisinc.com/) and look for the Panhandle Energy Pilot Patrol Application or City of Appleton Sign Retro-reflectivity Solution projects.

Advertisements

Charming the Snake : Python Tips

While fearing a real python might be justified, you shouldn’t be intimidated by the Python language. Esri has embraced Python as the language that fulfills the needs of its user community. Programming with Python using the new ArcPy site-package in ArcGIS 10 makes GIS programming accessible for the average GIS professional. For longtime GIS users who yearn for the simpler days of Avenue (the scripting language used with ArcView 3.x), Python has some of the same attractive features: a manageable number of well documented classes and functions, scripts that can be easily loaded into a document and executed, code shared as simple text files, and (perhaps best of all) no need to know what QueryInterface means! Python is a widely used, nonproprietary language, so learning Python can be beneficial for non-GIS applications.

To help you get started with Python, this article will highlight a few basic techniques that are commonly required when writing

Python scripts for ArcGIS, such as how to:

  • Reference additional libraries (i.e., import functionality) for use in a script.
  • Accept user-provided arguments.
  • Get a reference to the current map document.
  • Create, open, and write to files.
  • Show messages that report the script’s progress.
  • Execute a geoprocessing tool.
  • Access objects in a map document (data frames, layers, tables).

Importing Additional Modules

Python modules are files used to organize sets of additional functionality so your script can access it. The import statement is one way to bring a module into a script and make the functionality it defines available. To import a module, simply type an import statement at the top of the script window, like those shown below:

import os

import sys

import arcpy

import arcpy.mapping as mapping

This example shows some modules you may commonly work with. The os and sys modules contain functions for working with files on disk. The ArcPy site-package allows access to geoprocessing tools. The ArcPy mapping module provides functionality for working with the map document and the objects it contains.

Reading User Inputs

Commonly, a script will need some input from the user in order to run, and when creating a script, parameters can be defined to specify what information will be entered by the user. The order of parameters defined for the script is important, because parameter values are read from an ordered list of inputs. The following code illustrates how to read user inputs. Note that the

first parameter value is referenced by index position 0:

outDir = arcpy.GetParameterAsText(0)

packageMap = arcpy.GetParameter(1)

checkBrokenLayers = arcpy.GetParameter(2)

GetParameterAsText reads user input as a text string. GetParameter reads user input and returns an object (e.g., Boolean).

Getting a Reference to the Current Map

When scripting in any environment, the current document is often the key object. Once a reference to the current document is obtained, the script can drill down into the additional objects it contains. When scripting for ArcMap, a reference to the map document will provide access to the data frames, layers, and tables the map contains. The example below uses the MapDocument function to get a reference to the current map document and store it in a variable called mxd.

mxd = mapping.MapDocument(‘Current’)

The required parameter for the MapDocument function is either a path to a map document on disk or the keyword Current to get the map in which the script is executing.

Working with a File on Disk

Use the open function (imported in the os package) to open a file on disk:

reportFile = open(reportPath, ‘w’)

The open function takes two arguments: the file name and the file mode. Valid file modes are w for write, r for read, and a for append. If the file exists, it will be overwritten. If it does not exist, it will be created.

Use the write function to write information to a file (if it was opened in either write or append mode).

reportFile.write(reportText)

reportFile.close()

Make sure to close a file when you are done working with it.

Showing Status Messages as a Script Runs

Use the AddMessage function (from the ArcPy package) to display a message as your script runs. The code below produces a message that will appear in the script’s output window as it executes.

arcpy.AddMessage(‘Writing report to ‘ + reportPath)

Executing a Geoprocessing Tool

Any geoprocessing tool that is available from ArcToolbox may be executed programmatically using ArcPy. In fact, each tool is available as a function in ArcPy. Consult the ArcGIS documentation for more information regarding available geoprocessing tools. The example below will execute the Package Map geoprocessing tool (in the Data Management toolbox).

arcpy.PackageMap_management(mxdFilePath, packagePath)

PackageMap_management requires the path to the map document and the path to the output package file.

Get Objects in the Map (Data Frames and Layers, e.g.)

Use mapping.ListDataFrames to get a list of data frames from the map.

dataFrames = mapping.ListDataFrames(mxd, ‘’)

The ListDataFrames function takes two arguments: a map document (or a path to one) and a wildcard string for filtering data frames based on name (e.g., La*). The function returns a Python list of data frames. Similar to the ListDataFrames function, ListLayers returns a Python list object containing layer objects.

layers = mapping.ListLayers(mxd, ‘’, frame)

Conclusion

With the ArcPy site-package, Esri has integrated Python into ArcGIS 10. Python’s relative simplicity and power make it the scripting language of the future for ArcGIS.

For more information, contact Thad Tilton at ttilton@gisinc.com.

Table queries and other features in ArcGIS Server 10

ArcGIS 10 has lots of new features that have gotten attention, such as editing via REST-based clients (Flex, Silverlight, etc.) and new online mapping features at arcgis.com. For developers who’ve been working with ArcGIS Server for a while, there are more items that will make our lives easier. Among those that have caught my eye are: Read more of this post

AGS 10 Flex Templates and Flash Builder 4, what does it mean for your existing Flex Application?

Now that ESRI has officially released ArcGIS 10 there is both the need and desire to download and use the latest and greatest Flex API and Template.  The ESRI Flex  2.0 API is based on Flex 4 which is most readily available in Adobe’s new Flash Builder 4 (formally Flex Builder 3).  It is possible, and very doable, to bring Adobe’s Flex 4 sdk in to Flex Builder 3 but it is extremely easy to import a Flash Builder 4 project into Flex Builder 3.

It has been decided that Flash Builder 4 will now be the IDE where all Flex/Air development will take place.  The latest ESRI Flex Template can be downloaded here.  Once downloaded, getting the latest template into Flash Builder 4 is exactly the same as it was in Flex Builder 3.  But the issue is not how to use the latest template, rather it is how are all the legacy projects (the ones created in Flex Builder 3) will be incorporated into the new IDE.  Don’t worry, Adobe has made importing legacy projects very easy in Flash Builder 4.  Flash Builder 4 is shipped (or downloaded) with both the latest Flex 4 and Flex 3 (3.5) sdk’s.

Importing Legacy Project

Find the legacy project going to be imported in Flash Builder 4 and go to File -> Import and navigate to the correct project (either as a project folder or .zip project archive file).

Choose the project going to be “upgraded”.  Once Flash Builder has had a chance to review the project it will display a form asking what sdk the project should be based on (Flash Builder recognized the legacy project was created in Flex Builder and was based on a sdk other than 4.0).

Choose the specific sdk the project was based on, for this example the Flex 3.5 sdk was chosen (remember, the 3.5 sdk is pre-installed with Flash Builder 4 but it is possible to add additional sdk’s to Flash Builder in the same manner as Flex Builder).

Once the specific sdk is chosen Flash Builder 4 will update the workspace and recompile the project.  There is one last step, after the project has been recompiled to the sdk Flash Builder 4 will show a screen displaying information that the project will be upgraded and will no longer be able to be used in previous versions.

After saying “OK” to the upgrade the Flex Builder 3 project is ready to be manipulated/debugged/whatever your fancy in Flash Builder 4.  Enjoy!

Questions? Concerns?

You can email me @ brayo@gisinc.com