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; }
}