ThinkGeo.com    |     Documentation    |     Premium Support

Using Union

I would like to buffer a selection of multiple features. In the past it has been necessary to union the features into one shape for buffering and then intersect-selecting with the buffered shape. I am assuming a similar process is necessary with Map Suite.


I can obtain a collection of features into a Collection type, but can not use this collection as an input to layer.internalfeatures.union.


 


I see there is also the layer.edittools.union but it looks like this takes one shape and adds it to another. My question is what is the most straight forward way of creating a shape out of a collection of feature in a layer (or perhaps even an entire layer as all features to union will already be in a dedicated layer).


Thanks,.



Nelson 
  
 The following 2 static methods are ideal for you, which unions all the input features / areaShapes and return the result as a MultiPolygonShape. 
  
        static BaseShapeArea.MultipolygonShape Union(IEnumerable<AreaBaseShape> areaShapes); 
        static BaseShapeArea.MultipolygonShape Union(IEnumerable<Feature> features); 
  
 If you just want to union one shape, you can create an area based shape and call the Union method, it also returns a MultiPolygonShape. 
  
        PolygonShape a = new PolygonShape(); 
        a.Union(targetShape) 
  
 Ben 


Thank you, Ben.

My Pleasure. Just let us know if we might make your coding easier.

I am having a performance issue related to the scenario above involving trying to make a buffered selection based on an initial selection. The issue seems to explode into a memory leak that eventually renders the entire site non-responsive. The memory consumption jumps to surpluses of 1GB and eventually I am given an out of memory error.


This is how I obtain the original selection from a rectangle track shape:


                Dim highlightLayer As InMemoryFeatureLayer = GeoMap.DynamicOverlay.Layers("HighlightLayer")
                Dim parcelLayer As FeatureLayer = GeoMap.StaticOverlay.Layers("Parcels")
                Dim fCollection As System.Collections.ObjectModel.Collection(Of Feature)
                Dim bufferLayer As InMemoryFeatureLayer = GeoMap.DynamicOverlay.Layers("BufferLayer")

                parcelLayer.Open()

                highlightLayer.InternalFeatures.Clear()
                bufferLayer.InternalFeatures.Clear()

                Dim trackShape As PointShape = e.Position
                fCollection = parcelLayer.QueryTools.GetFeaturesIntersecting(trackShape, New String() {})

                For Each fFeature As Feature In fCollection
                    If Not highlightLayer.InternalFeatures.ContainsKey(fFeature.Id) Then highlightLayer.InternalFeatures.Add(fFeature.Id, fFeature)
                Next

                parcelLayer.Close()

                GeoMap.EditLayer.InternalFeatures.Clear()
                GeoMap.DynamicOverlay.Redraw()

                ScriptManager.RegisterClientScriptBlock(panelInfo, GetType(UpdatePanel), "callBuffer", "tabManager('Buffer');", True)


Then the user is given options of the units of measure and the amount to buffer the selection. At this point we can assume it will always be feet and the input will be 100. At this point unioning the features takes some times but goes through, but I am frozen at the buffer request which is what creates the memory leak. It's worth mentioning that if the collection of features is small then eventually the buffer completes, but I should be able to do this with 1000's of features without fear of system crash:


 


Protected Sub cmdBufferOk_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles cmdBufferOk.Click
        Dim highlightLayer As InMemoryFeatureLayer = DirectCast(GeoMap.DynamicOverlay.Layers("HighlightLayer"), InMemoryFeatureLayer)
        Dim bufferLayer As InMemoryFeatureLayer = DirectCast(GeoMap.DynamicOverlay.Layers("BufferLayer"), InMemoryFeatureLayer)
        Dim parcelLayer As FeatureLayer = GeoMap.StaticOverlay.Layers("Parcels")

        highlightLayer.Open()

        Dim fselectedFeatures As IEnumerable(Of Feature)
        fselectedFeatures = highlightLayer.FeatureSource.GetAllFeatures(ReturningColumnsType.NoColumns)

        Dim baseShape As MultipolygonShape = AreaBaseShape.Union(fselectedFeatures)
        Dim bufferedShape As MultipolygonShape = baseShape.Buffer(txtBuffer.Text, GeographyUnit.Meter, DistanceUnit.Feet)
        Dim bufferFeature As New Feature(bufferedShape)

        bufferLayer.Open()
        'bufferLayer.InternalFeatures.Clear()
        bufferLayer.InternalFeatures.Add("BufferFeature", bufferFeature)
        bufferLayer.Close()

        highlightLayer.InternalFeatures.Clear()

        parcelLayer.Open()
        Dim fCollection As System.Collections.ObjectModel.Collection(Of Feature)
        fCollection = parcelLayer.QueryTools.GetFeaturesIntersecting(bufferFeature, New String(1) {"STREETNUMB", "STREETNAME"})

        For Each fFeature As Feature In fCollection
            If Not highlightLayer.InternalFeatures.ContainsKey(fFeature.Id) Then highlightLayer.InternalFeatures.Add(fFeature.Id, fFeature)
        Next

        parcelLayer.Close()
        highlightLayer.Close()

        GeoMap.EditLayer.InternalFeatures.Clear()
        GeoMap.DynamicOverlay.Redraw()

        ScriptManager.RegisterClientScriptBlock(panelInfo, GetType(UpdatePanel), "callInfo", "tabManager('Information');", True)
        spnInfo.InnerHtml = GetIdentifyContent(fCollection, 50)

    End Sub


Nelson, 
  
 You can often find a .NET application consumes more memory than others. That’s because the memory of an object will not be finalized and retrieved until there is a pressure on the memory. That is to say from the task manager, the memory will keep climbing higher and higher and suddenly when hitting some threshold, going back down. 
  
 You can manually let the framework do the memory collection. Adding the following code at the end of your cmdBufferOK_Click() method, you will see the memory come back to normal in the task manager.  
  
 GC.Collect(); 
  
 In fact, it’s more efficient to let the .NET framework manage the memory itself. Although the memory keeps climbing up, program will not be crashed as always it will be retrieve at a right time.  
  
 For the Buffer method, there is an overload which accepts a BufferCapType, you can set it to Square, which means EndCaps are truncated flat at the line ends, to get some performance benefits.  
  
 Ben 


GC.Collect at the end of the method doesn't seem to work because the memory leak causes the 'out of memory' error that stems from the buffer happens before the function ever completes or gets to that line to clear the garbage. I am familiar with the way ASP.NET handles memory or namely how it holds it until the memory is needed elsewhere.


 


I can become out of memory on just one attempt to buffer; that is the issue. I could buffer small collections repeatedly and not receive the error it is only when doing a buffer on a moderately sized collection.


When I do as you recommend and change the following line of code, I am given an error on compile as if I had not formatted the line correctly, but I did follow the syntax as it appears in intellisense...


            Dim bufferedShape As MultipolygonShape = baseShape.Buffer(txtBuffer.Text, 8, BufferCapStyle.Square, GeographyUnit.Meter, DistanceUnit.Feet)
            

Not really sure what I'm doing wrong here but that seems to be the format as told by intellisense.



Nelson, 


I see your problem. It’s just about the method itself, not for the memory after executing that method, sorry for the misunderstanding.
 
To be accurate, I think that’s not “memory leak”. Our Buffer() method is implemented in open-source NTS module, which is written in managed code without using unmanaged resources, so all the memory is managed by GC, and there should not have any “leak” for the memory. I think the reason for the issue is that the method Buffer() itself does take much memory, even there is no “leak”, the consumed memory brings the “OutOfMemory” issue because of the very complex logic.
 


        
  1. Set the BufferCapType to BufferCapStyle.Square

  2.     
  3. Union shapes’ boundingbox instead before the buffering. This will be less accurate but will use much less memory and be much faster, 


 Your code has one error makes you can’t compile:


' The following code "txtBuffer.Text" is type of string, but here needs double type.
Dim bufferedShape As MultipolygonShape = baseShape.Buffer(txtBuffer.Text, GeographyUnit.Meter, DistanceUnit.Feet)
 
' So you have to change to.
Dim bufferedShape As MultipolygonShape = baseShape.Buffer(Convert.ToInt32(txtBuffer.Text), 8, BufferCapStyle.Square, GeographyUnit.Meter, DistanceUnit.Feet)
 

     
We hope this will help, any queries please let us know.
 
Ben

234-Post4980VB.zip (101 KB)

I am running into a discrepency. First, let me start by saying my shapefile is in meters and the map object has had this specified also. With that out of the way, I want to buffer in feet. Reading the API, I would use the line of code you provided last, GeoUnit.meter and DistanceUnit.Feet. 



This produces very incorrect results, and buffer by even 30 feet this becomes evident. After some math, it looks like the buffer tool is working opposite as it should. Swapping the units of measure to geounit.feet and distanceunit.feet produces much more realistic results. Why is this? 



Also, I am informed by VS that this API is obsolete anyways. What is a good way to implement this going forward, or will this API still be in tact after the next release?



Nelson, 
  
 It is a bug that the GeographicUnit and DistanceUnit are opposite, thanks for reporting this and we will make it right in the upcoming version (22rd this month). 
  
 The new version of the API is  
 
Dim bufferedShape As MultipolygonShape = baseShape.Buffer(Convert.ToInt32(txtBuffer.Text), 8, BufferCapType.Square, GeographyUnit.Meter, DistanceUnit.Feet)
 
 Sorry I didn’t provide you the recommended one last time. Just remind please remove all the “obsolete API” warnings in your application as in the coming release, we will get rid of all the “obsolete APIs” and if you are still using that, you will get an error instead of a warning. 
  
 Thanks, 
  
 Ben 


Did you accidentally use the same line of code for the new API as you did for the old? I don’t notice a difference?

Nelson, 
  
 It’s on the 3rd parameter. The old one is BuffferCapStyle.Square while the new one is BufferCapType.Square. Very similar isn’t it :) 
  
 Ben. 


Haha, yes. Thank you.

Thanks again for pointing out this issue and more suggestions are appreciated, Nelson.