How to open shape file in ArcGIS Runtime WPF SDK as Graphics/Feature layer

9774
24
01-23-2013 06:23 AM
Labels (1)
KevinChan
New Contributor
I googled and followed this tutorial http://www.arcgis.com/home/item.html?id=953a530ada30477d95cc69e788268bc9 and figured out how to import Shape files and Rasters into Runtime.

However, it seems most of the functionality uses a FeatureLayer/GraphicLayer instead an ArcGISLocalDynamicMapServiceLayer that the sample uses.

My question is if there is a way I can import the shape file as a graphics layer or if i can somehow cast my ArcGISLocalDynamicMapServiceLayer as a Graphics layer because shape file is essentially the same as a shape file.

An example is i'm trying to use the FeatureDataGrid() to show the attribute table of my shapep file, but I can't since it requires a graphics layer, not an ArcGISLocalDynamicMapServiceLayer.

Thanks and Regards, Kev
0 Kudos
24 Replies
MichaelBranscomb
Esri Frequent Contributor
Hi,

You can create a FeatureLayer from a ".../MapServer/dynamicLayer..." endpoint, and FeatureLayer also has a Source property which takes a LayerSource object (LayerMapSource or LayerDataSource), therefore it's possible to populate a FeatureDataGrid with the contents of a Shapefile added via the DynamicLayer sample you've been using.

 FeatureLayer featureLayer = new FeatureLayer();
featureLayer.ID = "blah";
featureLayer.OutFields.Add("*");
                        
featureLayer.Url = arcGisLocalDynamicMapServiceLayer.Url + "/dynamicLayer";
featureLayer.Source = new LayerDataSource() 
{
    DataSource = dataSource
};

featureLayer.Initialized += (s1, e1) => 
{
    MyDataGrid.Map = MyMap;
    MyDataGrid.GraphicsLayer = featureLayer as GraphicsLayer;
};

// featureLayer.Initialize(); // You only need to call initialize if not adding layer to map.
MyMap.Layers.Add(featureLayer);


You don't necessarily need to add the FeatureLayer to the map, and if you don't then you'll need to call the Initialize() method explicitly which will trigger a query and populate the feature layer with features. The important pieces in the query are #1 the "dynamicLayer" endpoint and #2 the layer definition (persisted by the client API and submitted on a per-request basis:

...&layer={"id":0,"source":{"type":"dataLayer","dataSource":{"type":"table","workspaceId":"8b5642c7-9ffc-4ee0-bf63-bd7a94fab189","dataSourceName":"Points"}},"definitionExpression":"1=1"}...


In the example above "dataSource" is an instance of a TableDataSource:

dataSource = new TableDataSource
{
    // Match the DataSourceName to the physical filename on disk (excluding extension).
    DataSourceName = fileName,

    // Provide the WorkspaceID (the unique workspace identifier created earlier). A LocalMapService may have multiple dynamic workspaces.
    WorkspaceID = workspaceInfo.Id
};



Cheers

Mike
0 Kudos
MichaelBranscomb
Esri Frequent Contributor
...Actually just a little extra info on this one. If you don't add the FeatureLayer to the map and you call Initialize explicitly then the FeatureLayer is not initially populated with features because no Update call is fired. In this case you'll need to make that Update call yourself. You could, although it's probably not necessary, wait for the UpdateCompleted event before assigning the FeatureLayer to the GraphicsLayer property of the FeatureDataGrid:

e.g.

featureLayer.Initialized += (s1, e1) => 
{

    featureLayer.UpdateCompleted += (s2, e2) => 
    {
        // Assign Map and GraphicsLayer property in UpdateCompleted event (probbaly optional)
        MyDataGrid.Map = MyMap;
        MyDataGrid.GraphicsLayer = featureLayer as GraphicsLayer;
    };
    featureLayer.Update(); // Call Update explicitly, that will populate the layer wiht features (and therefore populate the grid)
};

featureLayer.Initialize();
//MyMap.Layers.Add(featureLayer);


Cheers

Mike
0 Kudos
GeorgeFaraj
Occasional Contributor III
I tried this but I'm not having any luck yet. Am I doing this right? I'm using the same sample to add the shapefile using a dynamic layer, and then I call this method:

The Initialized handler gets called, but I don't see the features on the map. Any ideas?

Thanks.


private void AddFeatureLayerFromShapeFile(ArcGISLocalDynamicMapServiceLayer dynamicLayer)
{
 var featureLayer = new FeatureLayer();
 featureLayer.ID = "blah";
 featureLayer.OutFields.Add("*");
 featureLayer.Mode = FeatureLayer.QueryMode.OnDemand;
 featureLayer.OnDemandCacheSize = 500;
 //featureLayer.MaximumResolution = 25;
 featureLayer.Url = dynamicLayer.Url + "/" + dynamicLayer.DynamicLayerInfos[0].ID;
 featureLayer.Source = dynamicLayer.DynamicLayerInfos[0].Source;

 featureLayer.Initialized += (s1, e1) =>
 {
 };
 MyMap.Layers.Add(featureLayer);
}
0 Kudos
MichaelBranscomb
Esri Frequent Contributor
Hi,

I had a revelation that actually I'd probably confused the issue by keeping the ArcGISDynamicMapServiceLayer in my example whilst introducing a FeatureLayer as well. So I've simplified it to just use a FeatureLayer (below). So it now assumes that you actually want to add the FeatureLayer to the map and display that instead of the ArcGISDynamicMapServiceLayer. I'd recommend you definitely use the accelerated display mode if the feature layer may contain many thousands of graphics, or graphics with complex polygons.

Code:

    /// <summary>
    /// Interaction logic for DynamicLayersFeatureDataGrid.xaml
    /// </summary>
    public partial class DynamicLayersFeatureDataGrid : UserControl
    {
        // Get the path of the "empty" MPK from the application folder
        string _emptyMpkPath = @"..\Data\DynamicLayers\EmptyMPK_WGS84.mpk";

        public DynamicLayersFeatureDataGrid()
        {
            InitializeComponent();

            MyDataGrid.SelectionChanged += (s3, e3) =>
            {
                foreach (Graphic graphic in e3.RemovedItems)
                {
                    graphic.SetZIndex(0);
                }
                foreach (Graphic graphic in e3.AddedItems)
                {
                    graphic.SetZIndex(1);
                }
            };
        }

        /// <summary>
        /// Handles the Click event of the AddShapefileButton control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void AddShapefileButton_Click(object sender, RoutedEventArgs e)
        {
            // Setup the OpenFiledialog.
            OpenFileDialog openFileDialog = new OpenFileDialog();
            openFileDialog.Filter = "Shapefiles (*.shp)|*.shp";
            openFileDialog.RestoreDirectory = true;
            openFileDialog.Multiselect = false; // This sample assumes a single file is selected

            if (openFileDialog.ShowDialog() == true)
            {
                try
                {
                    // Remove any existing FeatureLayers in the Map
                    List<FeatureLayer> featureLayers = MyMap.Layers.OfType<FeatureLayer>().ToList();
                    foreach (var fl in featureLayers)
                    { MyMap.Layers.Remove(fl); }

                    // Call the add dataset method with workspace type, parent directory path, file names (without extensions) and delegate.
                    AddFileDatasetToDynamicMapServiceLayer(WorkspaceFactoryType.Shapefile,
                        Path.GetDirectoryName(openFileDialog.FileName),
                        Path.GetFileNameWithoutExtension(openFileDialog.SafeFileName));
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Error: Could not read file from disk. Original error: " + ex.Message);
                }
            }
        }

        /// <summary>
        /// Adds a file dataset (Shapefile) to a new feature layer.
        /// </summary>
        /// <param name="workspaceType">The workspace type (FileGDB, Raster, SDE, Shapefile) <see cref="http://resources.arcgis.com/en/help/runtime-wpf/apiref/index.html?ESRI.ArcGIS.Client.Local~ESRI.ArcGIS.Client.Local.WorkspaceFactoryType.html"/>.</param>
        /// <param name="directoryPath">A <see cref="System.String"/> representing the directory path.</param>
        /// <param name="fileNames">A <see cref="System.Collections.Generic.List{System.String}"/> representing the name of the file.</param>
        public void AddFileDatasetToDynamicMapServiceLayer(WorkspaceFactoryType workspaceType, string directoryPath, string fileName) 
        {
            try
            {
                // Generate a unique workspace ID (any unique string).
                string uniqueId = Guid.NewGuid().ToString();

                // Create a new WorkspaceInfo object with a unique ID.
                WorkspaceInfo workspaceInfo = new WorkspaceInfo(uniqueId, workspaceType, "DATABASE=" + directoryPath);

                // Create a new LocalMapService instance.
                LocalMapService localMapService = new LocalMapService
                {
                    Path = _emptyMpkPath, // Set the path property. 
                    EnableDynamicLayers = true, // Enable the dynamic layers capability.
                    MaxRecords = 1000000, // Set the maximum number of records
                };

                // Register the workspace to be used with this service.
                localMapService.DynamicWorkspaces.Add(workspaceInfo);

                // Asynchronously start the local map service.
                localMapService.StartAsync(x =>
                {
                    // Create a new ArcGISLocalDynamicMapServiceLayer passing in the newly started local service.
                    FeatureLayer featureLayer = new FeatureLayer()
                    {
                        Url = localMapService.UrlMapService + "/dynamicLayer", // Construct the URL to include the /dynamicLayer resource.
                        ID = fileName, // Assign ID 
                        OutFields = new ESRI.ArcGIS.Client.Tasks.OutFields() { "*" }, // Display all fields
                        SelectionColor = new SolidColorBrush(Colors.Yellow), // Yellow is generally a nice selection color
                    };

                    // The workspace is a feature class so create a new TableDataSource
                    DataSource dataSource = new TableDataSource
                    {
                        DataSourceName = fileName, // Match the DataSourceName to the physical filename on disk (excluding extension).
                        WorkspaceID = workspaceInfo.Id // Provide the WorkspaceID (the unique workspace identifier created earlier).
                    };

                    // Set the Source property of the DynamicLayerInfo object.
                    LayerDataSource layerDataSource = new LayerDataSource { DataSource = dataSource };

                    // Assign the LayerDataSource
                    featureLayer.Source = layerDataSource;

                    featureLayer.Initialized += (s, e) =>
                    {
                        // Set the FeatureDataGrid's Map property
                        MyDataGrid.Map = MyMap;

                        // Set the new FeatureLayer as the FeatureDataGrid's GraphicsLayer property
                        MyDataGrid.GraphicsLayer = featureLayer as GraphicsLayer;

                        SimpleRenderer renderer = null;

                        switch (featureLayer.LayerInfo.GeometryType)
                        {
                            case ESRI.ArcGIS.Client.Tasks.GeometryType.MultiPoint:
                                renderer = new SimpleRenderer() { Symbol = new SimpleMarkerSymbol() { Color = new SolidColorBrush(GetRandomColor()), Size = 8 } };
                                break;
                            case ESRI.ArcGIS.Client.Tasks.GeometryType.Point:
                                renderer = new SimpleRenderer() { Symbol = new SimpleMarkerSymbol() { Color = new SolidColorBrush(GetRandomColor()), Size = 8 } };
                                break;
                            case ESRI.ArcGIS.Client.Tasks.GeometryType.Polygon:
                                renderer = new SimpleRenderer() { Symbol = new SimpleFillSymbol() { Fill = new SolidColorBrush(GetRandomColor()), BorderBrush = new SolidColorBrush(GetRandomColor()) } };
                                break;
                            case ESRI.ArcGIS.Client.Tasks.GeometryType.Polyline:
                                renderer = new SimpleRenderer() { Symbol = new SimpleLineSymbol() { Color = new SolidColorBrush(GetRandomColor()) } };
                                break;
                            default:
                                break;
                        }

                        featureLayer.Renderer = renderer;
                    };
                    MyMap.Layers.Add(featureLayer);
                });
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        // Utility function: Generate a random System.Windows.Media.Color
        Random _random = new Random();
        private Color GetRandomColor()
        {
            var colorBytes = new byte[3];
            _random.NextBytes(colorBytes);
            Color randomColor = Color.FromRgb(colorBytes[0], colorBytes[1], colorBytes[2]);
            return randomColor;
        }

        private void Legend_Refreshed(object sender, Legend.RefreshedEventArgs e)
        {
            // Clear the sub items from the basemap layer.
            if (e.LayerItem.Layer == _worldTopographicBasemap)
                e.LayerItem.LayerItems.Clear();
        }
    }



Sorry - I haven't had time to look at the code in your previous yet... it's the end of the week here...

Cheers

Mike
0 Kudos
GeorgeFaraj
Occasional Contributor III
Thanks Mike, that resolved the problem with the layer not being visible. I can label its features, but I can't select anything with the Editor toolkit control. Why could that be?

I should note that I also add the dynamic service layer onto the map in addition to the feature layer, because the layer has too many features, so I show both with the feature layer set to OnDemand mode.
0 Kudos
MichaelBranscomb
Esri Frequent Contributor
Hi,

In OnDemand mode the FeatureLayer should be retrieving features for the map extent, which means you will have some duplication of content in the map. It would probabaly be better to set the FeatureLayer to SelectionOnly mode if you're using the dynamic map service layer for display purposes. In that case, only the features you select on the map will be displayed in the FeatureDataGrid. Here's an example in SL: http://resources.arcgis.com/en/help/silverlight-api/samples/start.htm#AttributeOnlyEditing (sorry - we haven't had time to port to WPF yet).

Regarding the selection issue - are you setting the Map property of the Editor in XAML? You'll need to set the Editor.Map after InitializeComponent() because WPF element-binding does not resolve when part of resource.

Cheers

Mike
0 Kudos
GeorgeFaraj
Occasional Contributor III

In OnDemand mode the FeatureLayer should be retrieving features for the map extent, which means you will have some duplication of content in the map. It would probabaly be better to set the FeatureLayer to SelectionOnly mode if you're using the dynamic map service layer for display purposes. In that case, only the features you select on the map will be displayed in the FeatureDataGrid. Here's an example in SL: http://resources.arcgis.com/en/help/silverlight-api/samples/start.htm#AttributeOnlyEditing (sorry - we haven't had time to port to WPF yet).


I'm using OnDemand because the layer contains too many features and they are not all loaded if I don't set this mode. I am hiding the dynamic map layer when I zoom to a specific resolution, and show the feature layer only at this point.



Regarding the selection issue - are you setting the Map property of the Editor in XAML? You'll need to set the Editor.Map after InitializeComponent() because WPF element-binding does not resolve when part of resource.


Yup, I'm doing that. I am able to select graphics on Feature Layers loaded through a feature service.
0 Kudos
GeorgeFaraj
Occasional Contributor III
I'm still not able to select features on shape files. Any ideas?
0 Kudos
AnuragKulshrestha
New Contributor
Hi Mike,

I wanted this same kind of functionality - to access the attributes of a shapefile added on the fly. For which I contacted Esri Tech support and the GIS Analyst after research concluded that its not possible to access attributes for shapefile.

Thanks for the code.

Hi,

I had a revelation that actually I'd probably confused the issue by keeping the ArcGISDynamicMapServiceLayer in my example whilst introducing a FeatureLayer as well. So I've simplified it to just use a FeatureLayer (below). So it now assumes that you actually want to add the FeatureLayer to the map and display that instead of the ArcGISDynamicMapServiceLayer. I'd recommend you definitely use the accelerated display mode if the feature layer may contain many thousands of graphics, or graphics with complex polygons.

Code:

    /// <summary>
    /// Interaction logic for DynamicLayersFeatureDataGrid.xaml
    /// </summary>
    public partial class DynamicLayersFeatureDataGrid : UserControl
    {
        // Get the path of the "empty" MPK from the application folder
        string _emptyMpkPath = @"..\Data\DynamicLayers\EmptyMPK_WGS84.mpk";

        public DynamicLayersFeatureDataGrid()
        {
            InitializeComponent();

            MyDataGrid.SelectionChanged += (s3, e3) =>
            {
                foreach (Graphic graphic in e3.RemovedItems)
                {
                    graphic.SetZIndex(0);
                }
                foreach (Graphic graphic in e3.AddedItems)
                {
                    graphic.SetZIndex(1);
                }
            };
        }

        /// <summary>
        /// Handles the Click event of the AddShapefileButton control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void AddShapefileButton_Click(object sender, RoutedEventArgs e)
        {
            // Setup the OpenFiledialog.
            OpenFileDialog openFileDialog = new OpenFileDialog();
            openFileDialog.Filter = "Shapefiles (*.shp)|*.shp";
            openFileDialog.RestoreDirectory = true;
            openFileDialog.Multiselect = false; // This sample assumes a single file is selected

            if (openFileDialog.ShowDialog() == true)
            {
                try
                {
                    // Remove any existing FeatureLayers in the Map
                    List<FeatureLayer> featureLayers = MyMap.Layers.OfType<FeatureLayer>().ToList();
                    foreach (var fl in featureLayers)
                    { MyMap.Layers.Remove(fl); }

                    // Call the add dataset method with workspace type, parent directory path, file names (without extensions) and delegate.
                    AddFileDatasetToDynamicMapServiceLayer(WorkspaceFactoryType.Shapefile,
                        Path.GetDirectoryName(openFileDialog.FileName),
                        Path.GetFileNameWithoutExtension(openFileDialog.SafeFileName));
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Error: Could not read file from disk. Original error: " + ex.Message);
                }
            }
        }

        /// <summary>
        /// Adds a file dataset (Shapefile) to a new feature layer.
        /// </summary>
        /// <param name="workspaceType">The workspace type (FileGDB, Raster, SDE, Shapefile) <see cref="http://resources.arcgis.com/en/help/runtime-wpf/apiref/index.html?ESRI.ArcGIS.Client.Local~ESRI.ArcGIS.Client.Local.WorkspaceFactoryType.html"/>.</param>
        /// <param name="directoryPath">A <see cref="System.String"/> representing the directory path.</param>
        /// <param name="fileNames">A <see cref="System.Collections.Generic.List{System.String}"/> representing the name of the file.</param>
        public void AddFileDatasetToDynamicMapServiceLayer(WorkspaceFactoryType workspaceType, string directoryPath, string fileName) 
        {
            try
            {
                // Generate a unique workspace ID (any unique string).
                string uniqueId = Guid.NewGuid().ToString();

                // Create a new WorkspaceInfo object with a unique ID.
                WorkspaceInfo workspaceInfo = new WorkspaceInfo(uniqueId, workspaceType, "DATABASE=" + directoryPath);

                // Create a new LocalMapService instance.
                LocalMapService localMapService = new LocalMapService
                {
                    Path = _emptyMpkPath, // Set the path property. 
                    EnableDynamicLayers = true, // Enable the dynamic layers capability.
                    MaxRecords = 1000000, // Set the maximum number of records
                };

                // Register the workspace to be used with this service.
                localMapService.DynamicWorkspaces.Add(workspaceInfo);

                // Asynchronously start the local map service.
                localMapService.StartAsync(x =>
                {
                    // Create a new ArcGISLocalDynamicMapServiceLayer passing in the newly started local service.
                    FeatureLayer featureLayer = new FeatureLayer()
                    {
                        Url = localMapService.UrlMapService + "/dynamicLayer", // Construct the URL to include the /dynamicLayer resource.
                        ID = fileName, // Assign ID 
                        OutFields = new ESRI.ArcGIS.Client.Tasks.OutFields() { "*" }, // Display all fields
                        SelectionColor = new SolidColorBrush(Colors.Yellow), // Yellow is generally a nice selection color
                    };

                    // The workspace is a feature class so create a new TableDataSource
                    DataSource dataSource = new TableDataSource
                    {
                        DataSourceName = fileName, // Match the DataSourceName to the physical filename on disk (excluding extension).
                        WorkspaceID = workspaceInfo.Id // Provide the WorkspaceID (the unique workspace identifier created earlier).
                    };

                    // Set the Source property of the DynamicLayerInfo object.
                    LayerDataSource layerDataSource = new LayerDataSource { DataSource = dataSource };

                    // Assign the LayerDataSource
                    featureLayer.Source = layerDataSource;

                    featureLayer.Initialized += (s, e) =>
                    {
                        // Set the FeatureDataGrid's Map property
                        MyDataGrid.Map = MyMap;

                        // Set the new FeatureLayer as the FeatureDataGrid's GraphicsLayer property
                        MyDataGrid.GraphicsLayer = featureLayer as GraphicsLayer;

                        SimpleRenderer renderer = null;

                        switch (featureLayer.LayerInfo.GeometryType)
                        {
                            case ESRI.ArcGIS.Client.Tasks.GeometryType.MultiPoint:
                                renderer = new SimpleRenderer() { Symbol = new SimpleMarkerSymbol() { Color = new SolidColorBrush(GetRandomColor()), Size = 8 } };
                                break;
                            case ESRI.ArcGIS.Client.Tasks.GeometryType.Point:
                                renderer = new SimpleRenderer() { Symbol = new SimpleMarkerSymbol() { Color = new SolidColorBrush(GetRandomColor()), Size = 8 } };
                                break;
                            case ESRI.ArcGIS.Client.Tasks.GeometryType.Polygon:
                                renderer = new SimpleRenderer() { Symbol = new SimpleFillSymbol() { Fill = new SolidColorBrush(GetRandomColor()), BorderBrush = new SolidColorBrush(GetRandomColor()) } };
                                break;
                            case ESRI.ArcGIS.Client.Tasks.GeometryType.Polyline:
                                renderer = new SimpleRenderer() { Symbol = new SimpleLineSymbol() { Color = new SolidColorBrush(GetRandomColor()) } };
                                break;
                            default:
                                break;
                        }

                        featureLayer.Renderer = renderer;
                    };
                    MyMap.Layers.Add(featureLayer);
                });
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        // Utility function: Generate a random System.Windows.Media.Color
        Random _random = new Random();
        private Color GetRandomColor()
        {
            var colorBytes = new byte[3];
            _random.NextBytes(colorBytes);
            Color randomColor = Color.FromRgb(colorBytes[0], colorBytes[1], colorBytes[2]);
            return randomColor;
        }

        private void Legend_Refreshed(object sender, Legend.RefreshedEventArgs e)
        {
            // Clear the sub items from the basemap layer.
            if (e.LayerItem.Layer == _worldTopographicBasemap)
                e.LayerItem.LayerItems.Clear();
        }
    }



Sorry - I haven't had time to look at the code in your previous yet... it's the end of the week here...

Cheers

Mike
0 Kudos