Select to view content in your preferred language

Creating custom feature layer

2169
10
10-14-2010 07:39 AM
StephenDavis
Emerging Contributor
for reasons that I posted here: http://forums.arcgis.com/threads/14319-Clustering-fails-using-FeatureLayer-OnDemand-with-1-000-featu...

I would like to create my own feature layer that supports an on demand mode.  I have created a new class deriving from graphicslayer and put in the methods to query for new features as the map extent changes.

The problem is that I don't know of a good way to find out the map extent has changed other than responding to the extentchanged property on the map and refreshing an extent property on my custom map.  While that works, I don't want to have to tell people who use this new type of layer that they have to manually update the extent on the layer from the extent changed event or bind to the map's extent property.

Is there an exposed property or method on the graphics layer where the map makes it aware of an extent change, or a way I can bind to the map without having to make the developer do anything but put the layer in the layers collection of the map?

Does anyone know how this is done in the existing feature layer?  It seems to just know the extent, unless under the hood of the map there is some code keeping an extent property updated on the feature layer object.

Any help would be greatly appreciated.  Thanks!
Stephen
0 Kudos
10 Replies
AlexAgranov
Emerging Contributor
I'd also like to know how to do this.
0 Kudos
DaveTimmins
Deactivated User
There is a method called MapParentChanged defined on the Layer type. For FeatureLayer it hooks the ExtentChanged property of the map. Unfortunately it's internal so you can't use it for what you require.

Layer

internal virtual void MapParentChanged(Map oldValue, Map newValue)
{
}

FeatureLayer

internal override void MapParentChanged(Map oldMap, Map newMap)
{
    if (oldMap != null)
    {
        oldMap.ExtentChanged -= new EventHandler<ExtentEventArgs>(this.Map_ExtentChanged);
        oldMap.PropertyChanged -= new PropertyChangedEventHandler(this.Map_PropertyChanged);
    }
    if (newMap != null)
    {
        newMap.PropertyChanged += new PropertyChangedEventHandler(this.Map_PropertyChanged);
        newMap.ExtentChanged += new EventHandler<ExtentEventArgs>(this.Map_ExtentChanged);
    }
    if ((((newMap != null) && base.IsInitialized) && (newMap.SpatialReference != null)) && ((this.query.OutSpatialReference == null) || !this.query.OutSpatialReference.Equals(newMap.SpatialReference)))
    {
        this.Update();
    }
}

This probably doesn't really help you in terms of being able to have your custom layer without extra binding to the map but it would be worth raising the issue with esri as they may look at resolving your initial issue in a future release.
0 Kudos
BjørnarSundsbø
Deactivated User
I'm having the exact same problem. I have a custom GraphicsLayer where I need to transform the geometry of the Graphic objects to the same srs as the map. Creating a constructor or other ways of accessing the extent/spatialReference of the map for the layer is not really a preferred way of doing this.

As the Map property of Layer is internal, I do not have access to it. There are a lot of classes and properties that are internal, which makes it hard to find workarounds.

Any suggestions for getting the GraphicsLayer to display the features at the correct location?

Bjørnar
0 Kudos
DominiqueBroux
Esri Frequent Contributor
Sorry I have no idea to help you.

perhaps, as a workaround, you could create a 'SpatialReference' property in your graphicslayer and tell the user to initialize this property:o
0 Kudos
DaveTimmins
Deactivated User
Hi,

you can create behavior to attach to the Map control that listens for changes to the LayerCollection for GraphicsLayers and also for changes to the GraphicCollection of the Layer so that you can automatically project new graphics by calling a GeometryService.

Cheers,
0 Kudos
BjørnarSundsbø
Deactivated User
Dave,

Do you by any chance have a small sample you can provide that does this? If you do, that would be great.

Based on your suggestion, would such behavior modify the geometries of the Graphic objects, or add logic "on top" of the existing layer?

Bjørnar
0 Kudos
DaveTimmins
Deactivated User
Hi Bjørnar,

it modifies the geometry. You can use the following as a starting point

This is the behavior class


        protected override void OnAttached()
        {
            base.OnAttached();
            Map.Layers.LayersInitialized += MapLayersInitialized;
            Map.Layers.CollectionChanged += LayersCollectionChanged;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            Map.Layers.LayersInitialized -= MapLayersInitialized;
            Map.Layers.CollectionChanged -= LayersCollectionChanged;
        }

        private void MapLayersInitialized(object sender, EventArgs args)
        {
            var layers = sender as LayerCollection;
            if (layers == null || !layers.Any())
                return;

            foreach (var layer in layers.Where(layer => layer is GraphicsLayer && layer.IsInitialized && layer.InitializationFailure == null))
                Project((GraphicsLayer)layer);
        }

        private void LayersCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if ((e.Action == NotifyCollectionChangedAction.Add || e.Action == NotifyCollectionChangedAction.Replace) && e.NewItems.Count > 0)
            {
                foreach (var layer in
                    e.NewItems.Cast<Layer>().Where(layer => layer is GraphicsLayer && layer.IsInitialized && layer.InitializationFailure == null
                        && !SpatialReference.AreEqual(Map.SpatialReference, layer.SpatialReference, false)))
                {
                    Project((GraphicsLayer)layer);
                }
            }
        }

        private void Project(GraphicsLayer graphicsLayer)
        {
            if (graphicsLayer.Graphics.Any())
            {
                Project(graphicsLayer.Graphics);
            }
            else
            {
                graphicsLayer.Graphics.CollectionChanged += (s, a) =>
                                                                {
                                                                    if (a.Action == NotifyCollectionChangedAction.Add && a.NewItems.Count > 0)
                                                                    {
                                                                        Project(a.NewItems.Cast<Graphic>().ToList());
                                                                    }
                                                                };
            }
        }

        private void Project(IList<Graphic> graphics)
        {
            if (graphics.Any() && graphics.First().Geometry != null 
                && SpatialReference.AreEqual(Map.SpatialReference, graphics.First().Geometry.SpatialReference, true))
                return;

            GeometryService geometryService = new GeometryService("url for service");            geometryService.ProjectCompleted += GeometryServiceProjectCompleted;            geometryService.ProjectAsync(graphics, Map.SpatialReference, graphics);
        }

private static void GeometryServiceProjectCompleted(object sender, GraphicsEventArgs e)
        {
            var existingGraphics = e.UserState as IList<Graphic>;
            if (existingGraphics == null || existingGraphics.Count != e.Results.Count)
                throw new InvalidOperationException("The results from the project do not match the values passed in to the operation.");

            for (var i = 0; i < e.Results.Count; i++)
            {
                Graphic graphic = e.Results;

                if (graphic.Geometry.Extent != null)
                {
                    Graphic graphicToDisplay = existingGraphics;
                    graphicToDisplay.Geometry = graphic.Geometry;
                }
            }
        }



Cheers,
0 Kudos
BjørnarSundsbø
Deactivated User
Dave,

Thanks. I think I will use some of that concept to set the SpatialReference of the layers to match the SR of the Map, and do the transform inside of the layer. Since the layer contains dynamic features, I will need to transform the geometries on every update as well, and excessive propertychanged events might become a bit "complex" and unreadable in lack of a better word.


Bjørnar
0 Kudos
BjørnarSundsbø
Deactivated User
Setting SpatialReference from the outside does not work, as the setter is protected. I'm very frustrated over the Map property being internal. I expect alot of custom implementations might need to get access to the maps spatial reference (and other information as well).

Currently I have no workaround, but I'm looking into it. I do not wish to add special properties to my custom layer to be able to solve the problem. An attached property on each layer binding to the map is a possible solution, though not very satisfactory.

Bjørnar
0 Kudos