Showing posts with label ESRI. Show all posts
Showing posts with label ESRI. Show all posts

Tuesday, March 5, 2013

ArcObjects: Licensing

This post is the 7th post in my ArcObjects series.

ESRI has a very annoying licensing settings which I curse every time my license expires (once a year using EDN). Lets start by saying ESRI delivers the license in the form of ABC######: EFL123456, ESU123456, ECP123456, RUD123456 and each has a different set of instructions. I usually get an ECP license (ArcGis Server) and there is a guide for that.

Assuming you managed to pass the nightmare and tried to use ArcObjects in your code then you will get an Exception. In this case I just run this code:

IPoint point = new PointClass { X = x, Y = y, SpatialReference = CreateWgsSpatialReference() };

And got this exception:

System.Runtime.InteropServices.COMException : Retrieving the COM class factory for component with CLSID {00A5CB41-52DA-11D0-A8F2-00608C85EDE5} failed due to the following error: 80040111 ClassFactory cannot supply requested class (Exception from HRESULT: 0x80040111 (CLASS_E_CLASSNOTAVAILABLE)).

As you can see it is very descriptive. But basically it tells you that without initializing the license you won’t be able to use any ArcObjects (even IPoint). In version 9.3 the license was done using class RuntimeManager but in version 10 the IAoInitialize interface was added. I usually add them together like this:

private static bool Initialize(ProductCode product, esriLicenseProductCode esriLicenseProduct)
{
    if (RuntimeManager.Bind(product))
    {
        IAoInitialize aoInit = new AoInitializeClass();
        aoInit.Initialize(esriLicenseProduct);
        return true;
    }
    return false;
}

This method checks if the current machine has a license of ProductCode which is an enum with values like Server or Engine and if it does returns true. The usage looks like this:

private static bool _isStarted = false;

public static void Start()
{
    if (_isStarted)
        return;

    if (!Initialize(ProductCode.Server, esriLicenseProductCode.esriLicenseProductCodeArcServer))
    {
        if(!Initialize(ProductCode.Engine, esriLicenseProductCode.esriLicenseProductCodeEngineGeoDB))
        {
            throw new ApplicationException(
                "Unable to bind to ArcGIS license Server nor to Engine. Please check your licenses.");
        }
    }
    _isStarted = true;
}

Since the production machines are usually ArcGis Server and the development machines are Engine we usually check if the machine has Server license and if it doesn’t then we go to the fallback of Engine. Now the creation of PointClass will work.

 

That’s it.

Tuesday, May 1, 2012

Polyline wrong coordinates

We have been developing for a long time on a single oracle DB Server (Development stage) and recently added a new integration server. Because of time constraints we decided to just copy the data from the Dev server using SQL (out DBA did that part), and since we moved to SDO_GEOMETRY the data can be transferred without the use of ArcDesktop utilities.

So the data was moved correctly and the registration went without a glitch. But when we tried to create a new line in the layer its coordinates moved from (35.5, 32.7) (in x,y) to (–324.4, 32.7). The first thing I checked was my code but there were no changes done and in the old environment everything worked fine.

Looking at the Layers table in the SDE schema that table looked like:

EFLAGS

LAYER_MASK

GSIZE1

GSIZE2

GSIZE3

MINX

MINY

MAXX

MAXY

LAYER_CONFIG

138674317

128

-2

0 0

-400

-400

471.3916

471.3916

SDO_GEOMETRY

My first thought was that the –400 somehow affected the data being inserted but the same values were in the old DB and they worked.

After many actions of unregister and register of that layer the problem was solved.

 

TODO: Add table border, it looks bad in the site

Tuesday, March 6, 2012

Refactoring ESRI: Making the Examples Readable, Part 1

This is the refactor for ESRI example for creating an Address Locator in a Personal GeoDatabase:

  1. public void AddressLocatorPersonalGDBViaArcObjects()
  2. {
  3.     // Open the default local locator workspace to get the locator style.
  4.     System.Object obj = Activator.CreateInstance(Type.GetTypeFromProgID(
  5.         "esriLocation.LocatorManager"));
  6.     ILocatorManager2 locatorManager2 = obj as ILocatorManager2;
  7.     ILocatorWorkspace locatorWorkspace = locatorManager2.GetLocatorWorkspaceFromPath
  8.         ("");
  9.  
  10.     // Get the locator style to base the new locator.
  11.     ILocatorStyle locatorStyle = locatorWorkspace.GetLocatorStyle(
  12.         "US Address - Dual Ranges");
  13.  
  14.     // Open the feature class to use as reference data.
  15.     IWorkspaceFactory2 workspaceFactory2 = new AccessWorkspaceFactoryClass();
  16.     IWorkspace workspace = workspaceFactory2.OpenFromFile(
  17.         @"D:\workspace\arcobjects\location\redlands.mdb",
  18.         0);
  19.     IFeatureWorkspace featureWorkspace = (IFeatureWorkspace) workspace;
  20.     IFeatureClass featureClass = featureWorkspace.OpenFeatureClass("Streets");
  21.  
  22.     // Set the feature class as the primary reference data table for the locator.
  23.     IDataset dataset = (IDataset) featureClass;
  24.  
  25.     IReferenceDataTables referenceDataTables = (IReferenceDataTables) locatorStyle;
  26.     IEnumReferenceDataTable enumReferenceDataTable = referenceDataTables.Tables;
  27.     enumReferenceDataTable.Reset();
  28.  
  29.     IReferenceDataTable referenceDataTable = enumReferenceDataTable.Next();
  30.     IReferenceDataTableEdit referenceDataTableEdit = (IReferenceDataTableEdit)
  31.                                                      referenceDataTable;
  32.     IName name = dataset.FullName;
  33.     referenceDataTableEdit.Name_2 = (ITableName) name;
  34.  
  35.     // Store the new locator in the same workspace as the reference data.
  36.     if (referenceDataTables.HasEnoughInfo)
  37.     {
  38.         locatorWorkspace = locatorManager2.GetLocatorWorkspaceFromPath(
  39.             @"D:\workspace\arcobjects\location\redlands.mdb");
  40.         ILocator locator = locatorWorkspace.AddLocator("New Redlands Locator",
  41.                                                        (ILocator) locatorStyle, "", null);
  42.     }
  43. }

The first order of business is refactoring this method.

The first thing to go was this:

  1. IFeatureClass featureClass = featureWorkspace.OpenFeatureClass("Streets");
  2.  
  3. // Set the feature class as the primary reference data table for the locator.
  4. IDataset dataset = (IDataset) featureClass;

To:

  1. IDataset dataset = (IDataset)featureWorkspace.OpenFeatureClass("Streets");

There was a few other things like: referenceDataTable->referenceDataTableEdit,

The end result was:

  1. public void AddressLocatorPersonalGDBViaArcObjects()
  2. {
  3.     // Open the default local locator workspace to get the locator style.
  4.     System.Object obj = Activator.CreateInstance(Type.GetTypeFromProgID(
  5.         "esriLocation.LocatorManager"));
  6.     var locatorManager2 = obj as ILocatorManager2;
  7.     var locatorWorkspace = locatorManager2.GetLocatorWorkspaceFromPath
  8.         ("");
  9.  
  10.     // Get the locator style to base the new locator.
  11.     var locatorStyle = locatorWorkspace.GetLocatorStyle(
  12.         "US Address - Dual Ranges");
  13.     // Open the feature class to use as reference data.
  14.     IWorkspaceFactory2 workspaceFactory2 = new AccessWorkspaceFactoryClass();
  15.     var featureWorkspace = (IFeatureWorkspace)workspaceFactory2.OpenFromFile(
  16.         @"D:\workspace\arcobjects\location\redlands.mdb",
  17.         0);
  18.  
  19.     // Set the feature class as the primary reference data table for the locator.
  20.     var dataset = (IDataset)featureWorkspace.OpenFeatureClass("Streets");
  21.     
  22.     var referenceDataTables = (IReferenceDataTables)locatorStyle;
  23.  
  24.     var enumReferenceDataTable = referenceDataTables.Tables;
  25.     enumReferenceDataTable.Reset();
  26.  
  27.     var referenceDataTableEdit = (IReferenceDataTableEdit) enumReferenceDataTable.Next();
  28.     
  29.     referenceDataTableEdit.Name_2 = (ITableName)dataset.FullName;
  30.  
  31.     // Store the new locator in the same workspace as the reference data.
  32.     if (referenceDataTables.HasEnoughInfo)
  33.     {
  34.         locatorWorkspace = locatorManager2.GetLocatorWorkspaceFromPath(
  35.             @"D:\workspace\arcobjects\location\redlands.mdb");
  36.         locatorWorkspace.AddLocator("New Redlands Locator",
  37.                                                        (ILocator) locatorStyle, "", null);
  38.     }
  39. }

39 lines without any loss of readability, not that it says much – it still not readable!

The next thing to look at is creating the ILocatorManager2, I don’t know about you but I prefer getting an exception about converting than about Null reference:

  1. (ILocatorManager2)Activator.CreateInstance(Type.GetTypeFromProgID("esriLocation.LocatorManager"));

(that also solved the problem of the ‘obj’ variable naming)

The next part is creating a Utils class for LocatorWorkspace:

  1. public class LocatorWorkspaceUtils
  2. {
  3.     private readonly ILocatorManager2 _locatorManager;
  4.  
  5.     #region Singleton
  6.  
  7.     private static readonly LocatorWorkspaceUtils instance = new LocatorWorkspaceUtils();
  8.  
  9.     // Explicit static constructor to tell C# compiler
  10.     // not to mark type as beforefieldinit
  11.     static LocatorWorkspaceUtils()
  12.     {
  13.     }
  14.  
  15.     private LocatorWorkspaceUtils()
  16.     {
  17.         _locatorManager =
  18.             (ILocatorManager2)Activator.CreateInstance(Type.GetTypeFromProgID("esriLocation.LocatorManager"));
  19.     }
  20.  
  21.     public static LocatorWorkspaceUtils Instance
  22.     {
  23.         get { return instance; }
  24.     }
  25.  
  26.     #endregion

It’s singleton with ILocatorManager already set, now the only thing we use it for is:

  1. private ILocatorWorkspace GetLocatorWorkspace(string fileLocation)
  2. {
  3.     return _locatorManager.GetLocatorWorkspaceFromPath(fileLocation);
  4. }

Getting the style:

  1. public ILocatorStyle GetLocatorStyle(string styleName)
  2. {
  3.     return GetLocatorStyle(styleName, "");
  4. }
  5.  
  6. public ILocatorStyle GetLocatorStyle(string styleName, string fileLocation)
  7. {
  8.     var locatorWorkspace = GetLocatorWorkspace(fileLocation);
  9.  
  10.     // Get the locator style to base the new locator.
  11.     return locatorWorkspace.GetLocatorStyle(styleName);
  12. }

Adding the locator style:

  1. public void AddLocatorStyle(string styleName, string fileLocation, ILocator locatorStyle)
  2. {
  3.     var locatorWorkspace = GetLocatorWorkspace(fileLocation);
  4.     locatorWorkspace.AddLocator(styleName, locatorStyle, "", null);
  5. }

Setting the dataset name in the locator:

  1. public void SetLocatorDatasetName(IName datasetName, ILocatorStyle locatorStyle)
  2. {
  3.     var referenceDataTables = (IReferenceDataTables)locatorStyle;
  4.  
  5.     var enumReferenceDataTable = referenceDataTables.Tables;
  6.     enumReferenceDataTable.Reset();
  7.  
  8.     var referenceDataTableEdit = (IReferenceDataTableEdit)enumReferenceDataTable.Next();
  9.  
  10.     //var name = new TableNameClass {Name = datasetName};
  11.  
  12.     referenceDataTableEdit.Name_2 = (ITableName)datasetName;
  13. }

Checking if the locator is good to be saved:

  1. public bool IsLocatorValidForSave(ILocatorStyle locatorStyle)
  2. {
  3.     return ((IReferenceDataTables)locatorStyle).HasEnoughInfo;
  4. }

Now the code looks like:

  1. public void AddressLocatorPersonalGDBViaArcObjects()
  2. {
  3.     // Get the locator style to base the new locator.
  4.     var locatorStyle = LocatorWorkspaceUtils.Instance.GetLocatorStyle("US Address - Dual Ranges");
  5.  
  6.     // Open the feature class to use as reference data.
  7.     IWorkspaceFactory2 workspaceFactory2 = new AccessWorkspaceFactoryClass();
  8.     var featureWorkspace = (IFeatureWorkspace)workspaceFactory2.OpenFromFile(
  9.         @"D:\workspace\arcobjects\location\redlands.mdb",
  10.         0);
  11.  
  12.     // Set the feature class as the primary reference data table for the locator.
  13.     var dataset = (IDataset)featureWorkspace.OpenFeatureClass("Streets");
  14.     LocatorWorkspaceUtils.Instance.SetLocatorDatasetName(dataset.FullName, locatorStyle);
  15.  
  16.     if (LocatorWorkspaceUtils.Instance.IsLocatorValidForSave(locatorStyle))
  17.     {
  18.         LocatorWorkspaceUtils.Instance.AddLocatorStyle("New Redlands Locator",
  19.             @"D:\workspace\arcobjects\location\redlands.mdb",
  20.             (ILocator)locatorStyle);
  21.     }
  22. }

Now something seems a bit off here, the only reason we open the workspace for the MDB file is to get the dataset FullName, WTF?!? Why not just pass the name as a… wait for it… string?

TODO: continue this

 

Keywords: ESRI, ArcObjects, refactor

IceRocket Tags: ,,

Wednesday, March 16, 2011

File GeoDatabase: Getting the Workspace

I have created FileWorkspaceUtils that inherits from WorkspaceUtils, it adds the functions GetRows and GetFeatures that return the raw IRow and IFeature data. In WorkspaceUtils I preferred that the low level programmer won’t even know he has something called IRow or IFeature.

  1. public class FileWorkspaceUtils:WorkspaceUtils
  2. {
  3.     public FileWorkspaceUtils(IFeatureWorkspace workspace) : base(workspace)
  4.     {
  5.     }
  6.  
  7.     public List<IRow> GetRows(string tableName)
  8.     {
  9.         var result = new List<IRow>();
  10.         DoActionOnSelectRows(tableName, null, row => result.Add(row.Clone()));
  11.         return result;
  12.     }
  13.  
  14.     public List<IFeature> GetFeatures(string layerName)
  15.     {
  16.         var result = new List<IFeature>();
  17.         DoActionOnSelectFeatures(layerName, null, feature => result.Add(feature.Clone()));
  18.         return result;
  19.     }
  20. }

//TODO: Post on the wonder of Extension Methods (row.Clone())

I have added code to WorkspaceProvider so that it will return the FileWorkspaceUtils (independent of File/Personal GeoDatabase):

  1. private const string PersonalGeoDatabaseFileExtension = ".MDB";
  2. private const string FileGeoDatabaseFileExtension = ".GDB";
  3.  
  4. /// <summary>
  5. /// Get a File WorkspaceUtils for Personal and File GeoDatabase
  6. /// </summary>
  7. /// <param name="filePath"></param>
  8. /// <returns></returns>
  9. public FileWorkspaceUtils GetFileWorkspace(string filePath)
  10. {
  11.     var extension = (Path.GetExtension(filePath) ?? String.Empty).ToUpper();
  12.     if (extension.CompareTo(PersonalGeoDatabaseFileExtension) == 0)
  13.         return CreatePersonalGeoDatabaseWorkspace(filePath);
  14.     if (extension.CompareTo(FileGeoDatabaseFileExtension) == 0)
  15.         return CreateFileGeoDatabaseWorkspace(filePath);
  16.  
  17.     throw new NotImplementedException("The only supported file types are mdb and gdb. Not: " + extension);
  18. }
  19.  
  20. private FileWorkspaceUtils CreatePersonalGeoDatabaseWorkspace(string filePath)
  21. {
  22.     AccessWorkspaceFactory workspaceFactory = new AccessWorkspaceFactoryClass();
  23.  
  24.     var workspace = workspaceFactory.OpenFromFile(filePath, 0);
  25.     return new FileWorkspaceUtils((IFeatureWorkspace)workspace);
  26. }
  27.  
  28. private FileWorkspaceUtils CreateFileGeoDatabaseWorkspace(string filePath)
  29. {
  30.     FileGDBWorkspaceFactory workspaceFactory = new FileGDBWorkspaceFactoryClass();
  31.  
  32.     var workspace = workspaceFactory.OpenFromFile(filePath, 0);
  33.     return new FileWorkspaceUtils((IFeatureWorkspace)workspace);
  34. }

The only problem is it doesn’t work, my unit tests that just check GetFileWorkspace throws a COMException:

Test method CompanyName.GIS.Core.Esri.Tests.WorkspaceProviderTests.GetWorkspace_ValidPersonalGeoDB_GetFileWorkspaceUtils threw exception:
System.Runtime.InteropServices.COMException: Exception from HRESULT: 0x80040228
at ESRI.ArcGIS.DataSourcesGDB.AccessWorkspaceFactoryClass.OpenFromFile(String fileName, Int32 hWnd)
at Core.Esri.WorkspaceProvider.CreatePersonalGeoDatabaseWorkspace(String filePath) in WorkspaceProvider.cs: line 200
at Core.Esri.WorkspaceProvider.GetFileWorkspace(String filePath) in WorkspaceProvider.cs: line 189
at Core.Esri.Tests.WorkspaceProviderTests.GetWorkspace_ValidPersonalGeoDB_GetFileWorkspaceUtils() in WorkspaceProviderTests.cs: line 55

The problem was caused by Licensing, I changed EsriInitilization to contained the old style licensing as well (the one with IAoInitialize, the new stuff is using RuntimeManager):

All my unit tests (427 tests) pass, so it works…

  1. public class EsriInitilization
  2. {
  3.     private static bool _isStarted = false;
  4.  
  5.     public static void Start()
  6.     {
  7.         if (_isStarted)
  8.             return;
  9.  
  10.         if (!Initialize(ProductCode.Server, esriLicenseProductCode.esriLicenseProductCodeArcServer))
  11.         {
  12.             if(!Initialize(ProductCode.Engine, esriLicenseProductCode.esriLicenseProductCodeEngineGeoDB))
  13.             {
  14.                 throw new ApplicationException(
  15.                     "Unable to bind to ArcGIS license Server nor to Engine. Please check your licenses.");
  16.             }
  17.         }
  18.         _isStarted = true;
  19.     }
  20.  
  21.     private static bool Initialize(ProductCode product, esriLicenseProductCode esriLicenseProduct)
  22.     {
  23.         if (RuntimeManager.Bind(product))
  24.         {
  25.             IAoInitialize aoInit = new AoInitializeClass();
  26.             aoInit.Initialize(esriLicenseProduct);
  27.             return true;
  28.         }
  29.         return false;
  30.     }
  31. }

That still throw an exception, this time simply because IFeature refused to be cloned – though it implemented ESRI’s IClone interface. The error I got was:

//TODO: Write error and new code

//TODO: Post after writing about Extension Method (TODO above)

Resources:

Esri Forum: COM Exception 0x80040228 When Opening a Personal Geodatabase

 

Keywords: License, COM, exception, IWorkspace, engine, Server, ArcGis, ESRI, Unit tests, MDB, GDB

Wednesday, January 26, 2011

Address Locator Style: Testing

Well since I don’t have ArcDesktop, and can’t do this.

I will do the easy thing and test my Address Locator Style by code, fun fun fun, NOT!

What do I want to achieve?

1. Basic: Create an Address Locator in a GeoDatabase

2. Optional: Delete an Address Locator in a GeoDatabase

3. Advanced: Use an Address Locator in a GeoDatabase

 

Well I decided to start from the ESRI example, that creates an Address Locator in a Personal GeoDatabase (after refactoring):

The main class:

  1. public class AddressLocatorUtils
  2. {
  3.     public void Create(string styleName, string mdbFilePath, string featureClassName, string locatorName)
  4.     {
  5.         var locatorStyle = LocatorWorkspaceUtils.Instance.GetLocatorStyle(styleName);
  6.         var dataset = AccessWorkspaceUtils.Instance.GetDataset(mdbFilePath, featureClassName);
  7.         LocatorWorkspaceUtils.Instance.SetLocatorDatasetName(dataset.FullName, locatorStyle);
  8.  
  9.         if (LocatorWorkspaceUtils.Instance.IsLocatorValidForSave(locatorStyle))
  10.         {
  11.             LocatorWorkspaceUtils.Instance.AddLocatorStyle(locatorName,mdbFilePath,(ILocator)locatorStyle);
  12.         }
  13.         else
  14.         {
  15.             throw new ApplicationException("Something went wrong...");
  16.         }
  17.     }
  18. }

The utils:

  1. public class LocatorWorkspaceUtils
  2. {
  3.     private readonly ILocatorManager2 _locatorManager;
  4.  
  5.     #region Singleton
  6.  
  7.     private static readonly LocatorWorkspaceUtils instance = new LocatorWorkspaceUtils();
  8.  
  9.     // Explicit static constructor to tell C# compiler
  10.     // not to mark type as beforefieldinit
  11.     static LocatorWorkspaceUtils()
  12.     {
  13.     }
  14.  
  15.     private LocatorWorkspaceUtils()
  16.     {
  17.         EsriInitilization.Start();
  18.         _locatorManager =
  19.             (ILocatorManager2)Activator.CreateInstance(Type.GetTypeFromProgID("esriLocation.LocatorManager"));
  20.     }
  21.  
  22.     public static LocatorWorkspaceUtils Instance
  23.     {
  24.         get { return instance; }
  25.     }
  26.  
  27.     #endregion
  28.  
  29.     public ILocatorStyle GetLocatorStyle(string styleName)
  30.     {
  31.         return GetLocatorStyle(styleName, "");
  32.     }
  33.  
  34.     public ILocatorStyle GetLocatorStyle(string styleName, string fileLocation)
  35.     {
  36.         var locatorWorkspace = GetLocatorWorkspace(fileLocation);
  37.  
  38.         // Get the locator style to base the new locator.
  39.         return locatorWorkspace.GetLocatorStyle(styleName);
  40.     }
  41.  
  42.     public void SetLocatorDatasetName(IName datasetName, ILocatorStyle locatorStyle)
  43.     {
  44.         var referenceDataTables = (IReferenceDataTables)locatorStyle;
  45.  
  46.         var enumReferenceDataTable = referenceDataTables.Tables;
  47.         enumReferenceDataTable.Reset();
  48.  
  49.         var referenceDataTableEdit = (IReferenceDataTableEdit)enumReferenceDataTable.Next();
  50.  
  51.         referenceDataTableEdit.Name_2 = (ITableName)datasetName;
  52.     }
  53.  
  54.     public bool IsLocatorValidForSave(ILocatorStyle locatorStyle)
  55.     {
  56.         return ((IReferenceDataTables)locatorStyle).HasEnoughInfo;
  57.     }
  58.  
  59.     public void AddLocatorStyle(string styleName, string fileLocation, ILocator locatorStyle)
  60.     {
  61.         var locatorWorkspace = GetLocatorWorkspace(fileLocation);
  62.         locatorWorkspace.AddLocator(styleName, locatorStyle, "", null);
  63.     }
  64.  
  65.     private ILocatorWorkspace GetLocatorWorkspace(string fileLocation)
  66.     {
  67.         return _locatorManager.GetLocatorWorkspaceFromPath(fileLocation);
  68.     }
  69. }

And AccessWorkspaceUtils contains:

  1. public IDataset GetDataset(string mdbFilePath, string featureClassName)
  2. {
  3.     // Open the feature class to use as reference data.
  4.     IWorkspaceFactory2 workspaceFactory2 = new AccessWorkspaceFactoryClass();
  5.     var featureWorkspace = (IFeatureWorkspace)workspaceFactory2.OpenFromFile(mdbFilePath, 0);
  6.  
  7.     // Set the feature class as the primary reference data table for the locator.
  8.     return (IDataset)featureWorkspace.OpenFeatureClass(featureClassName);
  9. }

My test:

  1. [TestMethod]
  2. public void Create_DllShepherdAddressLocator_AddressLocatorCreated()
  3. {
  4.     const string styleFileName = "Dll Shepherd Address";
  5.     const string referenceDataStyleName = "ZIP 5-Digit";
  6.     var styleName = String.Format("{0} - {1}", styleFileName,
  7.                                   referenceDataStyleName);
  8.     _addressLocatorUtils.Create(styleName, @"C:\Projects\Testings\Esri.AddressLocator\Esri.AddressLocator.CoreTests\bin\Debug\Israel_for_Roy.mdb",
  9.                                 "Streets", "New Dll Shepherd Locator");
  10. }

And it works, it create a style with the name.

Before:

Access-before-creating-address-locator

After:

access-after-creating-address-locator

Well we passed 1.

And then I tested it on something I created and it failed on:

  1. var referenceDataTables = (IReferenceDataTables)locatorStyle;

With this Exception:

System.InvalidCastException was unhandled by user code
  Message=Unable to cast COM object of type 'System.__ComObject' to interface type 'ESRI.ArcGIS.Location.IReferenceDataTables'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{600A5898-DDC1-11D3-9F74-00C04F8ED1C4}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).

I decided that though ESRI help might be not very helpful here, their help in ArcCatalog might be better. So I asked Vered to test it and it just asked Vered if she wanted to send the Error to ESRI after closing her ArcCatalog – she almost killed me!

 

The exception is thrown simply because locatorStyle does not implements the IReferenceDataTables interface. Which is weird because locatorStyle can be converted to ESRIGen2AddressLocatorStyle:

Esri-location-model-diagram-locator-style

As you can see ESRIGen2AddressLocatorStyle inherits from ESRIReferenceDataAddressLocatorStyle which implements IReferenceDataTables. The weirdness doesn’t stop here looking at the source for ESRIGen2AddressLocatorStyle:

  1. namespace ESRI.ArcGIS.Location
  2. {
  3.     [CoClass(typeof (ESRIGen2AddressLocatorStyleClass))]
  4.     [Guid("655C5C62-6478-11D3-9F57-00C04F6BDF06")]
  5.     [ComImport]
  6.     public interface ESRIGen2AddressLocatorStyle : ILocator
  7.     {
  8.     }
  9. }

It only implements ILocator and I can’t even find ESRIReferenceDataAddressLocatorStyle, I started to think I can’t read this diagrams but:

ESRI-class-diagram-help

According to this both ESRIReferenceDataAddressLocatorStyle and ESRIGen2AddressLocatorStyle are classes and one inherits from the other – exactly like I thought!

 

 

TODO: add link in “after refactoring” to the post about refactoring ESRI part 1

Resources:

 

Keywords: ESRI, ArcObjects, Access, unit test, address locator

Tuesday, January 25, 2011

Address Locator Style: Customizing Guide

Please read “Address Locator Style: Introduction” before reading this post.

Editing the Locator Style files:

I am going through this guide by ESRI.

What am I changing?

For me it’s to create an address locator without state (here in Israel we don’t have a state).

Also an optional change is adding common spelling mistakes in Hebrew to the mix.

 

Basic Change: Name and Description

1. Copied: USAddress.lot.xml, LocatorStyle.xsd, and LocatorStyle.xslt to my projects folder.

2. Rename USAddress.lot.xml

3. Change the name and description for the address locator:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <?xml-stylesheet type="text/xsl" href="LocatorStyle.xslt"?>
  3. <locators xsi:noNamespaceSchemaLocation="LocatorStyle.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  4.   <locator>
  5.     <name>Dll Shepherd Address</name>
  6.     <desc>Locator style for Dll Shepherd addresses</desc>
  7.     <version>10</version>

 

Change: create an address locator without state

1. Create ref_data_style with the name City Street House Number

  1. <ref_data_style>
  2.   <name>City Street House Number</name>
  3.   <desc>City,Street and House Number</desc>
  4.   <table_roles>
  5.     <table_role name="Primary">
  6.       <display_name>Primary Table</display_name>
  7.       <desc>streets feature class</desc>
  8.       <field_roles>
  9.         <field_role name="Primary.Shape" is_geometry="true">
  10.           <display_name>Geometry</display_name>
  11.           <preferred_name>Shape</preferred_name>
  12.         </field_role>
  13.         <field_role name="Primary.ID">
  14.           <display_name>Feature ID</display_name>
  15.           <preferred_name>ID</preferred_name>
  16.           <preferred_name>POINTID</preferred_name>
  17.           <preferred_name>FEATUREID</preferred_name>
  18.           <preferred_name>FEATURE_ID</preferred_name>
  19.           <preferred_name>FEAT_ID</preferred_name>
  20.           <preferred_name>FEATUREID_1</preferred_name>
  21.           <preferred_name>FEATUREID_2</preferred_name>
  22.           <preferred_name>DYNAMAPID</preferred_name>
  23.           <preferred_name>DYNAMAP_ID</preferred_name>
  24.           <preferred_name>DYNAMAPID_1</preferred_name>
  25.           <preferred_name>DYNAMAPID_2</preferred_name>
  26.           <preferred_name>OBJECTID</preferred_name>
  27.           <preferred_name>OBJECT_ID</preferred_name>
  28.           <preferred_name>OBJECTID_1</preferred_name>
  29.           <preferred_name>OBJECTID_2</preferred_name>
  30.           <preferred_name>FID</preferred_name>
  31.           <preferred_name>FID_1</preferred_name>
  32.           <preferred_name>FID_2</preferred_name>
  33.           <preferred_name>AREAID</preferred_name>
  34.           <preferred_name>LINK_ID</preferred_name>
  35.           <preferred_name>AREA_ID</preferred_name>
  36.           <preferred_name>LINKID</preferred_name>
  37.           <preferred_name>TLID</preferred_name>
  38.         </field_role>
  39.         <field_role name="Primary.CityName">
  40.           <display_name>CityName</display_name>
  41.           <preferred_name>CityName</preferred_name>
  42.           <preferred_name>CityNameEng</preferred_name>
  43.           <preferred_name>CITY_NAME</preferred_name>
  44.           <preferred_name>CITY</preferred_name>
  45.           <preferred_name>City</preferred_name>
  46.           <preferred_name>CityCode</preferred_name>
  47.           <preferred_name>CITY_CODE</preferred_name>
  48.         </field_role>
  49.         <field_role name="Primary.StreetName">
  50.           <display_name>StreetName</display_name>
  51.           <preferred_name>StreetName</preferred_name>
  52.           <preferred_name>StreetNameEng</preferred_name>
  53.           <preferred_name>STREET_NAME</preferred_name>
  54.           <preferred_name>STREET</preferred_name>
  55.           <preferred_name>Street</preferred_name>
  56.           <preferred_name>StreetCode</preferred_name>
  57.           <preferred_name>STREET_CODE</preferred_name>
  58.         </field_role>
  59.         <field_role name="Primary.HouseNumber">
  60.           <display_name>HouseNumber</display_name>
  61.           <preferred_name>HouseNumber</preferred_name>
  62.           <preferred_name>HOUSE_NUMBER</preferred_name>
  63.           <preferred_name>House</preferred_name>
  64.           <preferred_name>HouseNum</preferred_name>
  65.           <preferred_name>HOUSE</preferred_name>
  66.         </field_role>
  67.         <field_role name="Primary.User_fld">
  68.           <display_name>Additional Field</display_name>
  69.         </field_role>
  70.       </field_roles>
  71.     </table_role>
  72.   </table_roles>
  73.   <data_source type="indexed">
  74.     <mapping_schema ref="REG_COUNTRY" />
  75.     <queries>
  76.       <query>
  77.         <tables>
  78.           <table role_ref="Primary" />
  79.         </tables>
  80.         <fields>
  81.           <field ref="Shape" field_role_ref="Primary.Shape" />
  82.           <field ref="ID" field_role_ref="Primary.ID" />
  83.           <field ref="CityName" field_role_ref="Primary.CityName" />
  84.           <field ref="StreetName" field_role_ref="Primary.StreetName" />
  85.           <field ref="HouseNumber" field_role_ref="Primary.HouseNumber" />
  86.           <field ref="User_fld" field_role_ref="Primary.User_fld" />
  87.         </fields>
  88.       </query>
  89.     </queries>
  90.   </data_source>

I haven’t touched <inputs> and <multiline_grammar> since I don’t know how they work. Testing this now will throw an exception…

2. Since in the previous step we did:

  1. <data_source type="indexed">
  2.   <mapping_schema ref="REG_COUNTRY" />
  3.   <queries>
  4.     <query>
  5.       <tables>
  6.         <table role_ref="Primary" />
  7.       </tables>
  8.       <fields>
  9.         <field ref="Shape" field_role_ref="Primary.Shape" />
  10.         <field ref="ID" field_role_ref="Primary.ID" />
  11.         <field ref="CityName" field_role_ref="Primary.CityName" />
  12.         <field ref="StreetName" field_role_ref="Primary.StreetName" />
  13.         <field ref="HouseNumber" field_role_ref="Primary.HouseNumber" />
  14.         <field ref="User_fld" field_role_ref="Primary.User_fld" />
  15.       </fields>
  16.     </query>
  17.   </queries>
  18. </data_source>

Then we need to create the REG_COUNTRY mapping_schema with those fields. Like ZIP:

  1. <field name="ZIP" grammar_ref="ZIP5">
  2.   <desc>ZIP code</desc>
  3.   <preferred_name>ZIP</preferred_name>
  4.   <preferred_name>ZIPCODE</preferred_name>
  5. </field>

But what is the grammar for CityName? Well inside grammar->Zones->ZIP5:

  1. <def name="ZIP5">
  2.   <alt>`[0-9][0-9][0-9][0-9][0-9]`</alt>
  3. </def>

(It defines ZIP5 as a string of 5 numeric characters)

Looking above it I found:

  1. <def name="City">
  2.   <alias_list_ref ref="CityAliases"/>
  3.   <alt ref="wordlist"/>
  4. </def>

(This defines City as a string of words (wordlist is word+wordlist (who loves recursive?)))

The good news is that House is defined but the bad news Street is not. But that is easily fixable we will just add below City:

  1. <def name="Street">
  2.   <alt ref="wordlist"/>
  3. </def>

(You can use City for that as well but I prefer creating this definition that later can be customized (for example street must start with “St.” can be defined here)

Later I found this definition for street:

  1. <def name="StName">
  2.   <alias_list_ref ref="StreetNameAliases"/>
  3.   <alt ref="name"/>
  4. </def>

Back to our mapping schema, well I started with a copy of USZIP and changed the name and descriptions:

  1. <mapping_schema name="REG_COUNTRY" geom_type="point">
  2.   <desc>Inner Country schema - city,street,house</desc>
  3.   <desc>Point features containing Inner Country schema - city,street,house.</desc>
  4.   <fields>

(Shape and Id I kept the same)

  1. <field name="CityName" grammar_ref="City">
  2.   <desc>City Name</desc>
  3.   <preferred_name>CityName</preferred_name>
  4.   <preferred_name>CITY_NAME</preferred_name>
  5. </field>
  6. <field name="StreetName" grammar_ref="Street">
  7.   <desc>Street Name</desc>
  8.   <preferred_name>StreetName</preferred_name>
  9.   <preferred_name>STREET_NAME</preferred_name>
  10. </field>
  11. <field name="HouseNumber" grammar_ref="House">
  12.   <desc>House Number</desc>
  13.   <preferred_name>HouseNumber</preferred_name>
  14.   <preferred_name>HOUSE_NUMBER</preferred_name>
  15. </field>

(I kept User_fld since it’s not required and I just don’t know what it does)

Next <index> this I decided to copy+change from SingleAddress, this defines how the index of the data is going to be built:

  1. <index>
  2.   <dictionary ref="House"/>
  3.   <dictionary ref="StreetName" search="true"/>
  4.   <dictionary ref="City" search="true"/>
  5.   <dictionary ref="ID" type="ids"/>
  6.   <relationship>
  7.     <field_ref ref="ZIP"/>
  8.   </relationship>
  9.   <reverse_relationship>
  10.     <field_ref ref="House"/>
  11.     <field_ref ref="StreetName"/>
  12.     <field_ref ref="City"/>
  13.     <field_ref ref="ID"/>
  14.     <field_ref ref="User_fld"/>
  15.   </reverse_relationship>
  16. </index>

Outputs, hope it works:

  1. <outputs>
  2.   <output ref="Shape" type="geometry"/>
  3.   <output component="Status" candidate_mode="false" length="1"/>
  4.   <output component="Score" type="float" decimal_digits="2"/>
  5.   <output name="Match_addr" length="100">
  6.     <format>
  7.       <field_value ref="House"/>
  8.       <field_value ref="StreetName"/>
  9.       <field_value ref="City"/>
  10.     </format>
  11.   </output>
  12.   <output name="Ref_ID" ref="ID" type="fromdata" selector="WriteReferenceIDField"/>
  13.   <output ref="User_fld" type="string" length="120" selector="WriteAdditionalOutputFields" />
  14.   <output component="Match_time" type="float" selector="ShowElapsedTime"/>
  15. </outputs>

Reverse GeoCoding, I just copy changed this to my needs:

  1. <reverse_geocoding>
  2.   <reverse_geocoding_method name="Address">
  3.     <outputs>
  4.       <output ref="Shape" type="geometry" />
  5.       <output ref="ZIP" length="5" />
  6.       <output component="Match_time" type="float" selector="ShowElapsedTime"/>
  7.     </outputs>
  8.   </reverse_geocoding_method>
  9. </reverse_geocoding>

Result (cross my fingers and hope it works):

  1. <reverse_geocoding>
  2.   <reverse_geocoding_method name="Address">
  3.     <outputs>
  4.       <output ref="Shape" type="geometry" />
  5.       <output ref="House" length="15" />
  6.       <output ref="StreetName" length="100" />
  7.       <output ref="City" length="100" />
  8.       <output component="Match_time" type="float" selector="ShowElapsedTime"/>
  9.     </outputs>
  10.   </reverse_geocoding_method>
  11. </reverse_geocoding>

I just commented standardization (hope it’s not needed)

Testing got me this Exception:

System.InvalidCastException was unhandled by user code
  Message=Unable to cast COM object of type 'System.__ComObject' to interface type 'ESRI.ArcGIS.Location.IReferenceDataTables'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{600A5898-DDC1-11D3-9F74-00C04F8ED1C4}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).

 

 

Testing

Testing the changes: with ArcCatalog

TODO: I don’t really have ArcCatalog…

Testing the changes: with ArcObjects

I wrote a post about it and it can be found here.

 

TODO: add a link to the introduction

TODO: add a link to the arcObjects testing

TODO: Fix introduction post

Resources:

Customizing ArcGIS 10 locators (An Esri Geocoding Technical Paper)

Keywords: ESRI, ArcGis Server, geocode, geocoding, address locator