9

I am currently working on a project where when user loads up their map (mxd) into our system, we create several custom featuerlayers for them. My problem is though, I have no idea how to check if I have ALREADY created those layers already (say user loads up mxd, layers created, save, re-load the mxd, should verify if layers already exists).

Is there a Unique Id for a FeatuerLayerClass in ArcEngine10, there are OIDName, and ObjectClassID in FeatureLayerClass.FeatureClass, but those don't seem to work (cannot assign ObjectClassId, and want to use UniqueId for OIDName)?

I created my layer as featurelayerclass business object like this.

Code:

    /// <summary>
    ///     Unique Route LayerId
    /// </summary>
    public static Guid RouteFeatureLayerId
    {
        get { return Guid.Parse("ba25a332-0e48-4ce5-a4c5-38dc36c0700c"); }
    }

    /// <summary>
    ///     Feature class that stores info on the routes
    /// </summary>
    public FeatureLayerClass RouteFeatureLayer
    {
        get
        {
            if (_routeFeatureClass == null)
            {
                IPropertySet property = new PropertySetClass();
                property.SetProperty("Id", RouteFeatureLayerId);

                _routeFeatureClass = new FeatureLayerClass();
                _routeFeatureClass.FeatureClass = CreateFeatureClass(Workspace, null, ShapeType.Polylines.ToString(), CreateFields(ShapeType.Polylines, FeatureLayerType.Routes), null, null, "");
                _routeFeatureClass.Name = "Routes";
                _routeFeatureClass.Visible = true;
                _routeFeatureClass.Cached = true;
                _routeFeatureClass.AddExtension(property);
                CustomLayers.Add(_routeFeatureClass); 

            }

            return _routeFeatureClass;
        }
        set
        {
            _routeFeatureClass = value;
        }
    }

Creating workspace

    /// <summary>
    ///     Create a workspace for the shapefile or geodatabase
    /// </summary>
private IWorkspace CreateWorkspace(string workspaceType, string workspaceDirectory)
{
    Type factoryType = null;
    IWorkspaceFactory workspaceFactory = null;

    switch (workspaceType)
    {
        case "Shapefile":
            // Instantiate a Shapefile workspace factory
            factoryType = Type.GetTypeFromProgID("esriDataSourcesFile.ShapefileWorkspaceFactory");
            break;
        case "PersonalGeodatabase":
            // Instantiate an Access workspace factory
            factoryType = Type.GetTypeFromProgID("esriDataSourcesGDB.AccessWorkspaceFactory");
            break;
        case "FileGeodatabase":
            // Instantiate a file geodatabase workspace factory
            factoryType = Type.GetTypeFromProgID("esriDataSourcesGDB.FileGDBWorkspaceFactory");
            break;
    }

    workspaceFactory = (IWorkspaceFactory)Activator.CreateInstance(factoryType);

    //Create a directory hierarchy to seperate out datasets created for Points, Polylines, and Polygons
    Directory.CreateDirectory(workspaceDirectory);

    IWorkspaceName workspaceName = workspaceFactory.Create(workspaceDirectory + "\\", workspaceType, null, 0);
    IName Name = (IName)workspaceName;
    IWorkspace workspace = (IWorkspace)(Name.Open());
    return workspace;

}

Creating FeatureClass

        /// <summary>
        ///     Helper to create a Feature Class.
        /// </summary>
        private IFeatureClass CreateFeatureClass(IWorkspace workspace, IFeatureDataset featureDataset, string featureClassName, IFields fields, ESRI.ArcGIS.esriSystem.UID CLSID, ESRI.ArcGIS.esriSystem.UID CLSEXT, string configKeyword)
        {
            IFeatureClass featureClass = null;
            IFeatureWorkspace featureWorkspace = (IFeatureWorkspace)workspace; // Explicit Cast
            string shapeFieldName = String.Empty;

            try
            {
                if (featureClassName == "")
                {
                    return null; // name was not passed in
                }
                //else if (((IWorkspace2)workspace).get_NameExists(esriDatasetType.esriDTFeatureClass, featureClassName))
                //{
                //    featureClass = featureWorkspace.OpenFeatureClass(featureClassName); // feature class with that name already exists
                //    return featureClass;
                //}

                // assign the class id value if not assigned
                if (CLSID == null)
                {
                    CLSID = new ESRI.ArcGIS.esriSystem.UIDClass();
                    CLSID.Value = "esriGeoDatabase.Feature";
                }

                // locate the shape field
                for (Int32 j = 0; j < fields.FieldCount; j++)
                {
                    if (fields.get_Field(j).Type == esriFieldType.esriFieldTypeGeometry)
                    {
                        shapeFieldName = fields.get_Field(j).Name;
                    }
                }

                // finally create and return the feature class
                if (featureDataset == null)
                {
                    // if no feature dataset passed in, create at the workspace level
                    featureClass = featureWorkspace.CreateFeatureClass(featureClassName, fields, CLSID, CLSEXT, esriFeatureType.esriFTSimple, shapeFieldName, configKeyword);
                }
                else
                {
                    featureClass = featureDataset.CreateFeatureClass(featureClassName, fields, CLSID, CLSEXT, esriFeatureType.esriFTSimple, shapeFieldName, configKeyword);
                }
            }
            catch (Exception ex)
            {
                Debug.Assert(false, ex.ToString());
                Logger.Log.Debug(ex);
            }
            return featureClass;

        }

Code to get layer

            /// <summary>
            ///     Finds the layer
            /// </summary>
            /// <returns>the subcatchment layer</returns>
            private IGeoFeatureLayer GetLayer(FeatureLayerClass featureLayer)
            {
                IGeoFeatureLayer layer = null;
                ILayerExtensions layerExtension;

                for (int x = 0; x < MapControl.LayerCount; x++)
                {
                    layerExtension = ((ILayerExtensions)MapControl.get_Layer(x));

                    if (featureLayer.ExtensionCount > 0 && layerExtension.ExtensionCount > 0 &&
                        layerExtension.get_Extension(0) is PropertySetClass &&
                        featureLayer.get_Extension(0) is PropertySetClass &&
                        ((PropertySetClass)layerExtension.get_Extension(0)).GetProperty("Id") == ((PropertySetClass)featureLayer.get_Extension(0)).GetProperty("Id"))
                    {
                        layer = MapControl.get_Layer(x) as IGeoFeatureLayer;
                        break;
                    }
                }

                return layer;
            }

Thanks and Regards, Kevin

Hornbydd
  • 43,380
  • 5
  • 41
  • 81
Kev84
  • 860
  • 12
  • 22

1 Answers1

7

Feature classes and object classes do have their ids, which are unique within a single geodatabase. This very often satisfies most scenarios similar to yours.

If you cannot uniquely identify a layer based on its feature class, you can leverage layer extensions to store arbitrary data with the layer.

A layer extension can be added to a layer via ILayerExtensions interface. Now, there is no common interface for layer extensions, but they typically implement some persistence through IPersistStream. Your layer extension would not do anything special but store some data by which you will uniquely identify your added layer.

So your task would be as follows:

  • Create a COM class which would store your flag (or some kind of generated Id). Implement IPersistStream for this class. EDIT: you can easily use a PropertySet as the layer extension object, instead of creating your own class.
  • When you are adding a layer, loop through all the layers in the map and check if any of them has your layer extension assigned, with the stored data you expect.
  • If that's the case, do not add the layer as it is already present.
  • If not, add the layer, and add an instance of your layer extension to it via ILayerExtensions.

I had a very similar problem and layers extensions turned out to be the best fit.

EDIT: below I post some code for a helper static class which allows you to quickly work with properties set inside a propertyset stored in the layer extension (.NET 3.5 or higher required). It takes care of accessing the extension object and creating it if not already assigned to the layer. It's used like this:

        // 1) is a particular property ("MY.KEY") set on a layer?
        var isPropertySet = PropertySetLayerExtensionHelper.ExtensionPropertySetContainsKey(layer, "MY.KEY");

        // 2) set a property with a value on the layer:
        PropertySetLayerExtensionHelper.ExtensionPropertySetSetValueForKey(layer, "MY.KEY", "SomeValue");

        // 3) retrieve a value for the given key stored at some point before:
        var value = PropertySetLayerExtensionHelper.ExtensionPropertySetGetValueForKey(layer, "MY.KEY");

Instead of "SomeValue" you will probably generate and store some kind of layer identifier in there.

Here's the full source code for the PropertySetLayerExtensionHelper class:

public static class PropertySetLayerExtensionHelper
{
    /// <summary>
    /// Returns whether the property set stored in the layer extensions contains a value for the given key.
    /// </summary>
    /// <param name="layer">The layer.</param>
    /// <param name="key">The key.</param>
    /// <returns>Whether the property set stored in the layer extensions contains a value for the given key.</returns>
    public static bool ExtensionPropertySetContainsKey(ILayer layer, string key)
    {
        if (layer == null) throw new ArgumentNullException("layer");
        if (key == null) throw new ArgumentNullException("key");

        var propertySet = GetPropertySetInLayerExtension(layer);
        return propertySet != null
            && propertySet.AsEnumerable().Any(pair => pair.Key.Equals(key, StringComparison.OrdinalIgnoreCase));
    }

    /// <summary>
    /// Returns the value for the given key from the property set stored in the layer extension or <b>null</b>
    /// if no such key is present.
    /// </summary>
    /// <param name="layer">The layer.</param>
    /// <param name="key">The key.</param>
    /// <returns>The value for the given key from the property set stored in the layer extension or <b>null</b>
    /// if no such key is present.</returns>
    public static object ExtensionPropertySetGetValueForKey(ILayer layer, string key)
    {
        if (layer == null) throw new ArgumentNullException("layer");
        if (key == null) throw new ArgumentNullException("key");

        var propertySet = GetPropertySetInLayerExtension(layer);
        if (propertySet == null) return null;

        return propertySet.AsEnumerable()
            .Where(p => p.Key.Equals(key, StringComparison.OrdinalIgnoreCase))
            .Select(p => p.Value)
            .FirstOrDefault();
    }

    /// <summary>
    /// Sets the value for the given key in the property set stored in a layer extension. If there is
    /// no property set among the layer's extensions, it is created and assigned to the layer.
    /// </summary>
    /// <param name="layer">The layer.</param>
    /// <param name="key">The key.</param>
    /// <param name="value">The value for the given key.</param>
    public static void ExtensionPropertySetSetValueForKey(ILayer layer, string key, object value)
    {
        if (layer == null) throw new ArgumentNullException("layer");
        if (key == null) throw new ArgumentNullException("key");

        var propertySet = GetOrCreatePropertySetInLayerExtension(layer);
        if (propertySet == null)
        {
            throw new InvalidOperationException("The given layer does not support layer extensions.");
        }

        propertySet.SetProperty(key, value);
    }

    /// <summary>
    /// Returns a property set from a layer extension.
    /// </summary>
    /// <param name="layer">The layer.</param>
    /// <returns>A property set from a layer extension.</returns>
    public static IPropertySet GetPropertySetInLayerExtension(ILayer layer)
    {
        if (layer == null) throw new ArgumentNullException("layer");

        var layerExtensions = layer as ILayerExtensions;
        if (layerExtensions == null)
        {
            return null;
        }

        var propertySetExtension = layerExtensions.AsEnumerable().OfType<IPropertySet>().FirstOrDefault();
        return propertySetExtension;
    }

    /// <summary>
    /// Returns a property set from a layer extension. If not set on the layer,
    /// the property set is created and assigned to it.
    /// </summary>
    /// <param name="layer">The layer.</param>
    /// <returns>A property set from a layer extension.</returns>
    public static IPropertySet GetOrCreatePropertySetInLayerExtension(ILayer layer)
    {
        if (layer == null) throw new ArgumentNullException("layer");

        var propertySet = GetPropertySetInLayerExtension(layer);
        if (propertySet != null)
        {
            return propertySet;
        }

        var layerExtensions = layer as ILayerExtensions;
        if (layerExtensions == null)
        {
            return null;
        }

        propertySet = new PropertySetClass();
        layerExtensions.AddExtension(propertySet);
        return propertySet;
    }

    private static IEnumerable<object> AsEnumerable(this ILayerExtensions layerExtensions)
    {
        if (layerExtensions == null) throw new ArgumentNullException("layerExtensions");

        for (var i = 0; i < layerExtensions.ExtensionCount; i++)
        {
            yield return layerExtensions.get_Extension(i);
        }
    }

    private static IEnumerable<KeyValuePair<string, object>> AsEnumerable(this IPropertySet propertySet)
    {
        if (propertySet == null) throw new ArgumentNullException("propertySet");
        if (propertySet.Count == 0) yield break;

        object names;
        object values;

        propertySet.GetAllProperties(out names, out values);

        var nameArray = (string[])names;
        var valueArray = (object[])values;

        for (var i = 0; i < nameArray.Length; i++)
        {
            yield return new KeyValuePair<string, object>(nameArray[i], valueArray[i]);
        }
    }
}
Hornbydd
  • 43,380
  • 5
  • 41
  • 81
Petr Krebs
  • 10,291
  • 1
  • 22
  • 33
  • Sometimes, you can get away with just storing something like an IPropertySet with a special key in the ILayerExtension. Since this is a common "trick", developers should check for the existence of an IPropertySet before adding one. – James Schek Mar 23 '11 at 22:17
  • @James: good tip, I'll update the answer. – Petr Krebs Mar 23 '11 at 22:18
  • +1 last time I checked Esri only honors IPersistStream - not IPersistVariant - for layer extensions. I'm not sure why. I've asked IPersistVariant support as an enhance, but not sure it was ever implemented. Anyway, you might want to use Richie Carmichael's IPersistStream post for sample code. – Kirk Kuykendall Mar 23 '11 at 22:21
  • The thing that drives me crazy about using IPersistStream is that it doesn't work with Add-In's. The object you add to the ILayerExtensions must be COM CoCreatable. – James Schek Mar 23 '11 at 22:24
  • @Kirk: that's right, I remember not being able to implement layer extension persistence in VB due to that. Thanks for the correction. – Petr Krebs Mar 23 '11 at 22:24
  • @James Schek: misread the question. Sorry for the trouble :P – George Silva Mar 23 '11 at 22:26
  • @Kirk: the link you posted presents a solution which is a huge overkill in my opinion. The easiest way, instead of using .NET serialization with the possible need to alter type resolution, is to create an instance of VariantStreamIO class and hook it to the underlying stream through IVariantStreamIO.Stream. That way you can implement the body of IPersistStream Load/Save methods using IVariantStream interface. – Petr Krebs Mar 23 '11 at 22:31
  • @Kirk: ... assuming you do not need to persist .NET non-primitive types, of course. – Petr Krebs Mar 23 '11 at 22:34
  • @Petr that sounds good. I never thoroughly understood the details of that code, but just recall that it worked. Something simpler would certainly be worth seeing. – Kirk Kuykendall Mar 23 '11 at 22:41
  • @petr see question here http://gis.stackexchange.com/questions/7652/how-can-i-implement-persistance-in-a-layer-extension – Kirk Kuykendall Mar 23 '11 at 22:48
  • Hi Guys, Thanks for the answer, I managed to add IPropertySet to my FeatuerLayerClass by using AddExtension(obj), but when I am having trouble retrieving that layer as FeatureLayerClass.

    This is how I get it

    AxMapControl.get_Layer(x)

    I can cast that as a IGeoFeatureLayer, but I can't seem to get my FeatureLayerClass back, how do I get it so that I can get my FeatureLayerClass from AxMapControl?

    Thanks

    – Kev84 Mar 24 '11 at 14:43
  • I need the featurelayerclass to get my Ipropertyset extension – Kev84 Mar 24 '11 at 15:03
  • You cannot cast a COM object which is not created by .NET runtime to the Class generated interface. From .NET it looks like any COM object and its RCW is not typed the same. The Class types can only be used when you directly create an instance of them in your .NET code. You can however cast the layer instance to any interface FeatureLayer implements, like IFeatureLayer, IGeoFeatureLayer or ILayerExtensions, which is what you need. – Petr Krebs Mar 24 '11 at 15:16
  • @kev84 The only draw-back of using a propertyset is that others apps might also be doing the same thing. Probably not a big issue, but something to keep in mind to avoid collisions. – Kirk Kuykendall Mar 24 '11 at 15:24
  • @krik yes i found the impact it did to my app now. Here's what i did. 'FeatureLayerClass.AddExtension(PropertySetClass)' after I did that, the save on my tool bar breaks, and it no longer saves my mxd file. It saves and creates the mxd file, but the mxd file contains nothing in it. Have you guys encountered something like this before? – Kev84 Mar 24 '11 at 18:13
  • @Kev: I'll try to post some code which should make things more clear. – Petr Krebs Mar 24 '11 at 18:23
  • @krik thanks, i have posted code on how i create the workspace, and featurelayer/feature class, as soon as i get rid of the line of code that adds the extension, my save works again and it saves all layers. – Kev84 Mar 24 '11 at 18:46
  • @ALL I FIGURED IT OUT..... DAMN ARCENGINE..... it was because i used GUID as my id.... change to string, save works now.... thanks all – Kev84 Mar 24 '11 at 18:54
  • well... saving works.... but when I try to get my extension like GetLayer above... layerExtension.get_Extension(0) is PropertySetClass fails where featureayer.getExtension(0) is Propertysetclass passes ...it says layerExtension.get_Extension(0) is ComObject Type – Kev84 Mar 24 '11 at 19:03
  • As I said before, you CANNOT cast COM objects this way if they are instantiated outside .NET runtime. Anyway, see the code I posted. – Petr Krebs Mar 24 '11 at 19:08
  • So i cannot use the IPropertSet interface suggested by james? I have to create my own extensionhelper class? – Kev84 Mar 24 '11 at 19:27
  • The class provides 3 methods which all use IPropertySet under the hood: 1) determine if a key is set = ExtensionPropertySetContainsKey 2) save a value under a key = ExtensionPropertySetSetValueForKey 3) read a saved value for a key = ExtensionPropertySetGetValueForKey. This is all you need and I actually simplified the API on purpose for you. In case you need to use the IPropertySet (though I do not understand which additional methods on this interface are critical to you), you can get the IPropertySet through GetPropertySetInLayerExtension and GetOrCreatePropertySetInLayerExtension. – Petr Krebs Mar 24 '11 at 19:33
  • The key thing to understand here is that the static class I posted just simplifies the details of working with the layer's property set. Do examine the code and you will see that a PropertySetClass is internally used to store and persist the values. Did you read the documentation comments for the methods? Sorry I do not believe I am able to make it more clear. The example code snippet shows how to use the three methods, which is all you need. – Petr Krebs Mar 24 '11 at 19:37
  • Thanks Petr.... your extension helper worked very well.... thanks a lot... one heck of a lesson on arc... Thanks – Kev84 Mar 24 '11 at 20:29
  • @Kev: more than glad to help. And my thanks go to James who pointed out the advantage of using a PropertySet as the extension object. – Petr Krebs Mar 24 '11 at 20:40
  • I petrk, the site just gave me a warning about this thread being too long (which I'm of course adding to with this comment) -- would it be possible to incorporate some of the response in your answer body? Thanks for the contribution! – scw Mar 24 '11 at 21:33
  • Just reading this - I take it the extra data you save, gets stored in the MXD not the actual featureclass i.e. one stored in ArcSDE? – Vidar Jan 27 '14 at 15:08
  • Yes, it gets stored in the MXD along with the layer. – Petr Krebs Jan 27 '14 at 15:16