Tuesday, January 17, 2012

SIMPLE EDIT COMMANDS AND TOOLS - PART VII

Introduction


With the release of ArcGis Server 10, ESRI Inc. introduced a new service that can be activated for a map service : the feature service. This new service makes it a lot easier to do web editing over the internet. Through the ESRI Silverlight API you can now perform easy editing, and together with the ‘Geometry service’ you can create some of the editing operations found in the ArcGis Desktop application. In the next sections I will illustrate how you can create your own editing widgets, replacing the fixed edit widgets delivered by the ArcGis Silverlight API. In the sample code you will see the use of resources to make the application Multilanguage. In a later blog I will explain how you can use the Multilanguage functionality of Silverlight in cooperation with the MVVM pattern to make XAML and your C# code Multilanguage ready.

If you want to see how the application looks, you can find an example at the URL :


Editor control


When creating your own editor commands and tools, you will need the editor control found in the ArcGis JavaScript API. When looking at the API of this control, you will see that it contains the major CRUD methods needed for doing web editing. In this blog I explain the edit tools available in the Editor class.

To use the editor class of the ArcGis Silverlight API I created a specific ‘GisEditing’ service that will encapsulate all the edit operations needed. Because the editor control is needed to perform feature editing, it will instantiated in this custom class. The ViewModel involved in editing will not need creating the editor control. As with the other GIS services, an interface will be used to access editor functionality. The interface is available in the ViewModel through dependency injection.

GisEditing class


In this library, all editing functionality of our application will be implemented. This class is a singleton and is created in the boots trapper.  In the constructor we will instantiated the editor class. I add also a link to the GisOperation class to be able to have access to the map and the geometry services.

/// <summary>
/// Use IGisOperation interface to access the GisOperation service.
/// </summary>
/// <param name="gisOperations"></param>
public GisEditing(IGisOperations gisOperations, IMessageBoxCustom messageBoxCustom)
{
         editorTool = new Editor();
         this.gisOperations = gisOperations;
         this.messageBoxCustom = messageBoxCustom;
}


Through this class, we don’t need to add an editor component to the XAML files, all will be directed from this GisEditor service.

The functions that I implemented can be found in the interface below.

namespace Silverlight.Helper.Interfaces
{
  public delegate void GeometryServiceCompleteHandler(object sender, GraphicsEventArgs args);
  public delegate void MultipleResultOperationComplete(IList<Graphic> results);
  public delegate void SingleResultOperationComplete(Geometry result);
  public delegate void EditOperationComplete(int status,string message);
  public interface IGisEditing
  {
  Editor GetEditorTool();
  ObservableCollection<SymbolMarkerInfo> GetMarkerInfo(string layerId);
  IList<EditLayerData> GetEditLayers();
  ObservableCollection<TemplateData> GetTemplates(string layerId);
  void Initialize();
  void StartEditOperation(string operation, EditOperationComplete editOperationComplete);
  void EndEditOperation(int status, string message);
  void Intersect(IList<Graphic> targetGraphics, Geometry intersectGeometry);
 
  void CutOperation(IList<Graphic> polygons, Polyline polyline, 
    MultipleResultOperationComplete cutOperationComplete);
  void CreateConvexHull(IList<Graphic> pointList, 
    SingleResultOperationComplete singleResultOperationComplete);
  bool SplitPolygon(string layerName, MultipleResultOperationComplete cutOperationComplete);
  void SnapPolygons(string layerName, MultipleResultOperationComplete multipleResultOperationComplete);
  void UnionGeometries(IList<Graphic> geometries);
  void SaveAll();
  }
}


Most of these methods are an encapsulation of geometry services, hiding the asynchronous operation needed to execute the tool

Simple CRUD operation


To create a simple create / update or delete operation with the MVVM model you can do the following in XAML and ViewModel :

XAML

<Button  Style="{StaticResource ActionButton}" Name="btnAddGeometry" Command="{Binding AddCommand}" 
         CommandParameter="{Binding AddGeometryParameter}" >
  <Image Source="/Silverlight.UI.Esri.JTToolbarEditGeneral;component/Images/EditingPolygonTool32.png">
    <ToolTipService.ToolTip>                                                            <TextBox Text="{Binding Source={StaticResource LocalizedStrings},Path=AddGeometryTip}" 
                  BorderThickness="0" />
    </ToolTipService.ToolTip>
  </Image>
</Button>

A command is used to start the adding of a feature.

ViewModel

private ICommand _addCommand;
private ICommand _addCommand;
private ICommand _clearSelectionCommand;
private ICommand _deleteSelectedCommand;
private ICommand _editVerticesCommand;
 
 
public ICommand AddCommand
{
         get
         {
                  return _addCommand;
         }
         set
         {
                 _addCommand = value;
                 this.RaisePropertyChanged(() => this.AddCommand);
         }
}



/// <summary>
/// Add a new feature to a feature layer
/// </summary>
/// <param name="arg"></param>
protected virtual void OnAddCommandClicked(object arg)
{
  try
  {
    if (gisEditing.GetEditorTool() != null)
    {
      if (currentEditLayer == null)
      {
        ShowMessagebox.Raise(new Notification
         {
            Content = Silverlight.Helper.Resources.Helper.NoEditLayerSelected,
            Title = Silverlight.Helper.Resources.Helper.Warning
         }, confirmation =>
            {
             // No action required
            });
         return;
       }
 
       IList<string> layerIDs = new List<string>();
       layerIDs.Add(currentEditLayer.LayerName);
       gisEditing.GetEditorTool().LayerIDs = layerIDs;
       gisEditing.GetEditorTool().Freehand = false;
       Silverlight.Helper.DataMapping.FeatureLayerInfo layerInfo =
         gisOperations.GetFeatureLayerInfo(currentEditLayer.LayerName);
                 
       if (layerInfo.FeatureTemplates != null && layerInfo.FeatureTemplates.Count > 0)
         gisEditing.GetEditorTool().Add.Execute(
            layerInfo.FeatureTemplates.First(l => l.Key.Length > 0).Value);
       else
       {
         if (layerInfo.FeatureTypes != null && layerInfo.FeatureTypes.Count > 0)
         {
            FeatureType featureType = 
              layerInfo.FeatureTypes.FirstOrDefault(l => l.Key != null).Value as FeatureType;                                        gisEditing.GetEditorTool().Add.Execute(featureType.Id);
         }
         else
           gisEditing.GetEditorTool().Add.Execute(null);
       }
       EditOperationStarted("Add");
     }
   }
   catch (Exception ex)
   {
     ShowErrorMessagebox.Raise(new Notification
       {
         Content = String.Format("OnAddCommandClicked-{0}[{1}]", ex.Message, ex.StackTrace),
         Title = "System error"
       });
   }
}
 
protected virtual bool CanAddCommandClicked(object arg)
{
         return IsMapLoaded && !editActionActive;
}
 
 

The add command is built around the add method of the editor class. If you want to create a ViewModel  that has a more clear separation from the editor class, you could move this functionality towards the GisEditing service with the necessary parameters.

I found the documentation of ArcGis Silverlight API not always complete when it comes to detail the use of parameters.

In the add command I support subtypes and feature templates. Experimenting on the parameters of  the execute method of the add method of the editor class gave me the above result.  I hope in the future that ESRI will document in more detail the parameters.

The other simple edit commands (delete, vertices’ update)  are derived from the editor class and has very simple implementations.

Field subtypes and feature templates


If you want to use field subtypes creating a list of symbols where a user can choose, you must retrieve these symbols from the ArcGis Server feature service. The best way for doing this is when a feature class is initialized during the built of the map. In our GisOperation class each feature class initialized event is handled and at that moment all necessary information for a feature is available and can be saved in the GisOperation object. So in this way subtypes and feature templates are retrieved for later use. But also at the same time the name of the object id field and geometry type of the feature class are retrieved.



void InitializedFtLayer(object sender, EventArgs e)
{
  try
  {
         FeatureLayer layer = (FeatureLayer)sender;
         featureLayerInfos.Add(new Helper.DataMapping.FeatureLayerInfo()
         {
                 FeatureTemplates = layer.LayerInfo.Templates,
                 Url = layer.Url,
                 Name = layer.LayerInfo.Name,
                 Id = layer.ID,
                 FeatureTypes = layer.LayerInfo.FeatureTypes,
                 LayerGeometryType = layer.LayerInfo.GeometryType,
                 ObjectId = layer.LayerInfo.ObjectIdField
         });
         layersData.Add(new LayerData()
         {
                 ID = layer.LayerInfo.Id,
                 LayerName = layer.LayerInfo.Name,
                 Selection = true
         });
         VerifyInitialisationMap();
  }                                              
  catch (Exception ex)
  {
  messageBoxCustom.Show(String.Format("InitializedFtLayer-{0}/{1}", sender, ex.Message), GisTexts.SevereError, MessageBoxCustomEnum.MessageBoxButtonCustom.Ok);
  }
}



The XAML code for creating an edit widget looks like this: 

<ListBox Name="ListPoints"
                     helper:Selected.Command="{Binding SymbolSelected}"
                     ItemsSource="{Binding SymbolMarkers}"
                     Visibility="{Binding SubTypeVisibility}">
   <ListBox.ItemsPanel>
      <ItemsPanelTemplate>
         <StackPanel Orientation="Horizontal"/>
      </ItemsPanelTemplate>
   </ListBox.ItemsPanel>
   <ListBox.ItemTemplate>
      <DataTemplate>
         <StackPanel Background="LightGray" Orientation="Horizontal">
            <esriToolkitPrimitives:SymbolDisplay Width="25"
                               Height="25"
                               VerticalAlignment="Center"
                               Symbol="{Binding SymbolMarker}">
                <ToolTipService.ToolTip>
                    <TextBox BorderThickness="0" Text="{Binding Name}"/>
                </ToolTipService.ToolTip>
            </esriToolkitPrimitives:SymbolDisplay>
         </StackPanel>
      </DataTemplate>
   </ListBox.ItemTemplate>
</ListBox>

The item source used in the ViewModel for displaying the symbols is :

// Feature Types
private ObservableCollection<SymbolMarkerInfo> _symbolMarkers;
public ObservableCollection<SymbolMarkerInfo> SymbolMarkers
{
  get
  {
         return _symbolMarkers;
  }
  set
  {
         _symbolMarkers = value;
         this.RaisePropertyChanged(() => this.SymbolMarkers);
  }
}



Where the SymbolMarkerInfo has the following structure:

/// <summary>
/// Symbol information class
/// </summary>
public class SymbolMarkerInfo
{
         public Symbol SymbolMarker { getset; }
         public string Name { getset; }
         public object ObjectFeatureType { getset; }
         public string LayerId { getset; }
}

Because feature  templates have no symbol defined, in the edit toolbar I implemented templates using a combo box.

<ComboBox Name="templates"
          Width="250"
          Height="25"
          Margin="10,0,0,0"
          DisplayMemberPath="Description"
          IsEnabled="True"
          ItemsSource="{Binding EditTemplates}"
          Visibility="{Binding TemplateVisibility}">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectionChanged">
           <i:InvokeCommandAction Command="{Binding TemplateSelectCommand}" CommandParameter="{Binding SelectedItem, ElementName=templates, Mode=OneWay}" />
        </i:EventTrigger>
     </i:Interaction.Triggers>
</ComboBox>

See the use of the invoke command to trigger the selection. This is the standard way of MVVM for link combo box events to the ViewModel.

private ObservableCollection<TemplateData> _editTemplates = new ObservableCollection<TemplateData>();
public ObservableCollection<TemplateData> EditTemplates
{
  get
  {
         return _editTemplates;
  }
  set
  {
         _editTemplates = value;
  }
}

With TemplateData defined as

public class TemplateData
{
         public string Description { getset; }
         public FeatureTemplate EditTemplate { getset; }
         public string LayerId { getset; }
}




Sunday, December 4, 2011

STUDY : USING the ArcGis API for Microsoft Silverlight/WPF to create an ArcGis Database edit solution.

This is my test for how you can use the ArcGis Silverlight API to create powerful  web editing applications. In the past blogs I already outlined how I started this project. The most important before starting of a Silverlight project of this size is to make clear how the architecture of the project should look.

To have an architecture that is modular to keeps track of the rapid changes in the ArcGis Silverlight API’s, you must use a framework and / or pattern that can help you in this challenge.

To be able to write good Silverlight application, Microsoft developed the pattern MVVM (Model View ViewModel). Without this pattern you will soon create complex software modules that will be difficult to maintain. The software development cost is only a fraction of the TCO (Total Cost of Ownership). To reduce the TCO it is important that the software product is highly maintainable.

To help the developer using MVVM, different frameworks has been publically available. One important available framework is PRISM 4.0 developed  by Microsoft.  The framework simplify the use of the pattern MVVM in Silverlight applications. In my Silverlight application I used PRISM 4.0 with MEF as the container library.

PRISM 4.0 has support for different container libraries like MEF and UNITY. Other container libraries can also be used with PRISM 4.0. I decided to use MEF because I like the implementation based on the use of class and method attributes through the use of reflection.

The use of container libraries serves for the implementation of IOC (inversion of control) and dependency injection. The latter is used to expose different GIS functions in the ViewModel classes.

 The architecture of the application has the following layout :

 Application architecture

 Module Architecture



To maintain a flexible application, the configuration is maintained in XML files as done by the Silverlight ArcGis viewer. To create a general approach of the map editor, all configurations are contained in a folder. The folder serves as parameter for starting the map editor. The URL for starting the application is:

 http://<<website>>/<virtual map>>/WebmapTestPage.aspx?Application=Demo

 or



In the current version I only support the languages English and Dutch.

Some limited testing can be done with the Url below:


You can download the whole demo application through the link below. To be able to run the application you need to deploy the contents towards a virtual map of your Internet Information Server. The application can run on a local PC with the Microsoft Internet Information Server.
Deploy alse the crossdomain and clientaccesspolicy files on the Internet Information Server.
 

For the user manual I restrict myself to a quick guide covering most of the exposed tools and menus. Below is the link to the pdf file.





Saturday, September 3, 2011

MAKE ARCGIS SILVERLIGHT CONTROLS MVVM ENABLED - PART VI

Introduction


Before tackling the more advanced ArcGis Silverlight controls as the Editor, I will first look how we can enhance the ArcGis Silverlight controls so that they fit into the MVVM pattern.

Sometimes You will encounter ArcGis controls or classes with properties that cannot be set from within your ViewModel. When you try it, you will probably end up with a null pointer exception. The reason of this is that in order to have properties bound to you ViewModel, these properties must be inherit from the dependency property object. This is not always the case for the properties of an ArcGis Silverlight class, probably due technical reasons. The next article explains how we can enhance the class so that we can set non dependency properties from within the ViewModel pattern.

The dependency issue


If you have a property to be set, but you cannot do it from the ViewModel, you can add a new property to the class that inherit from the dependency object. You can use this property to set the value for a property that is not inherit from the dependency object. Let’s take an example of how we can do this by using the MeasureAction class from the ArcGis Sivlerlight API.

When you look at the examples from ESRI, you can use the measure action as

 <Button Content="Measure"  >
     <i:Interaction.Triggers>
       <i:EventTrigger EventName="Click">
         <esri:MeasureAction                                 
           AreaUnit="SquareMiles"
           DisplayTotals="{Binding Totals}"
           DistanceUnit="{Binding Distance}"
           MapUnits="Meters"
           MeasureMode="Polygon"                                  
           FillSymbol="{StaticResource DefaultFillSymbol}"
           TargetName="MyMap"/>
        </i:EventTrigger>
     </i:Interaction.Triggers>
 </Button>

 When we try to replace the property TargetName by the following expression :

             TargetName = “{Binding MyMap}”

You will get a null pointer exception, and that is because the property ‘TargetName’ is different implemented as the other properties. You can see this in the ESRI documentation shown next.




































You can see that properties as AreaUnit, MapUnits inherit from the dependency objet. When you look at the TargetName, this is inherited from an other object, in the case of the MeasureAction it is a Microsoft defined property. ESRI could solved it by implementing the map control by an own property inherit from the dependency object as this is the case for the Legend control. But for some technical reason this was not done.

To solve the problem,  simply add a new property ‘MapMeasure’ to the MeasureAction  that will contain the map control.

The map dependency property


Create a new class in the project with the MeasureAction that implement the dependency project. The code below show how this can  be done.

using System.Windows;
using ESRI.ArcGIS.Client;
using ESRI.ArcGIS.Client.Actions;

namespace MyToolbar.Helper
{
  public class MapContent
  {
    public static readonly DependencyProperty MapMeasureProperty =
  DependencyProperty.RegisterAttached("MapMeasure"typeof(Map),
  typeof(MapContent), new PropertyMetadata(OnMapMeasureChanged));

    public static Map GetMapMeasure(DependencyObject depObject)
    {
      return (Map)depObject.GetValue(MapMeasureProperty);
    }

    public static void SetMapMeasure(DependencyObject depObject, Map value)
    {
      depObject.SetValue(MapMeasureProperty, value);
    }

    private static void OnMapMeasureChanged(DependencyObject depObject,
      DependencyPropertyChangedEventArgs e)
    {
      MeasureAction measureAction = depObject as MeasureAction;
      measureAction.TargetObject = GetMapMeasure(measureAction);
    }
  }
}

This is a typical implementation of a dependency property. The only code that you has to add is the code that is in the method On<property>Changed. In the case of the MeasureAction, the TargetObject is initialized with the map control. You can now in all modules activate the measure tool.

In the real world the XAML code will now look like  

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Click"
            <esri:MeasureAction                                 
                                    AreaUnit="SquareMeters"
                                    DisplayTotals="True"
                                    DistanceUnit="Meters"
                                    MapUnits="Meters"
                                    MeasureMode="Polygon"                                  
                                    FillSymbol="{StaticResource DefaultFillSymbol}"
                                    mapMeasure:MapContent.MapMeasure="{Binding MapControl}"
                        />
            </i:EventTrigger>
</i:Interaction.Triggers>

As you can see, now we have made the MeasureAction ViewModel enabled.

When at a given time the ArcGis Silverlight API add a new property ‘Map’ to the MeasureAction, you only has to change the property in the XAML, no code change is required. This is another example of the power of using the MVVM pattern.