Hi, Does arcgis runtime for wpf support to export layer to ESRI shape file? If so, could anyone provide some code snippets to help me out? thanks in adavance.
The short answer is no. The Runtime for WPF products doesn't support the creation of local data. This functionality may be coming in a future release of the Runtime for .NET API. If you want to do this currently in the Runtime for WPF API you'd need to leverage a GP Service (whether local or online) to convert the resulting GraphicsLayers to Shapefiles that can be downloaded or copied to a specific location on the client machine.
Thanks Freddie very much for your reply.
What's a pity to know that the Runtime for WPF products doesn't support the creation of local data. This functionality is what i indeed need at this moment.
The way to use GP Service to convert GraphicsLayer to shape file as you mentioned is a bit difficult for me a beginner, could you please give me some clue to make it? any code snippet would be much helpful for me.
I found a similar way to do this using runtime for java here http://blogs.esri.com/esri/arcgis/2012/08/24/arcgis-runtime-sdk-for-java-writing-graphics-to-shapefiles/, but didn't find a good example for wpf...
Hi Peter,
I looked at the link you posted above and it appears to be doing what I was suggest. I currently have a .NET sample that does the exact same thing, but I feel that this workflow currently is a bit of a hack. I have a second way that I want to create this sample that may be closer to a best practice (i.e. zipping up the shapefile and providing a url that I can use to download it instead of hacking out the path to the local servers output directory). I will try to write it up by this weekend and upload it so that you can see both options.
Thanks Freddie very much for your help. I am looking forward to seeing your good samples...
By the way, do you mind sending your handy .net sample to my email(it's a bit difficult for me to access the GeoNet website due to the network restriction) so that i can learn how you implement it right now? and then when you finish your second better way, i can learn it later.
I'll just paste the code below for you to use in your project. You'll just need to create the gpk and reference it within the code, and update the parameters provided to it. I believe the last tool I used was the Copy Features tool. You should be able to use any tool that creates an output shapefile (i.e. Copy Features, Feature Class to Feature Class, Select, etc.).
*** XAML ***
<Window x:Class="CopyFeatures.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:esri="http://schemas.esri.com/arcgis/runtime/2013"
Title="Copy Features GP" Height="700" Width="800">
<Grid x:Name="LayoutRoot">
<Grid.Resources>
<esri:SimpleLineSymbol x:Key="ResultLineSymbol" Color="Red" Style="Solid" Width="3" />
<esri:SimpleFillSymbol x:Key="ResultFillSymbol" Color="LimeGreen" Outline="{StaticResource ResultLineSymbol}" />
<esri:SimpleRenderer x:Key="ResultRenderer" Symbol="{StaticResource ResultFillSymbol}" />
</Grid.Resources>
<esri:MapView x:Name="MyMapView">
<esri:Map />
<esri:MapView.GraphicsOverlays>
<esri:GraphicsOverlay x:Name="ResultsOverlay" Renderer="{StaticResource ResultRenderer}"/>
</esri:MapView.GraphicsOverlays>
</esri:MapView>
<Border HorizontalAlignment="Right" VerticalAlignment="Top" Margin="30" Padding="20" Width="300"
Background="White" BorderBrush="Black" BorderThickness="1">
<Border.Effect>
<DropShadowEffect />
</Border.Effect>
<StackPanel>
<TextBlock Text="Click on the Copy Features button to execute the gp tool. This will create a new shapefile and add the results to the display." TextWrapping="Wrap" />
<Button x:Name="CopyFeaturesButton" Content="Copy Features" FontWeight="Bold" Margin="0,12,0,0" Padding="5" Click="CopyFeaturesButton_OnClick" IsEnabled="False" />
<TextBlock x:Name="TxtInfo" FontSize="12" Visibility="Collapsed" Margin="0,12,0,0">
<Run Text="Shapefile:" FontSize="14" FontWeight="Bold" />
<LineBreak />
<Run Text="Name: " FontWeight="Bold" />
<Run Text="{Binding ID, Mode=OneWay}" />
<LineBreak />
<Run Text="Path: " FontWeight="Bold" />
<Run Text="{Binding DisplayName, Mode=OneWay}" />
<LineBreak />
<Run Text="Spatial Reference: " FontWeight="Bold" />
<Run Text="{Binding FeatureTable.SpatialReference.Wkid, Mode=OneWay}" />
<LineBreak />
<Run Text="Geometry Type: " FontWeight="Bold" />
<Run Text="{Binding FeatureTable.GeometryType, Mode=OneWay}" />
<LineBreak />
<Run Text="Features: " FontWeight="Bold" />
<Run Text="{Binding FeatureTable.RowCount, Mode=OneWay}" />
</TextBlock>
</StackPanel>
</Border>
</Grid>
</Window>
*** CODE BEHIND ***
using Esri.ArcGISRuntime.Data;
using Esri.ArcGISRuntime.Layers;
using Esri.ArcGISRuntime.LocalServices;
using Esri.ArcGISRuntime.Symbology;
using Esri.ArcGISRuntime.Tasks.Geoprocessing;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace CopyFeatures
{
public partial class MainWindow : Window
{
private const string ShpPath = @".\data\in_shp\States.shp";
private const string CopyFeaturesPath = @".\data\packages\CopyFeatures.gpk";
private const string ResultPath = @".\data\out_shp";
private const string ServiceName = "/Copy%20Features";
private Geoprocessor _gpTask;
private static readonly Regex _pathRegex = new Regex(@"(([a-z]:|\\\\[a-z0-9_.$]+\\[a-z0-9_.$]+)?(\\?(?:[^\\/:*?""<>|\r\n]+\\)+)[^\\/:*?""<>|\r\n]+).shp", RegexOptions.IgnoreCase);
private static readonly Random _random = new Random();
public MainWindow()
{
InitializeComponent();
MyMapView.Loaded += async (sender, args) =>
{
await LoadShapefile(ShpPath);
LocalGeoprocessingService copyFeaturesService = new LocalGeoprocessingService(CopyFeaturesPath, GeoprocessingServiceType.SubmitJob);
await copyFeaturesService.StartAsync();
_gpTask = new Geoprocessor(new Uri(copyFeaturesService.UrlGeoprocessingService + ServiceName));
Debug.WriteLine(copyFeaturesService.UrlGeoprocessingService);
CopyFeaturesButton.IsEnabled = true;
};
}
private async Task LoadShapefile(string path, bool useRenderer=false)
{
try
{
// open shapefile table
var shapefile = await ShapefileTable.OpenAsync(path);
// create feature layer based on the shapefile
var flayer = new FeatureLayer(shapefile)
{
ID = shapefile.Name,
DisplayName = path,
};
if (useRenderer)
flayer.Renderer = LayoutRoot.Resources["ResultRenderer"] as SimpleRenderer;
await flayer.InitializeAsync();
// Add the feature layer to the map
MyMapView.Map.Layers.Add(flayer);
if (MyMapView.Map.Layers.Count == 1)
MyMapView.SetView(flayer.FeatureTable.Extent.Expand(1.1));
else
{
TxtInfo.DataContext = flayer;
TxtInfo.Visibility = Visibility.Visible;
}
}
catch (Exception ex)
{
MessageBox.Show("Error creating feature layer: " + ex.Message, "Sample Error");
}
}
// Submit GP Job and Poll the server for results every 2 seconds.
private static async Task<GPJobInfo> SubmitAndPollStatusAsync(GPInputParameter parameter, Geoprocessor gpTask)
{
// Submit gp service job
var result = await gpTask.SubmitJobAsync(parameter);
// Poll for the results async
while (result.JobStatus != GPJobStatus.Cancelled && result.JobStatus != GPJobStatus.Deleted
&& result.JobStatus != GPJobStatus.Succeeded && result.JobStatus != GPJobStatus.TimedOut)
{
result = await gpTask.CheckJobStatusAsync(result.JobID);
await Task.Delay(2000);
}
return result;
}
private async void CopyFeaturesButton_OnClick(object sender, RoutedEventArgs e)
{
try
{
// Disable the button while gp executes
((Button) sender).IsEnabled = false;
// Get the Feature Layer from the map
var layer = MyMapView.Map.Layers[0] as FeatureLayer;
var filter = new QueryFilter {WhereClause = "1=1"};
// Get a list of the states in the loaded shapefile
var features = await layer.FeatureTable.QueryAsync(filter);
var states = features.Select(feature => feature.Attributes["STATE_NAME"].ToString()).ToArray();
// Select 9 random states from the list of states
var randomStates = new List<string>();
while (randomStates.Count < 9)
{
var state = states[_random.Next(0, states.Length - 1)];
if (randomStates.Contains(state))
continue;
randomStates.Add(state);
}
// Create a feature set with the selected states
filter.WhereClause = string.Format("STATE_NAME IN ('{0}')", string.Join("', '", randomStates));
var featSet = new FeatureSet(await layer.FeatureTable.QueryAsync(filter));
// Use the geoprocessor to copy the shapefile with only the selected records
var parameter = new GPInputParameter();
parameter.GPParameters.Add(new GPFeatureRecordSetLayer("in_features", featSet));
// Submit gp job and wait for status update
var result = await SubmitAndPollStatusAsync(parameter, _gpTask);
if (result.JobStatus != GPJobStatus.Succeeded) return;
// Confirm that the output is a GPFeatureRecordSetLayer
var resultData = await _gpTask.GetResultDataAsync(result.JobID, "out_feature_class");
if (!(resultData is GPFeatureRecordSetLayer)) return;
// Parse the messages returned from the geoprocessor
StringBuilder builder = new StringBuilder();
string copy = string.Empty;
foreach (var message in result.Messages)
{
builder.AppendLine(message.Description);
// Special Task: Search for shapefile path in output messages
var match = _pathRegex.Match(message.Description);
if (!match.Success) continue;
string filePath = match.Value;
if (!File.Exists(filePath)) continue;
var files = Directory.GetFiles(Path.GetDirectoryName(filePath),
Path.GetFileNameWithoutExtension(filePath) + ".*");
if (!(Directory.Exists(ResultPath)))
Directory.CreateDirectory(ResultPath);
// Special Task: If needed shapefile is found...copy all files related to it to a known location
foreach (var file in files)
{
File.Copy(file, Path.Combine(ResultPath, Path.GetFileName(file)), true);
if (file.EndsWith("Copy.shp"))
copy = Path.Combine(ResultPath, Path.GetFileName(file));
}
}
// Display the results in the map
if (copy != string.Empty)
await LoadShapefile(copy, true);
else
{
GPFeatureRecordSetLayer gpLayer = resultData as GPFeatureRecordSetLayer;
ResultsOverlay.Graphics.Clear();
ResultsOverlay.Graphics.AddRange(gpLayer.FeatureSet.Features.OfType<Graphic>());
}
Debug.WriteLine("**** GP Messages ****");
Debug.WriteLine(builder.ToString());
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "EXCEPTION", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
// Enable the button now that geoprocessing has completed
((Button)sender).IsEnabled = true;
}
}
}
}
Thanks Freddie for your hepful sample. However, i got some problem following your code:
1, After it executs SubmitJobAsync method, at first, the state of "result" becomes "submitted", and then it's state is "Failed" without any more error information.
2, I did see a message saying that "Copy Features Tool requires ArcGIS Runtime Standard with Geoprocessing deployment" when i creates the gpk. I a bit doubt that this is the reason why it fails on step 1 as I am currently using a develpment license, am i right?
What are the geoprocessing messages you're getting from the local server?