Richie's profileThe Sandpit - "We hack s...BlogLists Tools Help

Blog


    October 07

    The Sandpit has moved…

    Please continue to following The Sandpit from the new site.
    http://kiwigis.blogspot.com

    September 04

    Dynamic Charting for ArcGIS Desktop

    This post describes a new sample published by ESRI’s Application Prototype Lab and inspired by Jack Dangermond geodesign presentation at the 2009 ESRI International User Conference.

    Dynamic Charting for ArcGIS Desktop is a free sample (with source code) available from the ESRI code gallery.  The sample summarizes a layer’s features based on the length/area and grouped by unique values from the renderer.  The differentiating factor between this sample and the out-of-the-box charting tools is that this sample updates dynamically even in an edit session.

    Dynamic Charting for ArcGIS Desktop 

    Download

    August 26

    How to create a data report with ArcGIS Diagrammer

    ArcGIS Diagrammer is productivity tool for ESRI’s ArcGIS Desktop users.  Diagrammer is primarily used to create or view geodatabase designs in a graphical editor.  This post will examine data reporting, a feature of Diagrammer that might be useful to a wide variety of ArcGIS Desktop users.

    Let’s say that you or your organization has an existing geodatabase.  You may want to ask:

    1. Does my geodatabase have any empty feature datasets?
    2. Does my geodatabase have any feature classes with no data?
    3. Does my geodatabase have any subtypes with no data?
    4. Does my geodatabase have any feature classes with holes?

    Diagrammer’s data reporter will help answer these questions.  Below is a short walk-through of creating a data report for a sample geodatabase that is included with ESRI’s ArcTutor product.

    After starting ArcGIS Diagrammer, click Tools > Data Report.

    A new tab will be added to the application called Data Report.  If the Properties Window is not visible then click View > Properties.  In the Properties Window, click the ellipsis () button next to the Workspace property.

    The ellipsis button will launch a dialog for navigating to (and selecting) a geodatabase or SDE connection.  All geodatabase types are supported.

    The selected geodatabase will be scanned for feature datasets, feature classes and subtypes.  This may take a few seconds or minutes depending on the size and complexity of the geodatabase.  When the scanning is complete a HTML report will be presented in the Data Report tab.

    The report includes a header table and details about all feature classes, tables and subtypes.  Most useful is the number of features/rows per feature class, table and subtype.  Lastly, the report includes a small thumbnail representation of the data that, by default, hyperlinks to a larger image.

    Data reports can be sent to a local printer/plotter or virtual printer should you wish to create a PDF document.  Alternatively, reports can be exported to a new HTML document.  To do so, click File > Export.

    The save as dialog that appears will prompt you for the name and type of web document to create.  By default, “web archive” is selected.  We recommend you select “webpage, complete” as this will make a copy of the thumbnail images.  The only disadvantage of exporting a report to HTML is that the larger linked images will not included in the output location.  To prevent this, you can disable the creation of the larger images in the options dialog (see below).

    This is an example of an export data report displayed in Windows Explorer.

    When the exported HTML data report is viewed in a web browser, it looks identical to the report in ArcGIS Diagrammer.

    Some options are provided to change the look and feel of data reports.  To display the Diagrammer options dialog, click Tools > Options.

    The data report options are located under the Report > Data tab.  Most options are related to the font and the thumbnail images.

    In the screenshot below I modified the font to be “Segoe Print” and increased the size of the small image to 200x200 and removed the larger linked image (Show Large Image = False).

    That concludes this brief walkthrough of creating a data report with ArcGIS Diagrammer.  Diagrammer is a free download from ArcScripts and includes full source code.

    List of other online tutorials:

    August 25

    Site Suitability for Microsoft Surface

    During the 2009 ESRI International User Conference, Jack Dangermond introduced his vision of geographic design or “geodesign”.  Click here to view Jack’s presentation of his geodesign vision.  To assist Jack’s presentation a few demonstrations were created to help illustrate his points.  One such demonstration used a Microsoft Surface device to sketch planning areas on an interactive map.

     

    This application was developed using the ArcGIS API for WPF by the Applications Prototype Lab.  The base map is from ArcGIS Online and the overlaid suitability maps were sourced from a local ArcGIS Server.  A very basic application but it demonstrates the interactivity of a multi-user/multi-touch device for planning and communal design.

    July 17

    What would happen if the Earth stopped spinning?

    A new post by the Applications Prototype Lab examines what would happen to the world’s oceans if the Earth stopped spinning.

    What would happen if the Earth stopped spinning?

    Technorati Tags: ,,
    July 06

    New Blog from ESRI’s Applications Prototype Lab

    ESRI’s Applications Prototype Lab have just launched a blog which will showcase innovative proof-of-concepts and other interesting research.  Last month the lab released two videos of Surface applications, here and here.  Today the lab released eight new Surface videos here.

     

    For more videos please visit the post here.

    June 25

    How to Surface-enable the ArcGIS API for WPF

    The Applications Prototype Lab at ESRI have created a few proof-of-concepts for the Surface using the ArcGIS API for WPF.  Examples of which can be seen here and here.

    Surface applications are essentially WPF applications running in full screen mode.  When a user interacts with an application on a Surface device is not through the standard mouse API but through a specific Surface API.  The code below describes how to Surface-enable the map control that comes with the ArcGIS API for WPF.  This is just a basic implementation and should be treated as a developer sample rather than anything official.

    SurfaceMap.xaml

    After installing the Microsoft Surface SDK 1.0 (or 1.1) you will see a few extra items in the new projects dialog of Microsoft Visual Studio 2008.  Select Surface Application, add a reference to the ArcGIS API for WPF assemblies and then add a new resource file called SurfaceMap.xaml.  Ensure that the build action is set to page.

    <ResourceDictionary
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:local="clr-namespace:ESRI.PrototypeLab.Surface.Ccm"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:s="http://schemas.microsoft.com/surface/2008"
        xmlns:esri="clr-namespace:ESRI.ArcGIS.Client;assembly=ESRI.ArcGIS.Client"
        >
        <Style TargetType="{x:Type local:SurfaceMap}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type local:SurfaceMap}">
                        <Border Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                Width="{TemplateBinding Width}"
                                Height="{TemplateBinding Height}"
                                >
                            <esri:Map Name="PART_map" PanDuration="0" ZoomDuration="0" />
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ResourceDictionary>

    App.xaml

    Next you need to inform the compiler to load SurfaceMap.xaml as a resource.  To do so, add an entry to the App.xaml file as shown below.

    <Application x:Class="ESRI.PrototypeLab.Surface.Ccm.App"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        StartupUri="SurfaceWindow1.xaml"
        >
        <Application.Resources>
            <ResourceDictionary>
                <ResourceDictionary.MergedDictionaries>
                    <ResourceDictionary Source="...generic.xaml"/>
                    <ResourceDictionary Source="/ESRI.PrototypeLab.Surface.Ccm;component/SurfaceMap.xaml"/>
                </ResourceDictionary.MergedDictionaries>
            </ResourceDictionary>
        </Application.Resources>
    </Application>

    SurfaceMap.cs

    Add a new code file called SurfaceMap.cs and make sure that the build action is set to compile.  The code below contains the bulk of the logic to interpret contact (i.e. finger) manipulation.  The two key points to note is the manipulation processor and the inertia processor.  The manipulation processor will raise events when users manipulate performs a panning or zooming action.  The inertia processor is used to continue panning if the user flicks map.

    using System;
    using System.Windows;
    using System.Windows.Input;
    using ESRI.ArcGIS.Client;
    using ESRI.ArcGIS.Client.Geometry;
    using Microsoft.Surface.Presentation;
    using Microsoft.Surface.Presentation.Controls;
    using Microsoft.Surface.Presentation.Manipulations;
    using Microsoft.Surface;
    
    namespace ESRI.PrototypeLab.Surface.Ccm {
        public partial class SurfaceMap : SurfaceUserControl {
            private Map m_map = null;
            private Affine2DManipulationProcessor m_manipulationProcessor = null;
            private Affine2DInertiaProcessor m_inertiaProcessorMove = null;
            static SurfaceMap() {
                FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(typeof(SurfaceMap),
    new FrameworkPropertyMetadata(typeof(SurfaceMap))); } public SurfaceMap() : base() { // Define manipulation processor used or panning and Zooming this.m_manipulationProcessor = new Affine2DManipulationProcessor( Affine2DManipulations.TranslateX | Affine2DManipulations.TranslateY | Affine2DManipulations.Scale, this, true); this.m_manipulationProcessor.Affine2DManipulationStarted +=
    new EventHandler<Affine2DOperationStartedEventArgs>
    (this.ManipulationProcessor_Affine2DManipulationStarted); this.m_manipulationProcessor.Affine2DManipulationDelta +=
    new EventHandler<Affine2DOperationDeltaEventArgs>
    (this.ManipulationProcessor_Affine2DManipulationDelta); this.m_manipulationProcessor.Affine2DManipulationCompleted +=
    new EventHandler<Affine2DOperationCompletedEventArgs>
    (this.ManipulationProcessor_Affine2DManipulationCompleted); // Define inertia proceesor for panning this.m_inertiaProcessorMove = new Affine2DInertiaProcessor(); this.m_inertiaProcessorMove.Affine2DInertiaDelta +=
    new EventHandler<Affine2DOperationDeltaEventArgs>
    (this.InertiaProcessorMove_Affine2DInertiaDelta); this.m_inertiaProcessorMove.Affine2DInertiaCompleted +=
    new EventHandler<Affine2DOperationCompletedEventArgs>
    (this.InertiaProcessorMove_Affine2DInertiaCompleted); } public override void OnApplyTemplate() { base.OnApplyTemplate(); this.m_map = (Map)this.Template.FindName("PART_map", this); } public Map Map { get { return this.m_map; } set { this.m_map = value; } } protected override void OnContactDown(ContactEventArgs e) { base.OnContactDown(e); if (!e.Contact.IsFingerRecognized) { return; } e.Contact.Capture(this, CaptureMode.SubTree); this.m_manipulationProcessor.BeginTrack(e.Contact); e.Handled = true; } protected override void OnContactChanged(ContactEventArgs e) { base.OnContactChanged(e); if (!e.Contact.IsFingerRecognized) { return; } Point position = e.Contact.GetPosition(this); if (position.X < 0 || position.Y < 0 || position.X > this.ActualWidth || position.Y > this.ActualHeight) { e.Contact.Capture(this, CaptureMode.None); e.Handled = true; return; } e.Contact.Capture(this, CaptureMode.SubTree); this.m_manipulationProcessor.BeginTrack(e.Contact); e.Handled = true; } protected override void OnContactUp(ContactEventArgs e) { base.OnContactUp(e); if (!e.Contact.IsFingerRecognized) { return; } e.Contact.Capture(this, CaptureMode.None); } private void ManipulationProcessor_Affine2DManipulationStarted(
    object sender, Affine2DOperationStartedEventArgs e) { if (this.m_inertiaProcessorMove.IsRunning) { this.m_inertiaProcessorMove.End(); } } private void ManipulationProcessor_Affine2DManipulationDelta(
    object sender, Affine2DOperationDeltaEventArgs e) { if (this.m_map == null) { return; } if (this.m_map.Extent == null) { return; } if ((e.Delta.X == 0) && (e.Delta.Y == 0) && (e.ScaleDelta == 0)) { return; } if ((e.Delta.X != 0) || (e.Delta.Y != 0)) { MapPoint center = this.m_map.Extent.GetCenter(); Point screen = this.m_map.MapToScreen(center); Point newScreen; switch(ApplicationLauncher.Orientation){ case UserOrientation.Top: // Surface is upside-down newScreen = Point.Add(screen, e.Delta); break; case UserOrientation.Bottom: // Surface has normal orientation newScreen = Point.Subtract(screen, e.Delta); break; default: return; } MapPoint newCenter = this.m_map.ScreenToMap(newScreen); this.m_map.PanTo(newCenter); Window w = SurfaceWindow.GetWindow(this); } if (e.ScaleDelta != 0) { if (Contacts.GetContactsCaptured(this).Count > 1) { this.m_map.ZoomToResolution(this.m_map.Resolution / e.ScaleDelta); } } } private void ManipulationProcessor_Affine2DManipulationCompleted(
    object sender, Affine2DOperationCompletedEventArgs e) { this.m_inertiaProcessorMove.InitialOrigin = new Point(0, 0); this.m_inertiaProcessorMove.InitialVelocity = e.Velocity * 10; this.m_inertiaProcessorMove.DesiredDeceleration = 0.005; this.m_inertiaProcessorMove.Begin(); } private void InertiaProcessorMove_Affine2DInertiaDelta(
    object sender, Affine2DOperationDeltaEventArgs e) { if ((e.Velocity.X == 0) && (e.Velocity.Y == 0)) { return; } MapPoint center = this.m_map.Extent.GetCenter(); Point screen = this.m_map.MapToScreen(center); Point newScreen; switch (ApplicationLauncher.Orientation) { case UserOrientation.Top: // Surface is upside-down newScreen = Point.Add(screen, e.Velocity); break; case UserOrientation.Bottom: // Surface has normal orientation newScreen = Point.Subtract(screen, e.Velocity); break; default: return; } MapPoint newCenter = this.m_map.ScreenToMap(newScreen); this.m_map.PanTo(newCenter); } private void InertiaProcessorMove_Affine2DInertiaCompleted(
    object sender, Affine2DOperationCompletedEventArgs e) { } } }

    SurfaceWindow1.xaml

    The snippet below demonstrates how to add a reference to the SurfaceMap defined in the snippets above.

    <s:SurfaceWindow
        x:Class="ESRI.PrototypeLab.Surface.Ccm.SurfaceWindow1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:s="http://schemas.microsoft.com/surface/2008"
        xmlns:local="clr-namespace:ESRI.PrototypeLab.Surface.Ccm"
        Title="my Title"
        Height="768"
        Width="1024"
        Loaded="SurfaceWindow_Loaded"
        >
        <Grid>
            <local:SurfaceMap x:Name="surfaceMap" />
        </Grid>
    </s:SurfaceWindow>
    

     

    SurfaceWindow1.xaml.cs (code behind)

    In the code-behind of the Surface window, the map extent is defined and the ArcGIS Online world street map layer is added.

    private void SurfaceWindow_Loaded(object sender, RoutedEventArgs e) {
        // Add the ArcGIS Online street map
        ArcGISTiledMapServiceLayer streets = new ArcGISTiledMapServiceLayer() {
            ID = "street",
            Url = "http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_StreetMap_World_2D/MapServer",
            Opacity = 1
        };
        this.surfaceMap.Map.Extent = new Envelope(-116.992383, 33.126732, -116.560400, 32.797143);
        this.surfaceMap.Map.Layers.Add(streets);
    
        // Listen to map events
        this.surfaceMap.Map.ExtentChanging += new EventHandler<ExtentEventArgs>(this.Map_ExtentChanging);
        this.surfaceMap.Map.ExtentChanged += new EventHandler<ExtentEventArgs>(this.Map_ExtentChanged);
    }

    This concludes this tutorial on adding Surface support to the ArcGIS API for WPF map control. For more information, please visit the ArcGIS API for Silverlight/WPF home page, support forum and resource center.

    Cross Country Mobility for Microsoft Surface

    Yesterday the Applications Prototype Lab at ESRI released demonstration videos of two Microsoft Surface applications.  The first was a demonstration of a simulated police dispatcher (announced here) and the second is a cross country mobility application discussed in this post.

    This Surface application is built with ESRI’s ArcGIS API for WPF and references map and geoprocessing services from ArcGIS Server.  Cross country mobility is the name giving to an exercise of determining the most efficient path between two locations.  Depending on the data (and parameters) the end user can find a route that is the fastest, shortest, most fuel efficient, avoids urban areas, flattest or any other condition.

     

    The first step illustrated in the video is the rating of three geographic layers: slope, vegetation and transportation.  The user can assign a preference to weight one more than others.  For example, slope could be a larger consideration if moving heavy equipment than the vegetation type.  Secondly, items within each layer can also be rated.  For example, the user can indicate that low slope is preferable to steep slopes and that grades great than 40° are “no go” (or impossible to traverse).

    The next step is to indicate the intended target location for the three flagged vehicles/people/units.  In the demonstration video the target is represented by a bulls eye button than can dragged into position.

    After the three geographic layer have been rated and the target placed into position, a request is sent to ArcGIS Server to perform a weighted overlay using the user defined parameters.  The result is a new geographic layer called a cost surface.  A cost surface is like an image where each pixel contains a cost value, that is, the cost for an object to traverse it.

    The next step, uses the cost surface to find the least cost path from the three flagged objects to the target.

    The final step is the creation of a cost corridor.  A cost corridor is an area around the least cost path with a plus or minus one, two and three percent variation.  Basically, what alternative path could the three flagged objects take by sacrificing one to three percent cost (in time, money, fuel etc).

    This is a very brief discussion of one of many geoprocessing capabilities in the ArcGIS product suite.  I would encourage you to explore this exciting technology at the ESRI website.

    Police Dispatcher on Microsoft Surface

    In May 2009, the Applications Prototype Lab published a web application called “Police Dispatcher”.  The application simulated a police dispatch system with real time incidents and the tracking of police vehicles.  The application was built using Silverlight 2 and the ArcGIS API for Silverlight.

    Police Dispatcher for Microsoft Silverlight

    The police dispatcher demonstration was recently ported to the Microsoft Surface as a Surface application.  Surface applications are similar to standard WPF application except that they target the Surface hardware and include references to a few extra libraries.  The transition was relatively trivial, for example, the application references the ArcGIS API for WPF rather than the ArcGIS API for Silverlight.

    In the Surface application we took advantage of some the goodness of WPF such as drop shadows and glow bitmap effects.

     

    May 13

    Adding Tab-key Support to the PropertyGrid Control

    The PropertyGrid control is convenient for adding object editing support with minimal code.  However, for some reason it does not support property navigation with the tab key.  Instead, clicking tab key will progressively set focus to other controls in your form.  This post provides a simple subclassed PropertyGrid with support for tab-key navigation of properties.

    image

    Below is the source code to a subclassed PropertyGrid control called TabbedPropertyGrid.  There were two major issues that needed to be overcome.  The first is that the PropertyGrid does not raise any keyboard events, to workaround this the subclassed PropertyGrid must hijack keyboard events from the parent form.  The second issue is that there is no intuitive way to navigate GridItems.  However, this MSDN forum thread provided a few clues to solve this using the Griditems and Parent property.

    public class TabbedPropertyGrid : PropertyGrid {
        public TabbedPropertyGrid() : base() { }
        public void SetParent(Form form) {
            // Catch null arguments
            if (form == null) {
                throw new ArgumentNullException("form");
            }
    
            // Set this property to intercept all events
            form.KeyPreview = true;
    
            // Listen for keydown event
            form.KeyDown += new KeyEventHandler(this.Form_KeyDown);
        }
        private void Form_KeyDown(object sender, KeyEventArgs e) {
            // Exit if cursor not in control
            if (!this.RectangleToScreen(this.ClientRectangle).Contains(Cursor.Position)) {
                return;
            }
    
            // Handle tab key
            if (e.KeyCode != Keys.Tab) { return; }
            e.Handled = true;
            e.SuppressKeyPress = true;
    
            // Get selected griditem
            GridItem gridItem = this.SelectedGridItem;
            if (gridItem == null) { return; }
    
            // Create a collection all visible child griditems in propertygrid
            GridItem root = gridItem;
            while (root.GridItemType != GridItemType.Root) {
                root = root.Parent;
            }
            List<GridItem> gridItems = new List<GridItem>();
            this.FindItems(root, gridItems);
    
            // Get position of selected griditem in collection
            int index = gridItems.IndexOf(gridItem);
    
            // Select next griditem in collection
            this.SelectedGridItem = gridItems[++index];
        }
        private void FindItems(GridItem item, List<GridItem> gridItems) {
            switch (item.GridItemType) {
                case GridItemType.Root:
                case GridItemType.Category:
                    foreach (GridItem i in item.GridItems) {
                        this.FindItems(i, gridItems);
                    }
                    break;
                case GridItemType.Property:
                    gridItems.Add(item);
                    if (item.Expanded) {
                        foreach (GridItem i in item.GridItems) {
                            this.FindItems(i, gridItems);
                        }
                    }
                    break;
                case GridItemType.ArrayValue:
                    break;
            }
        }
    }

    After adding the TabbedPropertyGrid to your form, the form must be parsed into the control using the SetParent method.

    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
    
            // Assign the form to the propertygrid
            this.tabbedPropertyGrid1.SetParent(this);
            this.tabbedPropertyGrid1.SelectedObject = this;
        }
    }

    Known Issues:  Shift-Tab does not select properties in reverse order.

    Technorati Tags: ,,

    April 15

    ArcGIS Server Explorer for Silverlight

    The ArcGIS Server Explorer is a new contribution on the ESRI Code Gallery for dynamically generating a catalog of map services.  The Silverlight control supports multiple servers.  The contribution includes a sample web application that will preview map services selected in the treeviewClick here to view the live sample.  The explorer control is based on the TreeView from the Silverlight 2 Toolkit.

    ArcGIS Server Explorer for Silverlight

    The snippet below demonstrates how to add a reference the ArcGIS Server Explorer control into a web application.  The web application is listening to the page load event and the event that is raised when a user clicks on a map service.

    <UserControl
        x:Class="ServerExplorerSample.Page"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:esri="clr-namespace:ESRI.ArcGIS;assembly=ESRI.ArcGIS"
        xmlns:se="clr-namespace:ESRI.ArcGIS.ServerExplorer;assembly=ESRI.ArcGIS.ServerExplorer"
        Loaded="UserControl_Loaded"
        >
        <Grid>
            <se:ServerTreeView x:Name="serverTreeView" LayerClicked="ServerTreeView_LayerClicked" />
        </Grid>
    </UserControl>

    In this example, the page load event is used to add a reference to a public ArcGIS Server, namely, ArcGIS Online.  You can only add a reference to local server if you are hosting the web application on your intranet.  This is a Silverlight security limitation.

    private void UserControl_Loaded(object sender, RoutedEventArgs e) {
        Uri uri = new Uri("http://services.arcgisonline.com/arcgis/rest/services");
        Server server = new Server(uri, "ArcGIS Online");
        this.serverTreeView.Servers.Add(server);
    }

    And finally, the snippet below demonstrates how the web application is responding to the a clicked map service.  The event handler contains a reference to a layer object that can be added directly to an Silverlight map control.

    private void ServerTreeView_LayerClicked(object sender, LayerEventArgs e) {
        // Add Selected Layer
        this.map.Layers.Clear();
        e.Layer.Initialized += (evtsender, args) => {
            this.map.ZoomTo(e.Layer.InitialExtent);
        };
        this.map.Layers.Add(e.Layer);
    }

    April 09

    Geochat for Silverlight

    The Prototype Lab at ESRI has just launched a geo-collaboration control for Silverlight.  The control allows users to chat and exchange information such as graphics.  Here is a live sample.

    Geochat for Silverlight

    Geochat is implemented in two parts.

    1. Server-side
      A Windows Communication Foundation (WCF) duplex service is created to maintain a connection to Silverlight clients.  The geochat implementation acts as broker between Silverlight clients by forwarding messages to individual (or all) clients.
      http://msdn.microsoft.com/en-us/library/cc645027(VS.95).aspx
    2. Client-side
      The Silverlight client is using PollingDuplexHttpBinding to maintain a connection with the server using AJAX polling.  The polling occurs every ten seconds.
      http://msdn.microsoft.com/en-us/library/cc645028(VS.95).aspx

    The client/server implementation effectively allows the server to push information to the browser.  This is also referred to as Comet.  Source code to both of the client and server projects are provided in the download from the ESRI code gallery.

    Building the Server

    Compiling and publishing the WCF service is straight forward.  The only prerequisite is Microsoft .NET 3.5 SP1.  If you are publishing the service on Microsoft Windows Vista then you may need to register the “svc” MIME type using the following command.

    C:\Windows\Microsoft.NET\Framework\v3.0\Windows Communication Foundation>ServiceModelReg.exe –i

    Building the Client

    The code gallery only includes the source code to the geochat user control rather than the entire web application.  After adding a reference to the geochat assembly, the steps below will discuss how to configure it and send/receive messages.

    At the top of your code page add a using statement for the geochat namespace.

    using ESRI.ArcGIS.Chat;

    In the your page (or user control) constructor use the ChatEnvironment singleton to set the name of the geochat service and begin listening to incoming messages.

    public Page() {
        InitializeComponent();
    
        // Url to the geochat service ("service.svc" will be automatically appended)
        ChatEnvironment.Default.Server = "http://your.server.com/ChatService";
    
        // Add event handler to listen for incoming messages
        ChatEnvironment.Default.NoteReceived += new EventHandler<NoteEventArgs>(this.ChatEnvironment_NoteReceived);
    }

    In the snippet below, geometry is converted to JSON and sent to all other Silverlight clients.

    if (ChatEnvironment.Default.IsConnected) {
        // Create data message and assign geometry
        DataMessage dataMessage = new DataMessage() {
            Text = this.GeometryToJson(new MapPoint(10,10)),
            Type = typeof(MapPoint).ToString()
        };
    
        // Send data message to all silverlight clients
        ChatEnvironment.Default.SendMessage(dataMessage, null);
    }

    To send the message to an individual user rather than everyone, replace “null” with a user object.  Use the following property to return a collection of all available users.

    ChatEnvironment.Default.Users

    The snippet above referenced a method to convert ESRI’s Silverlight geometry into JSON.  Since the message will be ultimately sent down the wire as a SOAP 1.1 message, the JSON string must be enclosed in a CDATA block.

    private string GeometryToJson(Geometry geometry) {
        MemoryStream memoryStream = new MemoryStream();
        DataContractJsonSerializer serializer = new DataContractJsonSerializer(geometry.GetType());
        serializer.WriteObject(memoryStream, geometry);
        memoryStream.Position = 0;
        StreamReader streamReader = new StreamReader(memoryStream);
        string json = streamReader.ReadToEnd();
        memoryStream.Close();
        return string.Format("CDATA[{0}]", json);
    }

    Now, let’s add code to receive incoming geometry messages.  Firstly we only want to process DataMessages and ignore other messages types (eg connection, disconnection and text).  The snippet below extracts the geometry from the message as well as the name of the sender.

    private void ChatEnvironment_NoteReceived(object sender, NoteEventArgs e) {
        if (e.Note.Payload.GetType() == typeof(DataMessage)) {
            // Get DataMessage
            DataMessage dataMessage = (DataMessage)e.Note.Payload;
    
            // Get Geometry
            Geometry geometry = this.JsonToGeometry(dataMessage.Text, dataMessage.Type);
    
            // Who sent this message?
            string from = e.Note.From.FriendlyName;
        }
    }

    Here is the code for the JsonToGeometry method referenced above.  The code assumes that the incoming message is some sort of ESRI Silverlight geometry and that the JSON string is enclosed in a CDATA block.

    private Geometry JsonToGeometry(string json, string type) {
        Type t = null;
        if (type == typeof(MapPoint).ToString()) { t = typeof(MapPoint); }
        else if (type == typeof(Polyline).ToString()) { t = typeof(Polyline); }
        else if (type == typeof(Envelope).ToString()) { t = typeof(Envelope); }
        else if (type == typeof(Polygon).ToString()) { t = typeof(Polygon); }
        else { return null; }
    
        // JSON to Geometry
        string json2 = json.Substring(6, json.Length - 7);
        Byte[] bytes = Encoding.Unicode.GetBytes(json2);
        MemoryStream memoryStream = new MemoryStream(bytes);
        DataContractJsonSerializer dataContractJsonSerializer = new DataContractJsonSerializer(t);
        Geometry geometry = dataContractJsonSerializer.ReadObject(memoryStream) as Geometry;
        memoryStream.Close();
        return geometry;
    }

    That concludes the introduction to the geochat user control for Silverlight.  Enjoy!

    April 02

    Police Dispatcher Demo – ArcGIS API for Microsoft Silverlight

    This is a quick sample thrown together that demonstrates some of the capabilities of the ArcGIS API for Microsoft Silverlight.
    http://maps.esri.com/police/default.aspx

    Video in Map Tooltips

    One intriguing feature is the ability to extend ESRI’s map tips with multimedia content.  In the police sample, video hosted by the server is streamed into the map tip.

    Maptip with video

    The following XAML shows how this is achieved.

    <esri:Map>
        <esri:Map.Layers>
            <ve:TileLayer Opacity="0" LayerStyle="AerialWithLabels"/>
            <ve:TileLayer Opacity="1" LayerStyle="Road"/>
            <esri:GraphicsLayer>
                <esri:GraphicsLayer.MapTip>
                    <StackPanel Orientation="Vertical" Background="Transparent">
                        <TextBlock Text="..."/>
                        <Border
                            Width="200"
                            Height="150"
                            BorderBrush="SkyBlue"
                            BorderThickness="1"
                            HorizontalAlignment="Right"
                            VerticalAlignment="Top"
                            >
                            <Border.Background>
                                <VideoBrush
                                    SourceName="video"
                                    Stretch="UniformToFill"
                                    />
                            </Border.Background>
                        </Border>
                    </StackPanel>
                </esri:GraphicsLayer.MapTip>
            </esri:GraphicsLayer>
        </esri:Map.Layers>
    </esri:Map>

    Where “video” is defined as:

    <MediaElement 
        x:Name="video" 
        AutoPlay="True"
        Source="police-video-large.wmv"
        IsMuted="True"
        Opacity="0"
        IsHitTestVisible="False"
        MediaEnded="Video_MediaEnded"
        />

    Animated Graphics

    The Silverlight animation environment is very powerful.  This power can be leveraged by the ArcGIS API for Silverlight.  For example, the incident graphics are displayed as a red icons with a rotating swirl.  There are three animations in action here:

    1. The graphic enlarges when the user’s mouse hovers over it,
    2. The red swirl rotates clockwise, and
    3. The red swirl gradually fades out over ten seconds.

    Incident

    This behavior can be achieved by subclassing the ESRI MarkerSymbol and overloading the common and mouseover view states.

    IncidentSymbol.cs

    using System.IO;
    using System.Windows.Controls;
    using System.Windows.Markup;
    using ESRI.ArcGIS.Symbols;
    
    namespace ESRI.PrototypeLab.PoliceDispatcher {
        public class IncidentSymbol : MarkerSymbol {
            public IncidentSymbol() : base() {
                string key = "<your namespace>.IncidentSymbol.xaml";
                Stream stream = typeof(IncidentSymbol).Assembly.GetManifestResourceStream(key);
                string template = new StreamReader(stream).ReadToEnd();
                this.ControlTemplate = XamlReader.Load(template) as ControlTemplate;
            }
        }
    }

    IncidentSymbol.xaml

    <ControlTemplate
        xmlns="http://schemas.microsoft.com/client/2007"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
        >
        <Grid
            Cursor="Hand"
            RenderTransformOrigin="0.5,0.5"
            Height="50"
            Width="50"        
            >
            <Grid.RenderTransform>
                <TransformGroup>
                    <ScaleTransform x:Name="scale" ScaleX="1" ScaleY="1" />
                    <TranslateTransform X="-25" Y="-25"/>
                </TransformGroup>
            </Grid.RenderTransform>
            <vsm:VisualStateManager.VisualStateGroups>
                <vsm:VisualStateGroup x:Name="CommonStates">
                    <vsm:VisualState x:Name="Normal">
                        <Storyboard>
                            <DoubleAnimation 
                                Storyboard.TargetName="rotateTransform"
                                Storyboard.TargetProperty="Angle"
                                From="0" To="360"
                                Duration="0:0:3"
                                RepeatBehavior="Forever"
                                />
                        </Storyboard>
                    </vsm:VisualState>
                    <vsm:VisualState x:Name="MouseOver">
                        <Storyboard>
                            <ObjectAnimationUsingKeyFrames
                                Duration="0"
                                Storyboard.TargetName="path"
                                Storyboard.TargetProperty="Visibility"
                                >
                                <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                            </ObjectAnimationUsingKeyFrames>
                            <DoubleAnimation
                                BeginTime="0:0:0"
                                Storyboard.TargetName="scale"
                                Storyboard.TargetProperty="ScaleX"
                                To="2"
                                Duration="0:0:0.2"
                                />
                            <DoubleAnimation
                                BeginTime="0:0:0"
                                Storyboard.TargetName="scale"
                                Storyboard.TargetProperty="ScaleY"
                                To="2"
                                Duration="0:0:0.2"
                                />
                        </Storyboard>
                    </vsm:VisualState>
                </vsm:VisualStateGroup>
                <vsm:VisualStateGroup x:Name="SelectionStates">
                    <vsm:VisualState x:Name="Selected" />
                    <vsm:VisualState x:Name="Unselected" />
                </vsm:VisualStateGroup>
            </vsm:VisualStateManager.VisualStateGroups>
            <Image
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                Height="32"
                Width="32"
                Source="ESRI.PrototypeLab.PoliceDispatcher;component/Images/incident.png"
                />
            <Path x:Name="path" Opacity="1" >
                <Path.Data>
                    <PathGeometry>
                        <PathGeometry.Figures>
                            <PathFigureCollection>
                                <PathFigure StartPoint="25,0" IsClosed="True">
                                    <PathFigure.Segments>
                                        <PathSegmentCollection>
                                            <ArcSegment
                                                Point="50,25"
                                                Size="25,25"
                                                SweepDirection="Clockwise"
                                                IsLargeArc="False"
                                                RotationAngle="0"
                                                />
                                            <LineSegment Point="45,25"/>
                                            <ArcSegment
                                                Point="25,5"
                                                Size="20,20"
                                                SweepDirection="Counterclockwise"
                                                IsLargeArc="False"
                                                RotationAngle="0"
                                                />
                                        </PathSegmentCollection>
                                    </PathFigure.Segments>
                                </PathFigure>
                            </PathFigureCollection>
                        </PathGeometry.Figures>
                    </PathGeometry>
                </Path.Data>
                <Path.Fill>
                    <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
                        <GradientStop x:Name="color1" Color="#00FF0000" Offset="0"/>
                        <GradientStop x:Name="color2" Color="#FFFF0000" Offset="1"/>
                    </LinearGradientBrush>
                </Path.Fill>
                <Path.RenderTransform>
                    <TransformGroup>
                        <RotateTransform x:Name="rotateTransform" Angle="0" CenterX="25" CenterY="25" />
                    </TransformGroup>
                </Path.RenderTransform>
            </Path>
        </Grid>
    </ControlTemplate>

    April 01

    Silverlight Serialization to JSON

    This post will walkthrough the process of serializing and de-serializing an object to/from a JSON using Silverlight.

    Starting from a new Silverlight Application, add references to the System.Runtime.Serialization and System.ServiceModel.Web assemblies.

     image

    At the top of the Page.xaml.cs code page, add the highlighted using statements.

     image

    Still in the Page.xaml.cs code page, add a definition of a Person class that we will ultimately serialize to JSON.  The class must be attributed with DataContract, a flag that denotes that the class (and all public members) are serializable.  In the screenshot below, I have explicitly added DataMember attributes to shorten the property names in the JSON string.

    image

    Add a Button to the XAML and add an event handler for the click event, so that the code in Page.xaml.cs looks like the following.

    image 

    Within the Button_Click method, let’s start by creating a new instance of the Person class.

    image

    Using DataContractJsonSerializer the person object can be converted to a JSON string.

    image

    Let’s add a message box to display the JSON string.

    image

    Now let’s convert the JSON string (denoted by the “json” variable) back into a Person object (denoted by “person2”).

    image

    Lastly, just to confirm that de-serialized object is correct, display the property values of “person2”.

    image 

    The final step is to run this code and see if it works!  Run the application in debug and click on the button you defined above to execute the serialization code.  The first popup displays the serialized Person as a JSON string.  The JSON is using the shorter member names defined using the DataMember attribute.

    image

    The second popup displays properties from the de-serialized person.

    image 

    Presentation Extension for ArcGIS Desktop

    Today the ESRI Prototype Lab published a new sample entitled Presentation Extension for ArcGIS Desktop on the ArcGIS Desktop Code Gallery.  This sample allows ArcMap users to give presentations directly from ArcMap without Microsoft PowerPoint.

    Below is a screenshot of the presentation manager, a simple-to-use window that allows the ArcMap user to create and manipulate “slides”.  A slide can be consider similar to a traditional bookmark except that it also includes a title, notes, a thumbnail and most important, layer visibility.

    The Presentation Manager

    The second tab on the presentation manager defines the behavior of the presentation window.  By default, the presentation window will occupy the entire extent of the primary display.  When exiting a presentation you can optionally force ArcMap to display the last viewed slide and copy any ink added to the current map.

    Presentation Options

    To activate the presentation, click the green play button on the presentation toolbar or presentation manager window.  The blue resume button will activate the presentation starting from the currently selected slide.  The presentation window consists of a map, title, navigation controls and a small expander button in the lower right hand corner.

    The Presentation Window

    Clicking the expander will display a set of somewhat familiar mapping tools.  Use these buttons to interact with the map:

    • Zoom in,
    • Zoom out,
    • Rotate,
    • Green pen,
    • White pen,
    • Erasure,
    • Exit

    The Expander

    To exit the presentation click the “x” button or press the escape key.

    How was it implemented?

    Both the Presentation Manager and Presentation Window are built using the Windows Presentation Framework (or WPF).  This is very exciting technology with virtually unlimited flexibility.

    The Presentation Window however is actually two WPF windows.  The first is a simple window hosting the ESRI map control inside a WindowsFormsHost.  A second window containing the title and buttons is overlaid on top.  We used some messy Win32 calls to parent the map window to the overlay window.  The dual window implementation is due to a draw order issue associated with hybrid GDI/WPF windows.  Namely, it is not possible to overlay a WPF control on top of a GDI control (e.g. ESRI map control).

    Prerequisites

    Known Issues

    Microsoft Virtual Earth layers cause the presentation window to take a long time to start.

    October 16

    GeoTagger for ArcGIS Explorer

    In July 2008, the ESRI Prototype Lab published an extension to ArcMap that added support for MetaCarta's GeoTagger OnDemand service.  This extension is now available as a custom task for ArcGIS Explorer.  The task is available as a free download (including source code) from ArcScripts.

    GeoTagger for ArcGIS Explorer

    Before you can use GeoTagger for ArcGIS Explorer you must create a MetaCarta account and signup to use MetaCarta web services.

    1. Create a MetaCarta Account
      Visit http://accounts.metacarta.com,
      Enter your email address and pick a password,
      Enter your name, address and telephone number (optional),
      Click the Create Account button.
    2. Sign-up to use MetaCarta's Web Services
      Visit http://accounts.metacarta.com/account,
      Enter your username (email address) and password as entered above,
      Click the link “agree to the terms and services” link,
      Read the license agreement.
      If you agree check “I have read and agree the...” and click the Submit button.

    The source code is available post-install.  The default location of the source code is C:\Program Files\GeoTagger for ArcGIS Explorer\SOURCE.  The task will be automatically be install for the current user in C:\Documents and Settings\<profile name>\Application Data\ESRI\ArcGIS Explorer\Tasks (in XP) or C:\Users\<profile name>\AppData\Roaming\ESRI\ArcGIS Explorer\Tasks (in Vista).

    August 14

    Silverlight Map Viewer for ArcGIS Server

    During the plenary of the 2008 ESRI International Conference, Ismael Chivite demonstrated a Silverlight map viewer for ArcGIS Server. The source code to the Silverlight map viewer is available here on ESRI Resource Center's code gallery.

    The Silverlight map viewer is a proof of concept developed by the ESRI Prototype Lab.  The viewer is based on the MultiScaleImage control which is also known as DeepZoomDeepZoom is essentially a technology for viewing an image pyramids.  A popular implementation of the DeepZoom technology is the Hard Rock Cafe Memorabilia web page.

    The map viewer is based on Silverlight 2 beta 2 which is cross-browser on both Mac and Windows machines.

    The Silverlight map viewer can be used for any ArcGIS Server cached map service.  However there is one "gotcha".  Due to a limitation with the MultiScaleImage control, tile scales must double sequentially.  For example, if the first tile scale is 1:1000, then the next scale must be 1:2000 then 1:4000 and so on.  The viewer uses the ArcGIS Server REST API to query the map service tile structure.

    Two sample web applications are published on ESRI's website for you to try now.  The first application displays the World 2D Imagery layer from ArcGIS Online.  Click here or the image below to display the web application.

    Silverlight Map Viewer for ArcGIS Server

    The second sample web application demonstration the JavaScript API exposed through the Silverlight map viewer.  Currently there are only a few API's exposed.  View the source code of the following application to understand how to manipulate the Silverlight (e.g. zoom and pan) using JavaScript.

    Sample Silverlight Web Application with JavaScript

    Technorati Tags: ,,
    August 01

    How to develop backward compatible ArcGIS application?

    This post will describe a technique for developing ArcGIS Desktop (or Engine) customizations that are backwardly compatible.

    Let's say you develop a custom toolbar for ArcGIS Desktop on a computer that has ArcGIS Desktop 9.2 sp4 installed.  When that toolbar is deployed to other computers then you may get mixed results.  The toolbar will work on computers with 9.2 sp4, 9.2 sp5, 9.2 sp6 and 9.3 but will barf on computers with 9.0, 9.1, 9.2, 9.2 sp1, 9.2 sp2 and 9.2 sp3.

    Notice that by default all customizations are upwardly compatible.  This is because ESRI installs publisher policy files into the GAC so that assembly references are automatically redirected to the current version.

    The GAC

    But how can you make your toolbar (developed on 9.2 sp4) run on older versions of ArcGIS Desktop?  If you want your toolbar to run on 9.2, 9.2 sp1, 9.2 sp2, 9.2 sp3 then you can:

    1. Compile the toolbar on a computer with 9.2 (no service packs),
    2. Force your project to reference older 9.2 ESRI assemblies.

    Obviously the first option requires you to maintain a historic version of ArcGIS Desktop.  This may be inconvenient and possibly not the most efficient use of a computer.  This problem is magnified if you want to deploy a customization that is compatible with older versions like say 9.0 or 9.1.

    The second option is the best that I have discovered.  After the initial setup, it only requires minor changes to your .NET projects.  Below I will describe the setup needed to create a customization that supports ArcGIS Desktop 9.2, 9.2 sp1-6 and 9.3.

    On any computer:

    1. Install Microsoft .NET 2.0,
    2. Install ArcGIS Desktop 9.2 (do NOT install any service packs),
    3. Copy of the DotNet folder to a location that is accessible to your development computer. Rename the folder to "DotNet92".
      (e.g. copy C:\Program Files\ArcGIS\DotNet to \\ server\files\DotNet92)

    On your development computer:

    1. Copy the 9.2 dotnet folder to your development computer
      (e.g. \\ server\files\DotNet92 to C:\Program Files\ArcGIS\DotNet92)

      The ArcGIS Installation Folder

    In your Microsoft Visual Studio projects:

    1. Open the project that contains references to the ESRI assemblies.  The path name of the assembly reference should point to the DotNet folder in the ArcGIS Installation folder.

       
    2. In the project properties, click the Reference Paths tab.  Click the ellipse button to navigate to and select the new DotNet92 folder (ie C:\Program Files\ArcGIS\DotNet92).  Click the Add Folder button to add the path name to the list of reference paths.


    3. Save the project.  Restart Microsoft Visual Studio and re-open the project.  If you re-examine the reference paths they should now be directed to the new 9.2 (no sp) assemblies in the DotNet92 folder that you copied from another computer.

    The customizations that you developed on this computer are now backwardly compatible to ArcGIS Desktop 9.2 (no sp) and above.  This same technique can be used to create customizations that are backwardly compatible with older versions of ArcGIS Desktop (or Engine) such as 9.0 or 9.1.  Due to assembly changes at 9.0, this technique cannot be used to create customizations that are backwardly computer to pre-9.0 versions like 8.1, 8.2 or 8.3.

    One important considering when compiling against an older assembly is that you cannot take advantage of new features.  For example, if you compile a project against 9.1 assemblies then it can be safely run on computers with 9.2 and 9.3 but you will be unable to use new functionality available in those versions.

    Technorati Tags: ,,
    July 31

    Exploiting the ESRI Projection Engine (second edition)

    Late last year we created a .NET wrapper for the ESRI projection engine (PE).  The PE is a C DLL that is used by many ESRI products like ArcGIS Engine, ArcGIS Desktop, ArcGIS Explorer and also Adobe Acrobat Reader 9.  Click here to read the previous post.

    The wrapper has been extended to include two more functions and comes with a test application. Additionally, code was added to safely unload the PE DLL to reduce the risk of memory leaks.  The source code (include test application) can be downloaded from here on ArcScripts.

    Below is a screenshot demonstrating the PE function that computes the geodesic distance (and azimuth) between two geographic locations.

    The next screenshot demonstrates another PE function that computes a location on an ellipsoid based using a distance and azimuth from a known location.

    The last screenshot demonstrates a PE function that performs a geographic transformation.  For a list of preset geographic transformations please visit one of following links (#1, #2 or #3).  In the example below, a coordinate based on the Ain El Adb ellipsoid is transformed to the WGS84 ellipsoid.

    Convert Decimal Degrees to Degrees Minutes Seconds

    This post contains C# code to convert decimal degrees into degrees, minute and seconds.  The screenshot below demonstration the code being using to convert a latitude (in decimal degrees) to a correctly formatted DMS value.

    Sample application to convert DD to DMS

    Here is the code associated with the button that does the conversion.

    private void Button_Click(object sender, EventArgs e) {
        if (sender == this.buttonConvertToDms) {
            if (string.IsNullOrEmpty(this.textBoxDD.Text)) { return; }
            double d;
            if (!double.TryParse(this.textBoxDD.Text, out d)) { return; }
            CoordinateType type = this.radioButtonLat.Checked ? CoordinateType.latitude : CoordinateType.longitude;
            this.textBoxDMS.Text = Engine.DDtoDMS(d, type);
        }
        else if (sender == this.buttonClose) {
            this.Close();
        }
    }

    Lastly, here is the formatting code.  Unlike similar sample, this code will pad minutes and seconds with zeros and append a N, S, W or E compass heading.

    public enum CoordinateType { longitude, latitude };
    public static string DDtoDMS(double coordinate, CoordinateType type) {
        // Set flag if number is negative
        bool neg = coordinate < 0d;
    
        // Work with a positive number
        coordinate = Math.Abs(coordinate);
    
        // Get d/m/s components
        double d = Math.Floor(coordinate);
        coordinate -= d;
        coordinate *= 60;
        double m = Math.Floor(coordinate);
        coordinate -= m;
        coordinate *= 60;
        double s = Math.Round(coordinate);
    
        // Create padding character
        char pad;
        char.TryParse("0", out pad);
    
        // Create d/m/s strings
        string dd = d.ToString();
        string mm = m.ToString().PadLeft(2, pad);
        string ss = s.ToString().PadLeft(2, pad);
    
        // Append d/m/s
        string dms = string.Format("{0}°{1}'{2}\"", dd, mm, ss);
    
        // Append compass heading
        switch (type) {
            case CoordinateType.longitude:
                dms += neg ? "W" : "E";
                break;
            case CoordinateType.latitude:
                dms += neg ? "S" : "N";
                break;
        }
    
        // Return formated string
        return dms;
    }
    Technorati Tags: