ThinkGeo.com    |     Documentation    |     Premium Support

Custom PointStyle rendering based on column value fails

Hi,


I'm using an InMemoryFeatureLayer to render dynamically saved features, read from an SQL database. For each feature I'm adding an attribute (ft.ColumnValues.Add), that will determine how it should be rendered. I have verified that this assignment works.


I then add this layer to my map via a LayerOverlay, and use a ColumnSymbologyPointStyle class with a DrawCore method to render the points. This is called as expected. And I can see that my attribute is defined in the feature's ColumnValues collection (from the DrawCore routine argument "features").


However, when I retrieve the attribute value, it's blank ! I.e., the attributes are correctly transferred (the keys), but their associated values are lost.


Is this a known problem, or am I doing something wrong ?


 



Lars, 
  
   Did you specify the column definition when you created the InMemeoryFeatureLayer?  The reason I ask is that when you specify a column to return in the RequiredColumnNames we will first check to see if the column you are asking for is in the columns collection on the FeatureSource of the layer.  if it is not then we think it is a dynamic column and we populate that value from the CustomColumnFetch event. 
    
     In the constructor for the InMemoryFeatureLayer there is an overload that allows you to specify the columns.  If you set that I think your values will come across just fine.  Another thing we could do on our side is if we are about to call the CustomFieldFetch but see there is already a value for the column then we could leave it alone. 
   
 David

Hi David,



I'm adapting the code for "CachedValueStyle" from your sample project "VBExtendingMapSuiteStyle". Here's the code without all my comments:




Public Class ColumnSymbologyPointStyle
    Inherits PointStyle

    Private m_pointStyle As PointStyle
    Private m_symColumnName As String

    Public Sub New(ByVal defPointStyle As PointStyle, ByVal symColumnName As String)
        Me.m_pointStyle = defPointStyle
        Me.m_symColumnName = symColumnName
    End Sub

    Protected Overrides Sub DrawCore(ByVal features As System.Collections.Generic.IEnumerable(Of ThinkGeo.MapSuite.Core.Feature), ByVal canvas As ThinkGeo.MapSuite.Core.GeoCanvas, ByVal labelsInThisLayer As System.Collections.ObjectModel.Collection(Of ThinkGeo.MapSuite.Core.SimpleCandidate), ByVal labelsInAllLayers As System.Collections.ObjectModel.Collection(Of ThinkGeo.MapSuite.Core.SimpleCandidate))
        For Each ft As Feature In features
            If m_symColumnName <> "" Then
                Dim sym As String = ft.ColumnValues(m_symColumnName).ToString()
                If sym Is DBNull.Value OrElse sym = "" Then
                    'default point style
                Else
                    m_pointStyle = PointStyles.CreateSimpleTriangleStyle(New GeoColor(255, 0, 0), 16) 'debug
                End If
            End If
            Dim featureCollection As Collection(Of Feature) = New Collection(Of Feature)()
            featureCollection.Add(ft)
            m_pointStyle.Draw(featureCollection, canvas, labelsInThisLayer, labelsInAllLayers)
        Next
    End Sub

    Protected Overloads Overrides Function GetRequiredColumnNamesCore() As Collection(Of String)
        Dim columns As New Collection(Of String)()
        columns = m_pointStyle.GetRequiredColumnNames()
        If Not columns.Contains(m_symColumnName) Then
            columns.Add(m_symColumnName)
        End If
        Return columns
    End Function

    Protected Overloads Overrides Sub DrawSampleCore(ByVal canvas As GeoCanvas)
        m_pointStyle.DrawSample(canvas)
    End Sub

End Class





Here's how I use the custom point symbol definition:




Dim lbl As String
Dim wkt As String
Dim sym As String
Dim ft As Feature
Dim ftsLayer As InMemoryFeatureLayer = _
            New InMemoryFeatureLayer()
'... fetch data from DB into record set "rs" : Label, WKT-string, symbology-spec (all defined as strings)
For i = 0 To rs.Count - 1
    lbl = rs(i)(0)
    wkt = rs(i)(1)
    sym = rs(i)(2)

    ft = New PointShape(wkt).GetFeature()

    ft.ColumnValues.Add("label", lbl)
    ft.ColumnValues.Add("symbology", sym)

    ftsLayer.InternalFeatures.Add(ft)
Next

Dim defPointStyle As PointStyle = PointStyles.CreateSimplePointStyle(PointSymbolType.Star, New GeoColor(0, 0, 0), 10)

ftsLayer.ZoomLevelSet.ZoomLevel01.DefaultPointStyle = New ColumnSymbologyPointStyle(defPointStyle, "symbology")
ftsLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20





I think I've done what is necessary, but hopefully you can see what's wrong/missing.

 



Hi David, 



I forgot to note, that the assignment of "m_pointStyle" in DrawCore never happens, as the "sym" variable is always blank.


I.e., it's always the "defPointStyle" that gets rendered.


 



Hi Lars,


We found that you didn’t specify the column definition when you created the InMemoryFeatureLayer, you should use another overload to create the InMemoryFeatureLayer so that the column value of each feature can be added to the layer. The code is like this:

Dim columns As New Collection(Of FeatureSourceColumn)()
columns.Add(New FeatureSourceColumn("label"))
columns.Add(New FeatureSourceColumn("symbology"))

Dim ftsLayer As New InMemoryFeatureLayer(columns, New PointShape() {})
Dim ft As New Feature(New PointShape(0, 0))
ft.ColumnValues.Add("label", "lbl")
ft.ColumnValues.Add("symbology", "sym")
ftsLayer.InternalFeatures.Add(ft)

Please have a try to see what happens and any more questions please let me know.
Thanks,
Sun

Hi Sun,


Thanks, that worked :-)


The supplied code isn't valid VB though, so I ended up with this instead:



Dim ftsLayer As InMemoryFeatureLayer = New InMemoryFeatureLayer()
ftsLayer.Open()
ftsLayer.Columns.Add(New FeatureSourceColumn("label"))
ftsLayer.Columns.Add(New FeatureSourceColumn("symbology"))

 



Sorry, I’m not good at coding with VB.Net. Thank you for pointing me about my mistake.


Any more questions please let me know.
Thanks,
Sun

Hi Sun,


Just had a second look of your coding example, and I noticed you've used "InternalFeatures" to add features.


I'm using it too, but VS shows me this warning:



Warning    2    'Public ReadOnly Property InternalFeatures() As ThinkGeo.MapSuite.Core.GeoCollection(Of ThinkGeo.MapSuite.Core.Feature)' is obsolete: 'You are bypassing the automatic spatial indexing if the modify, add or delete. You need to call BuildIndex method later.'.



I've looked around in the docs, but can't seem to find an alternative. The obvious candidate, lyr.FeatureSource.AddFeature() doesn't seem to work. At first it threw this error (and I did remember to call Open() first :-)):



The FeatureSource is not in a transaction.



I then added a call to lyr.FeatureSource.BeginTransaction(), and now it doesn't err, but it doesn't work either - nothing is rendered.


Any thoughts ?


 



Lars, 
  
   You need to call the BeginTransaction and then your Add methods.  After that you need to call the CommitTransaction which is what will commit the items to the internal features.  After the commit you can check to see what is in there before you draw to make sure.  Also if you are using a projection you need to remember that we are assuming that you are passing in the shapes in the projected format and not the from format.  If you are using projection just make sure you remove it for a bit while you add these records.  The internal features gives you direct access to the internals and bypasses the projection part.  That is another reason why it kind of breaks all the rules, normally if you have a projection everything in the outside of the layer is projected. 
  
 David 
  
 David

Hi David,


Great, I was looking for it, but apparently managed to simply overlook CommitTransaction.


The feature adding works perfectly now, without warning and all :-)


 



Lars, 
  
   Great!  We added the internal features to be a shortcut but in hindsight it causes a bunch of issues and doesn’t work like all of the other layers or feature sources.  I think the cases where it is nice is when you want to clear it quickly and also do a one line operation such as an add etc.  i think what we can do is add some transactioned add/delete/edit etc where the transaction is automatic.  This would be slower then calling the being transaction but convienant if you had just a small list of things.  On the other hand calling the begin and commit are not that hard either.  We do need to support a clear or delete all kind of thing if we want it to truly replace the internal features. 
  
 David