Syncing Geocoder for Offline Capability in SDK for .NET

3628
2
Jump to solution
04-28-2015 02:26 PM
AndrewTangeman1
New Contributor II

Hello,

I am utilizing ESRI's Runtime SDK for .NET using the Desktop API. The applications I am developing will need to update internal data on the client via a secure web adapter connection to an outside instance of ArcGIS for Server 10.2.2.

This sync task can be considered a 1-way replica. That is, the client instances do not need to sync edits back up to the ArcGIS for Server. After the data is cached locally using the sync task, the application then records the name of the geodatabase in the .NET solution for persistent offline use.

My GeoLocator on the client side needs to utilize the most recent data available. Our locators utilize placename and road networks that change frequently. Since they will be only connecting to the ArcGIS for Server instance using a secure web adapter once or twice a week, I need a workflow to push or update a new locator to the client each time it connects.

Keeping in mind that these sync operations cannot be performed via FTP, SQL, or any other data access other than the token-authenticated link between the client app and the server, how can I best approach this problem?

One idea I had was to use a code snippet from ArcObjects for .NET (RebuildAddressLocatorPersonalGeodataase()) to rebuild the locator using data from the synced geodatabase. However, this sounds a bit convoluted.

Can anyone expand on my solution, or provide me with resources to accomplish this from within a .NET 4.5 environment?

Thanks

-Andy

Message was edited by: Andrew Tangeman, trimmed redundant info and made question more direct and concise. Added tags.

0 Kudos
1 Solution

Accepted Solutions
AndrewTangeman1
New Contributor II

After about a week of receiving no reply to this thread (and also shrugged shoulders from different unnamed support teams) I decided it was either sink or swim on finding a solution. Instead of sinking, I have developed a solid and efficient workaround.

This solution should work for anyone who needs to sync ArcGIS Runtime content remotely via a service, and can't use shared directories or FTP sites for various reasons.

Since the ArcGIS Runtime SDK for.NET supports syncing attachments, I simply added a dummy point variable to an SDE Feature Layer and attached a .zip file containing the runtime content. A separate job on the ArcGIS for Server 10.2.2 side uses a mix of Python and .Net c# to rebuild, package, and update the geocoder attachment on the FeatureLayer. The layer is then published and consumed via a sync-enabled Feature Service.

Once published, I sync the point layer with the local .geodatabase on the client side using .NET Runtime. I then iterate through the FeatureTables, query the attachments, and grab the .zip package. After, I use a method available in .Net 4.5 that supports ZIP archives to stream the result and write to a local file.

private async Task GetLocator()
{
    try
    {
        Console.WriteLine("Connecting to feature");
        Esri.ArcGISRuntime.Geometry.SpatialReference mySpatialReference = Esri.ArcGISRuntime.Geometry.SpatialReference.Create(102100);
        Esri.ArcGISRuntime.Data.ServiceFeatureTable LocatorFeatureTable = await ServiceFeatureTable.OpenAsync
            (new Uri(SERVER_URL + LocatorSericePath),
                sr_override: mySpatialReference);
        var rc = await LocatorFeatureTable.QueryAsync(1);
        // check if any were found
        if (LocatorFeatureTable.HasAttachments)
        {
            // var featureID = table.GlobalIDField;
            var attachmentResult = await LocatorFeatureTable.QueryAttachmentsAsync(1);
            // check if any were found
            if (attachmentResult.Infos.Any())
            {
                foreach (var info in attachmentResult.Infos)
                {
                    var item = string.Format("{0}: {1} - {2} ({3} bytes)", info.ID.ToString(), info.ContentType, info.Name, info.Size);
                    Console.WriteLine(item);
                    using (var stream = await info.GetDataAsync() as MemoryStream)
                    {
                        using (var archive = new FileStream(@"C:\Temp\" + info.Name + ".zip", FileMode.Create))
                        {
                            stream.Seek(0, SeekOrigin.Begin);
                            stream.CopyTo(archive);
                        }
                    }
                    try
                    {
                        string[] filePaths = Directory.GetFiles(LocalGeolocatorDir);
                        foreach (string filePath in filePaths)
                            File.Delete(filePath);
                        ZipFile.ExtractToDirectory(@"C:\Temp\" + info.Name + ".zip", LocalGeolocatorDir);
                    }
                    catch (Exception e)
                    {
                        ZipFile.ExtractToDirectory(@"C:\Temp\" + info.Name + ".zip", LocalGeolocatorDir);
                    }
                }
            }
        }
        else
        {
            Console.WriteLine("No attachments found");
        }
    }
    catch (Exception e)
    {
        Console.WriteLine("Error in GetLocator() method");
        Console.WriteLine(e.Message);
    }
}

Hope this is useful to someone who experienced issue with the lack of sync functionality for geocoding within the .NET Desktop API. Just define any global variables (such as LocalGeocoderDir) and you should be good to go. I would be interested to hear the results if anyone happens to experiments with syncing other Runtime content, such a Network Dataset.

Please let me know if you have any questions, and I will try to be courteous enough to provide a quick and helpful response.

View solution in original post

0 Kudos
2 Replies
AndrewTangeman1
New Contributor II

After about a week of receiving no reply to this thread (and also shrugged shoulders from different unnamed support teams) I decided it was either sink or swim on finding a solution. Instead of sinking, I have developed a solid and efficient workaround.

This solution should work for anyone who needs to sync ArcGIS Runtime content remotely via a service, and can't use shared directories or FTP sites for various reasons.

Since the ArcGIS Runtime SDK for.NET supports syncing attachments, I simply added a dummy point variable to an SDE Feature Layer and attached a .zip file containing the runtime content. A separate job on the ArcGIS for Server 10.2.2 side uses a mix of Python and .Net c# to rebuild, package, and update the geocoder attachment on the FeatureLayer. The layer is then published and consumed via a sync-enabled Feature Service.

Once published, I sync the point layer with the local .geodatabase on the client side using .NET Runtime. I then iterate through the FeatureTables, query the attachments, and grab the .zip package. After, I use a method available in .Net 4.5 that supports ZIP archives to stream the result and write to a local file.

private async Task GetLocator()
{
    try
    {
        Console.WriteLine("Connecting to feature");
        Esri.ArcGISRuntime.Geometry.SpatialReference mySpatialReference = Esri.ArcGISRuntime.Geometry.SpatialReference.Create(102100);
        Esri.ArcGISRuntime.Data.ServiceFeatureTable LocatorFeatureTable = await ServiceFeatureTable.OpenAsync
            (new Uri(SERVER_URL + LocatorSericePath),
                sr_override: mySpatialReference);
        var rc = await LocatorFeatureTable.QueryAsync(1);
        // check if any were found
        if (LocatorFeatureTable.HasAttachments)
        {
            // var featureID = table.GlobalIDField;
            var attachmentResult = await LocatorFeatureTable.QueryAttachmentsAsync(1);
            // check if any were found
            if (attachmentResult.Infos.Any())
            {
                foreach (var info in attachmentResult.Infos)
                {
                    var item = string.Format("{0}: {1} - {2} ({3} bytes)", info.ID.ToString(), info.ContentType, info.Name, info.Size);
                    Console.WriteLine(item);
                    using (var stream = await info.GetDataAsync() as MemoryStream)
                    {
                        using (var archive = new FileStream(@"C:\Temp\" + info.Name + ".zip", FileMode.Create))
                        {
                            stream.Seek(0, SeekOrigin.Begin);
                            stream.CopyTo(archive);
                        }
                    }
                    try
                    {
                        string[] filePaths = Directory.GetFiles(LocalGeolocatorDir);
                        foreach (string filePath in filePaths)
                            File.Delete(filePath);
                        ZipFile.ExtractToDirectory(@"C:\Temp\" + info.Name + ".zip", LocalGeolocatorDir);
                    }
                    catch (Exception e)
                    {
                        ZipFile.ExtractToDirectory(@"C:\Temp\" + info.Name + ".zip", LocalGeolocatorDir);
                    }
                }
            }
        }
        else
        {
            Console.WriteLine("No attachments found");
        }
    }
    catch (Exception e)
    {
        Console.WriteLine("Error in GetLocator() method");
        Console.WriteLine(e.Message);
    }
}

Hope this is useful to someone who experienced issue with the lack of sync functionality for geocoding within the .NET Desktop API. Just define any global variables (such as LocalGeocoderDir) and you should be good to go. I would be interested to hear the results if anyone happens to experiments with syncing other Runtime content, such a Network Dataset.

Please let me know if you have any questions, and I will try to be courteous enough to provide a quick and helpful response.

0 Kudos
AnttiKajanus1
Occasional Contributor III

Good to hear that you managed to get that done and it sounds like a viable workaround. We try to share locators and network datasets using Portal functionality but if you don't have Portal in your AG Server installation then that's not going to help much but posting this if someone else finds it helpful.

So one solution to share (any data that can be zipped) with ArcGIS Online or Portal on premises is to use ArcGISPortalItem that contains the data in it's content. When connecting to the rest enpoint, you need to query the correct Portal Item (or use direct id), download data and unzip it to the disk similarly than OP has done. You can easily check if the Portal Item has changed from the last time you downloaded the data too.

I demonstrated this in ArcGIS Runtime SDK for .NET: Transitioning to It from Other Esri .NET SDKs | Esri Video  around 29 minutes where I also upload the data to the portal (which also can live on premises).

0 Kudos