ThinkGeo.com    |     Documentation    |     Premium Support

Tag property issue

Hello ThinkGeo,


I have been trying to "tag" my features with certain objects I use for future reference. The code I do this follows. My issue is that the tag dissapears when I try to call the same sub again. Am i missing something or the use of tag is not intedned to be the one I try to use it for? Also what is the difference between TAG of a feature and TAG of a shape?


Kind Regards


Yiannis


Public Sub GetShipType()
 Console.WriteLine("Here we are")
 Try
 aTrackLayer.FeatureSource.BeginTransaction()
 Dim features = aTrackLayer.FeatureSource.GetAllFeatures(ReturningColumnsType.AllColumns)
 For Each aFeature In features
 Console.WriteLine(aFeature.ColumnValues("id_object"))
 Dim aVessel = shipsArray.Find(AddressOf New VesselMatcher(Guid.Parse(aFeature.ColumnValues("id_object"))).Predicate)
 If aVessel Is Nothing Then
 aVessel = Vessel.GetVessel(Guid.Parse(aFeature.ColumnValues("id_object")), GetDBConnection())
 shipsArray.Add(aVessel)
 End If
 Dim aTrip = aVessel.GetTripDetails(aFeature.ColumnValues("trip_id"), DateTime.Parse(aFeature.ColumnValues("first_date")), DateTime.Parse(aFeature.ColumnValues("last_date")), False, GetDBConnection())
 aFeature.Tag = aTrip
 aTrackLayer.FeatureSource.UpdateFeature(aFeature)
 Next
 aTrackLayer.FeatureSource.CommitTransaction()
 features = aTrackLayer.FeatureSource.GetAllFeatures(ReturningColumnsType.AllColumns)
 Console.WriteLine("Done")
 Catch ex As Exception
 Console.WriteLine("opps")
 'aTrackLayer.EditTools.RollbackTransaction()
 aTrackLayer.FeatureSource.RollbackTransaction()
 End Try
 End Sub



Yiannis, 
  
 I’ve run into this one too.  The FeatureSource GetAllFeatures and GetFeatures____ methods return near clones of the source features, but the cloning process does not copy the Tag value.  This, in effect, makes the Tag property pretty much useless.  You’re pretty much stuck ensuring that your features have unique Id values, then retrieving the objects you might have placed in the tags into a separate collection, such as a dictionary keyed on the the feature Id values. 
  
 Of course, we can also hope that this behaviour is not by design and that it might be changed in a future version… 
  
 Nicole

Nicole,



thank you very much for this. I though that something like this was going on although I spend quite a few hours trying to find a way around based on this: msdn.microsoft.com/en-us/lib...80%29.aspx


Since the feature is a structure and this error, as described in MSDN, appears if you try to assign an object to the TAG when you modify my code and instead of "for each" you use a "for i=0 to features.count - 1". Maybe it is not a bad thing that it is not working after all since a propper OO implementation would rather require to have the "feature" or its ID as a property of my "trip object".


Thanks again


Yiannis



Yiannis & Nicole,
 
Thanks for your post and sharing.
 
I tried the idea and think it is working properly, am I misunderstanding anything?
 
Following code shows how to create an InmemoryFeatureLayer and try to add some features into it:
winformsMap1.MapUnit = GeographyUnit.DecimalDegree;
 
winformsMap1.CurrentExtent = new RectangleShape(0, 100, 100, 0);
winformsMap1.BackgroundOverlay.BackgroundBrush = new GeoSolidBrush(GeoColor.StandardColors.White);
 
InMemoryFeatureLayer inMemoryLayer = new InMemoryFeatureLayer();
Feature polygonFeature = new Feature(BaseShape.CreateShapeFromWellKnownData("POLYGON((10 60,40 70,30 85, 10 60))"));
polygonFeature.Tag = "This is test tag for Polygon";
inMemoryLayer.InternalFeatures.Add("Polygon", polygonFeature);
inMemoryLayer.InternalFeatures.Add("Multipoint", new Feature(BaseShape.CreateShapeFromWellKnownData("MULTIPOINT(10 20, 30 20,40 20, 10 30, 30 30, 40 30)")));
 
Feature lineFeature = new Feature(BaseShape.CreateShapeFromWellKnownData("LINESTRING(60 60, 70 70,75 60, 80 70, 85 60,95 80)"));
lineFeature.Tag = "This is test tag for Line";
inMemoryLayer.InternalFeatures.Add("Line", lineFeature);
inMemoryLayer.InternalFeatures.Add("Rectangle", new Feature(new RectangleShape(65, 30, 95, 15)));
 
inMemoryLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle.FillSolidBrush.Color = GeoColor.FromArgb(100, GeoColor.StandardColors.RoyalBlue);
inMemoryLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle.OutlinePen.Color = GeoColor.StandardColors.Blue;
inMemoryLayer.ZoomLevelSet.ZoomLevel01.DefaultLineStyle.OuterPen = new GeoPen(GeoColor.FromArgb(200, GeoColor.StandardColors.Red), 5);
inMemoryLayer.ZoomLevelSet.ZoomLevel01.DefaultPointStyle.SymbolPen = new GeoPen(GeoColor.FromArgb(255, GeoColor.StandardColors.Green), 8);
inMemoryLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
 
LayerOverlay staticOverlay = new LayerOverlay();
staticOverlay.Layers.Add("InMemoryFeatureLayer", inMemoryLayer);
winformsMap1.Overlays.Add("InMemoryOverlay", staticOverlay);
 
winformsMap1.Refresh();
 
 
 
Following code shows how to do query against it , and from the results the tag information is not lost, please take a try if you are interested:
InMemoryFeatureLayer inMemoryLayer = (InMemoryFeatureLayer)winformsMap1.FindFeatureLayer("InMemoryFeatureLayer");
 
inMemoryLayer.Open();
Collection<Feature> allFeatures = inMemoryLayer.QueryTools.GetAllFeatures(ReturningColumnsType.AllColumns);
Collection<Feature> outsiteFeatures = inMemoryLayer.QueryTools.GetFeaturesOutsideBoundingBox(new RectangleShape(0, 10, 10, 0), ReturningColumnsType.AllColumns);
 
Any more questions please feel free to let me know.
 
Thanks.
 
Yale

Yale, 
  
 Which version are you using? 
  
 Nicole

Yale, 
  
 have you tried though to Update the tag? Not sure if it is working when you first create it as a feature but my Layer is already there with features that have tag = nothing. Then I run the code I attached to update the tag.  
  
 Kind Regards 
 Yiannis

Yiannis, 
  
 Thanks for your post and feedback. 
  
 If I am not misunderstanding anything, what you are trying to do is to set tags to the features queried out and then attempts to set tags to the features in the layer? If so, I am afraid it will fail as  
 Feature is a structure object which will be immune to the original features in the layer once copied for returning back.  
  
 Any more questions please feel free to let me know. 
  
 Thanks. 
  
 Yale 


Dear Yale, 
  
 That settles it then :) I would love though to be able to do this and this is probably the point of the discussion. For ThinkGeo choosing a structure instead of a class for the Feature there must be some reason (speed?) and if so, the TAG property is not supposed to be used like the way Nicole and I tried to. Which raises the final questions: 
  
 1. What exactly is the purpose of the TAG 
 2. What is the difference in logic between a Feature tag and the tag of its shape. 
  
 Regards 
 Yiannis

Yale, 
  
 Feature is a value type, but it is not immutable.  The Tag property has a setter, and there is no constructor overload that takes an object argument that might be assigned to the Tag property, so the Tag value can and must be assigned after construction of a Feature instance.  The central problem here is that the method(s) used to copy a feature for return by the GetAllFeatures and GetFeatures____ methods do not all (or at least did not all) copy the Tag value, so the only way to retrieve a Tag value was to read it from the original feature, which is of very limited utility. 
  
 I am still curious to know which version you used for the test in which you could read the Tag value from a feature returned from GetAllFeatures and GetFeaturesOutsideBoundingBox.  Also, have you tested GetFeatureById and GetFeaturesInsideBoundingBox?  IIRC, those were the ones that were causing problems for me when I tried to use tags. 
  
 Nicole

Ionannis,


Thanks for your post and questions.
 
1)      The purpose of Tag is to be used transfer some user information like customized object.
2)      When we set the tag for the shape and then assign the shape to the feature, the tag will be assigned to the feature too.
 
Any more questions please feel free to let me know.
 
Thanks.
 
Yale

Nicole,


Thanks for your post and questions.
 
I have verified that the Tag in the APIs GetFeatureById and GetFeaturesInsideBoundingBox work properly as expected and I use the latest version, if you want, you can get it from the latest 5.0 public release.
 
Any more questions please feel free to let me know.
 
Thanks.
 
Yale

Yale, 
  
 since you mention version 5.0, shall I go on and use it for my release code or is it introducing major changes that will delay my release? 
  
 Regards 
 Yiannis

Yiannis,


Thanks for your posts and response.
 
Please go ahead to use the version 5.0.0.0 which is our latest public release, following shows its change list, please take a look if you are interested:
wiki.thinkgeo.com/wiki/Map_S...Change_Log
 
Any more questions please feel free to let me know.
 
Thanks.
 
Yale

Hi again,


Eventually this "Feature/tag" issue drives me mad as I get more issues when I am trying to do something more structured. Here is the problem again:


Class A inherits from MultilineShape


Class A overloads the GetFeature() Function as follows:


Public Overlads Function GetFeature() As ThinkGeo.MapSuite.Core.Feature


Dim someColumns as New Dictionary(of String, String)


someColumns.add("ID", idVar) 'where idVar private member variable of class A


Dim myFeature = MyBase.GetFeature(someColumns)

myFeature.Tag = Me


return myFeature


End Function


Now guess what!!! When I try to query the layer that these features are attached to like this:


Dim test1 = aLayer.QueryTools.GetFeaturesIntersecting(searchArea, ReturningColumnsType.AllColumns)


my features in test1 have CLASS A as the tag, as expected, but no columns at all! However if I do:


aLayer.InternalFeatures.Item(0) the item at 0 has all the columns and the tag as well.


Do I need to implement something more in my subclass to get the columns as well? Or am I doing something fundamentally wrong?


Kind Regards


Yiannis



 Yiannis,


Thanks for your post and question.


I think the problem is probably from the inherited class, as I am testing the following code snippet, it works as expected. You can take a try and check the features returned back in the button click event.






private void AddFeaturesFromAFeatureLayer_Load(object sender, EventArgs e)
        {
            winformsMap1.MapUnit = GeographyUnit.DecimalDegree;

            winformsMap1.CurrentExtent = new RectangleShape(0, 100, 100, 0);
            winformsMap1.BackgroundOverlay.BackgroundBrush = new GeoSolidBrush(GeoColor.StandardColors.White);

            InMemoryFeatureLayer inMemoryLayer = new InMemoryFeatureLayer();
            Feature polygonFeature = new Feature(BaseShape.CreateShapeFromWellKnownData("POLYGON((10 60,40 70,30 85, 10 60))"));
            polygonFeature.ColumnValues.Add("ColumnKey1", "ColumnValue1");
            polygonFeature.ColumnValues.Add("ColumnKey2", "ColumnValue2");
            polygonFeature.ColumnValues.Add("ColumnKey3", "ColumnValue3");
            polygonFeature.Tag = polygonFeature;
            inMemoryLayer.InternalFeatures.Add("Polygon", polygonFeature);
            inMemoryLayer.InternalFeatures.Add("Multipoint", new Feature(BaseShape.CreateShapeFromWellKnownData("MULTIPOINT(10 20, 30 20,40 20, 10 30, 30 30, 40 30)")));

            BaseShape baseShape1= BaseShape.CreateShapeFromWellKnownData("LINESTRING(60 60, 70 70,75 60, 80 70, 85 60,95 80)");
            baseShape1.Id = "Line";
            Feature lineFeature = new Feature(baseShape1);
            lineFeature.Tag = "This is test tag for Line";
            inMemoryLayer.InternalFeatures.Add("Line", lineFeature);
            inMemoryLayer.InternalFeatures.Add("Rectangle", new Feature(new RectangleShape(65, 30, 95, 15)));

            inMemoryLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle.FillSolidBrush.Color = GeoColor.FromArgb(100, GeoColor.StandardColors.RoyalBlue);
            inMemoryLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle.OutlinePen.Color = GeoColor.StandardColors.Blue;
            inMemoryLayer.ZoomLevelSet.ZoomLevel01.DefaultLineStyle.OuterPen = new GeoPen(GeoColor.FromArgb(200, GeoColor.StandardColors.Red), 5);
            inMemoryLayer.ZoomLevelSet.ZoomLevel01.DefaultPointStyle.SymbolPen = new GeoPen(GeoColor.FromArgb(255, GeoColor.StandardColors.Green), 8);
            inMemoryLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

            LayerOverlay staticOverlay = new LayerOverlay();
            staticOverlay.Layers.Add("InMemoryFeatureLayer", inMemoryLayer);
            winformsMap1.Overlays.Add("InMemoryOverlay", staticOverlay);

            
            winformsMap1.Refresh();
        }

        private void addAFeatureButton_Click(object sender, EventArgs e)
        {
            InMemoryFeatureLayer inMemoryLayer = (InMemoryFeatureLayer)winformsMap1.FindFeatureLayer("InMemoryFeatureLayer");

            inMemoryLayer.Open();
            Collection<Feature> allFeatures = inMemoryLayer.QueryTools.GetAllFeatures(ReturningColumnsType.AllColumns);

            Collection<Feature> outsiteFeatures = inMemoryLayer.QueryTools.GetFeaturesOutsideBoundingBox(new RectangleShape(0, 10, 10, 0), ReturningColumnsType.AllColumns);
            Feature lineFeature = inMemoryLayer.QueryTools.GetFeatureById("Line", ReturningColumnsType.AllColumns);
            Collection<Feature> insideFeatures = inMemoryLayer.QueryTools.GetFeaturesInsideBoundingBox(new RectangleShape(double.MinValue, double.MaxValue, double.MaxValue, double.MinValue), ReturningColumnsType.AllColumns);

            BaseShape shape = new EllipseShape(new PointShape(50, 50), 10, 10);
            shape.Id = "Ellipse";

            inMemoryLayer.Open();
            inMemoryLayer.EditTools.BeginTransaction();
            inMemoryLayer.EditTools.Add(new Feature(shape));
            inMemoryLayer.EditTools.CommitTransaction();
            inMemoryLayer.Close();

            winformsMap1.Refresh(winformsMap1.Overlays["InMemoryOverlay"]);
        }

Any more questions please feel free to let me know.


Thanks.


 


 



Hi Clint, 
  
 Thank you for this. However with all due respect you are not helping me a lot. What you demonstrate me is a straight usage of the API that we know it works and we have done it countless time. My question I think was a little bit more advanced and had to do with inheritance. To put it in a more black/white context, either there is a bug here that disallow us to inherit thus simplifying our code or as I said earlier I am missing some “overload” in my Class A that inherits from the MultiLine.  
  
 So what I suggest is try to create a Class A that inherits from MultilineShape or PointShape etc, overload the GetFeature and do in there the: 
 polygonFeature.ColumnValues.Add(“ColumnKey1”, “ColumnValue1”); 
 polygonFeature.ColumnValues.Add(“ColumnKey2”, “ColumnValue2”); 
 polygonFeature.ColumnValues.Add(“ColumnKey3”, “ColumnValue3”); 
 polygonFeature.tag = this (or Me) 
 return polygonFeature 
  
 The reason that you may wish to do something like this is very simple. Imagine that you have 2 different objects with their own (“ColumnKey1”, “ColumnValue1”) properties. They are all, let’s say Multiline shapes BUT one is a Line that a car is creating as it travels and one is a line that expresses the road that the car is travelling on. I am sure you understand that COLUMNS/properties of these lines are different. But let’s say we want to show them all on the same layer. Isn’t it simpler every object to return it’s own columns when asked rather than fighting with a plethora of carFeature.ColumnValues.Add(“ColumnKey1”, “ColumnValue1”); roadFeature.ColumnValues.Add(“ColumnKey1”, “ColumnValue1”); … in the main body of our program? 
  
 Anyway, since my feature (as is created by the subclass) seems to retains in this instance the tag to the object, I have manage to bypass all this code by using the tag and accessing directly the  my object without having to remember columns names etc. However as I said, the issue remains as a “why not”.  
  
 Kind Regards 
 Yiannis

Thanks Yiannis, 
  
 We will look into it and keep you informed on any updates.  
  
 Thanks, 
 Lee

Guys, 
  
   I have a good idea about what you want to do will propose a solution along with some background and maybe even some more questions.   
  
   First off overriding the method will not work, as you found.  The reason is that the method GetFeature is not Virtual in our framework.  This mean that if you override it then your override is only effective if the class is cast to your type of class.  If your CarLineShape, for example, is cast to BaseShape then the method used will be the BaseShape version and not your special version.  Marking a method as Virtual slows down the method call so we only reserve it for places we have a defines scenarios for extending the method.  Of course we cannot foresee every scenario so many times we an extension point as customs bring us good scenarios. 
  
   In the case of GetFeature the method was designed as a conversion function and may not be the best place to extend the method.  We added the Tag property a few version back due to customer suggestions that it would make certain tasks easier.  I was reluctant as Microsoft has openly denounced the use of Tag like properties in the past and all but tried to eradicate them after VB6.  Having said that they are handy so we added them.  I think the root of the issue stemmed from the interplay between shapes and features.  Features were designed as structures for speed and memeory optimizations.  As such they are supposed to be small and compact.  They were designed to just be used in the drawing pipeline when we read data from a FeatureSource and transfer the data to be drawn.  A Feature is just a very thin wrapper over a byte array of Well Known Binary and is really just a transport mechanism.  The shapes derived from BaseShape were supposed to be objects that were classes that were easy to manipulate geometrically speaking, meaning union, loop through points etc.  The problem we found is that you really need both in many scenarios and that they are not equal.  Couple this with the way that structures work by value and it confuses many people.  When you convert from one to another things may be lost such as the column values etc.  If I were to do it all over again I would just have one class and change it so that the class was small and that the points of the geometries were structures.  This would have made things much easier and if we ever do a major compatability break these will be the first in line.  Enough of the background below is a proposal… 
  
   I suggest we add ColumnValues to the BaseShape, and by extension all the sub shapes.  Whenever we convert to or from a feature we move of the column values.  In this was the Feature and BaseShape are truly equal and there is no loss.  In the scenario you outlines above you could add the column values in the constructor of the new shapes sub class that that would carry over perfectly to the feature.  In this way I think we do not need to make the GetFeature virtual and preserve a bit of speed there.  What do you think?  I am open to suggestion or other constructive criticism. 
  
 David 


Hi David, 
  
 Thank you for the idea and the suggestions. Sorry for been late in my answer. I think your scenario is a good one simply because should you think it logical, “the shape” i.e inherited classes have “properties” that may be revealed via columns. Then a “feature” created by a shape may revel those columns. I would even go one step beyond and suggest that this “columns” on the base shape should not be part of a constructor or part something that the user must explicitly declare. Rather than an overridable method (virtual on the baseshape maybe) that it will be the user’s responsibility to override should it is necessary. Besides when we inherit form a baseshape we do it because “our shape” will have some additional properties. This properties should be enough to populate the columns that will be returned by a “GetColumns” virtual method. The same method that the getfeature should consult to populate its columns.  
  
 Just my 2 cents 
 Kind Regards 
 Yiannis

Yiannis, 
  
 I believe we are thinking of the same thing.  Imagine you create a new shape which represents a line that a car has traveled.  The new shape would inherit from LineShape and for the example called CarLineShape.  When you override the LineShape you create a new default constructor.  In that constructor you can define the column values you want the shape to have by default.  The user will see the columns just by creating the CarLineShape even if they used the default, blank, constructor. 
  
 I don’t think we need another method that is virtual to accomplish what you want.  I may be wrong or misunderstand you.  What we are going to do is to move forward with what I have detailed above as a first step and then see if we need to go further.  Sound good? 
  
     public class CarLineShape: LineShape 
     { 
         public CarLineShape(): base() 
         { 
             //Create the columns here 
         } 
     } 
  
 David