ThinkGeo.com    |     Documentation    |     Premium Support

Feature Merge Gets Slower

It appears to me that, when merging features, the merge process takes longer with each successive addition of a feature to the merged whole.


I have a process that:


1. Retrieves a feature from a shape file


2. Puts in on an in-memory layer


3. Retrieves another feature from the same shape file


4. Merges it with the feature(s) in the in-memory layer.


5. Go to step 3 and repeat until complete


After each merge the successive feature merge operations take longer to complete. After about 20 features it gets quite slow. Any operations with more than that can be uncomfortably long.


I may be pushing the envelope with this process, but is it supposed to process this slowly?


Bob Mc.



Bob,


Here attached is a demo code with the function you said. The performance is not bad as it takes me 6.949s for read all the features one by one from autinStreets.shp (including all the info in DBF) and add them one by one to a InMemoryLayer. There are 13843 records in austinStreets.shp. The size for shp file is 1.2M and the corresponding dbf is 3.5M.


Please have a look the attached code.


Ben



42-MergeToInMemoryFeatureSource.txt (1.71 KB)

Thanks Ben. The demo code certainly educates me regarding a different approach than the one I took. Chalk it up to my inexperience with the product.


However, I have a couple of questions:


1. I'm actually looking to "union" or merge the selected features into a single unified feature. Does the sample code you provided achieve that effect? If not, how would I do that?


2. Once I get all the features union'ed, how to I get them onto a map layer? Do I have to loop through the features one-by-one and add them to a layer.


Thanks for your assistance,


Bob Mc.



Bob, 
  
   I think the key is to use the Union on the AreaShape that is static.  I think it is on all of the AreaShape types like polygon etc.  It takes an IEnumerable number of Features or Shapes I think.  It uses a different method to union which is quite fast if you know up front you have lots of shapes to union.  Try it out, I will send you some code if you need it but it should be straight forward.  In the next beta it will take Features or Shapes if it only takes one type now.  We are going over the entire API and anywhere we take a shape we will have an overload for a feature and vice versa.  I will talk with Ben and let him know about this feature if he doesn’t already.  Also I noticed in Ben’s code he used the EditTools API for the editing.  This is really a good method for most Layers however if you are using an InMemory Layer you can skip the formalities and add the features directly to the Features collection on the InMemoryLayer.  It is a shortcut for this kind of layer because it is backed by a dictionary in memory but you can’t use this trick on any other layer types.  Hope this helps. 
  
 David

Thanks David. I need a little clarification though. What do you mean by the AreaShape that is "static"?


Here's a condensed version of the code I have. Can you point out where the inefficiencies are?


        Dim zip As String

        Dim zipLayer As New ShapeFileLayer("mapfiles/CA/CAxcta5cu.shp")

        Dim mapShapeLayer As New InMemoryLayer

        Dim zipFeature As Feature = Nothing

        Dim featureCnt As Integer = 0

        Dim firstKey As String = String.Empty

        Dim targetShape As AreaBaseShape = Nothing


        zipLayer.Open()


        For Each zip In zipList

            featureCnt += 1


            zipFeature = zipLayer.FeatureSource.GetFeatureById(zip.GIST_ID.ToString, New String() {"GIST_ID", "COUNTY", "ZCTA"})

            mapShapeLayer.Features.Add(zipFeature.Id, zipFeature)


            If featureCnt = 1 Then

                firstKey = zipFeature.Id

            Else

                Try

                    targetShape = CType(zipFeature.GetShape, AreaBaseShape)

                    mapShapeLayer.Open()

                    mapShapeLayer.EditTools.BeginTransaction()

                    mapShapeLayer.EditTools.Union(mapShapeLayer.Features.Item(firstKey).Id, targetShape)

                    mapShapeLayer.EditTools.Delete(zipFeature.Id)

                    Dim unionResults As TransactionResult = mapShapeLayer.EditTools.CommitTransaction()

                    mapShapeLayer.Close()

                Catch ex As Exception

                    If mapShapeLayer.EditTools.IsInTransaction Then

                        mapShapeLayer.EditTools.RollbackTransaction()

                    End If

                    mapShapeLayer.Close()

                    Exit For

                End Try

            End If

        Next


        zipLayer.Close()

 


Thanks,


Bob Mc.






 


Bob,


 


I can help.  I see you are using the Union on the EditTools and I forgot that was there.  It is really for doing unions like when the user draws a shape on the screen for editing.  I mean that was its purpose and for what you are doing it's not great.  I can see now we may need to tweak our API to make that more obvious.


 


What you want to do is a Union like I mentioned where you can pass in a bunch of areas at one time and it will much more efficiently union them.


 


Static: If you already know skip this...


 


Static is a C# keyword and it allows us to create a method that only shows up on the non instance version of a class.  Typically when you want to call a method on an object such as a PolygonShape you create a new instance like “PolygonShape myPolygon = new PolygonShape” and then when the object is 'alive' you can call methods on the myPolygonShape like “double area = myPolygonShape.GetArea()” and it will return an area.  Static methods do not work like this static methods work on the 'not-alive' version of the class.  They are intended to attach helpful functions to a class but they do not work on the class instance itself.  In the example above the GetArea was assumed to get the area of that instance of the object.  Let me show you a call to a static method. “PolygonShape.GetArea(someOtherPolygon, GeographicUnit.DecimalDegrees)”  You may notice that we did not have to 'new' an object instance.  We can type just the plain 'PolygonShape' class and it has some methods.  Notice also the GetArea is a similar method to the above example.  The only difference is that you need to pass in a PolygonShape instance for it to work.  This is very helpful because you do not need an instance of the object.


 


In the case of the union we wanted a static method on the AreaBaseShape, this is the abstract class that all of the area type shapes such as PolygonShape, RectangleShape, etc.  inherit from, to have a way to efficiently union large groups.  To access this union method you just just type in the class name and method.  For example you could do “PolygonShape.Union(...) or RectangleShape.Union(...) or AreaShape.Union(...)”  They will all do the same thing for you.



 


        ' Create the holding collection of all of the area shapes

        ' you want to union together

        Dim zipShapes As New Collection(Of AreaBaseShape)



        ' Loop through your zips and for each zip get the shape

        ' and populate the collection

        For Each zip In zipList

            zipFeature = zipLayer.FeatureSource.GetFeatureById(zip.GIST_ID.ToString, New String() {"GIST_ID", "COUNTY", "ZCTA"})

            zipShapes.Add(zipFeature.GetShape())

        Next



        ' Take the collection of zips and pass it to the static method on

        ' the AreaBaseShape.  Note you could also do it on the PolygonShape

        ' RectangleShape or anything that inherits from AreaBaseShape and

        ' you will get the same results.

        Dim combinedZips As MultipolygonShape = AreaBaseShape.Union(zipShapes)



        ' Here is the shortcut to add the feature to your InMemoryLayer and remember it only

        ' works with InMemoryLayers

        mapShapeLayer.Features.Add("TheIdofYourFeature",new Feature(combinedZips, "TheIdofYourFeature")



        ' I hope this helps and should be much faster.  Also I didnt integrate

        ' this with the rest of your code.  It is not becasue I am lazy but my VB.NET

        ' skill is not what it used to be and its so frustrating for me to write in it. :(

        ' Sorry about that.


David



Thanks David. No problem on the VB stuff, I code in both C# and VB.Net, but we're using VB.Net for this project.


Thanks for the programming lesson as well. I'm well versed in static methods (Shared keyword in VB.Net) but I didn't catch the inference initially. Now I get it.


I'll give your example a try and report back. Thanks again,


Bob Mc.



David, your merge code worked wonderfully. The features are merged together very, very quickly. Thank you very much.


The problem I'm having now is one that I reported under the heading "Deployment Problem" yesterday. I've now been able to reproduce it in development. What happens is, after I merge the features I put the merged feature on an in-memory layer and add that to the map control. The map zooms to the proper extent, but no feature is displayed and if I attempt to zoom or scroll the map fills with a pink color and a broken image icon appears in the top left corner.


I created a test app that produces the problem. The code is below:


<%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="_Default" %>

<%@ Register assembly="WebEdition" namespace="ThinkGeo.MapSuite.WebEdition" tagprefix="cc1" %>


w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">


w3.org/1999/xhtml">



    Untitled Page





    <form> id="form1" runat="server">

    

        <asp:ScriptManager ID="ScriptManager1" runat="server">

        </asp:ScriptManager>


        <asp:UpdatePanel runat="server" ID="upnlMap" UpdateMode="Conditional">

            <ContentTemplate>

                <cc1:Map ID="ctlDirMap" runat="server" height="768px" width="1024px"></cc1:Map>

                <asp:Button runat="server" ID="btnShow5295" Text="Show Dir #5295" />

            </ContentTemplate>

        </asp:UpdatePanel>

    

    </form>






Imports System.Collections

Imports ThinkGeo.MapSuite.Core

Imports ThinkGeo.MapSuite.WebEdition


Partial Class _Default

    Inherits System.Web.UI.Page


    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        If Not Page.IsPostBack Then

            ctlDirMap.MapUnit = GeographyUnit.DecimalDegree


            ctlDirMap.PanZoomBar.Enabled = True

            ctlDirMap.LayerSwitcher.Enabled = True

            ctlDirMap.MousePosition.Enabled = True


            ctlDirMap.BackgroundMap.YahooMap.Name = "Yahoo Demo"

            ctlDirMap.BackgroundMap.YahooMap.YahooMapType = YahooMapType.Regular


            ctlDirMap.CurrentExtent = New RectangleShape(-94.64456, 39.13578, -94.4674, 39.03605)

        End If

    End Sub


    Protected Sub btnShow5295_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnShow5295.Click

        Dim gistList() As Integer = { _

                1208, 1211, 1067, 1068, 1069, 1216, 1221, 1222, 1223, 1224, 1228, 1231, _

                1233, 1234, 1241, 1070, 1242, 1243, 1244, 1252, 1254, 1255, 1256, 1257, _

                1258, 1259, 1260}


        Dim lastUSState As String = String.Empty

        Dim zipMapSourcePath As String = ConfigurationManager.AppSettings("zipMapPath")

        Dim zipSource As ShapeFileFeatureSource = Nothing

        Dim featureCnt As Integer = 0

        Dim zipShapes As New System.Collections.ObjectModel.Collection(Of AreaBaseShape)

        Dim zipFeature As Feature


        Try

            Dim stopWatch As New System.Diagnostics.Stopwatch

            stopWatch.Start()


            zipSource = New ShapeFileFeatureSource("C:\MapFiles\CA\CAzcta5cu.shp")

            zipSource.Open()


            For Each gist As Integer In gistList

                featureCnt += 1


                zipFeature = zipSource.GetFeatureById(gist.ToString, New String() {"GIST_ID", "COUNTY", "ZCTA"})

                zipFeature.ColumnValues.Add("DIRNUM", String.Format("{0:000000}", 5295))

                zipShapes.Add(zipFeature.GetShape())


            Next


            zipSource.Close()


            Dim combinedZips As MultipolygonShape = AreaBaseShape.Union(zipShapes)


            System.Diagnostics.Debug.WriteLine("Elapsed milliseconds = " & stopWatch.ElapsedMilliseconds)


            Dim mapShapeLayer As New InMemoryLayer

            mapShapeLayer.Open()

            mapShapeLayer.Name = "D5295"

            mapShapeLayer.FeatureSourceColumns.Add(New FeatureSourceColumn("GIST_ID", "Integer", 8))

            mapShapeLayer.FeatureSourceColumns.Add(New FeatureSourceColumn("COUNTY", "String", 5))

            mapShapeLayer.FeatureSourceColumns.Add(New FeatureSourceColumn("ZCTA", "String", 5))

            mapShapeLayer.FeatureSourceColumns.Add(New FeatureSourceColumn("DIRNUM", "String", 6))

            mapShapeLayer.Features.Add("5295", New Feature(combinedZips, "5295"))

            mapShapeLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle.OutlinePen.Color = GeoColor.FromArgb(255, GeoColor.SimpleColors.Black)

            mapShapeLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle.FillSolidBrush.Color = GeoColor.FromArgb(150, GeoColor.SimpleColors.Gold)

            mapShapeLayer.ZoomLevelSet.ZoomLevel01.DefaultTextStyle = TextStyles.City1("DIRNUM")

            mapShapeLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20

            mapShapeLayer.Close()


            ctlDirMap.DynamicLayers.Add("D5295", mapShapeLayer)


            mapShapeLayer.Open()

            ctlDirMap.CurrentExtent = mapShapeLayer.GetBoundingBox()

            mapShapeLayer.Close()


        Catch ex As Exception

            Response.Write("Error occurred in map generation - " & ex.Message)

        End Try


    End Sub

End Class

 


Thanks for your help so far.


Bob Mc.



Bob, 



Please see the reply from the following link. 



Ben 



gis.thinkgeo.com/Support/DiscussionForums/tabid/143/forumid/12/tpage/1/view/topic/postid/4598/Default.aspx#4626