ThinkGeo.com    |     Documentation    |     Premium Support

Too big memory footprint

Hello all,


I have been fighting with realy large datasets from databases for quite long time. I have also tried to produce my own MapSuite database implementation in order to find out why my pc memory usage by the MapSuite application is so huge. So long I have find out that the API requires that the GetFeaturesInsideBoundingBoxCore returns a Collection of Features. It is these features that are stored in memory before returned to the core implementation for presentation that are filling up the RAM. Not sure if there are any other solution  to this problem, like for example drawing the points as they come thus reducing the need to store the Features in memory and then taking advantage of the screen coords to retrieve more information. The basic implementation I use is as follows:


Protected Overrides Function GetFeaturesInsideBoundingBoxCore(boundingBox As ThinkGeo.MapSuite.Core.RectangleShape, returningColumnNames As System.Collections.Generic.IEnumerable(Of String)) As System.Collections.ObjectModel.Collection(Of ThinkGeo.MapSuite.Core.Feature)

 Dim retFeatures As New System.Collections.ObjectModel.Collection(Of ThinkGeo.MapSuite.Core.Feature)

 Dim buildMBR As String = "BuildMBR(" & boundingBox.UpperLeftPoint.X & "," & boundingBox.UpperLeftPoint.Y & "," & boundingBox.LowerRightPoint.X & "," & boundingBox.LowerRightPoint.Y & ")"

 Dim selectAllFeaturesCommand As New SQLiteCommand("select *, AsText(" & mTableInfo.GeometryColumnName & ") as geom from " & mTableInfo.TableName & " Where MBRContains(" & buildMBR & "," & mTableInfo.GeometryColumnName & ")", mSqLiteConn)

 Dim allFeaturesReader = selectAllFeaturesCommand.ExecuteReader()

 If allFeaturesReader.HasRows Then

 While allFeaturesReader.Read

 Dim wkt = DirectCast(allFeaturesReader("geom"), String)

 Dim aFeature = New Feature(wkt)

 retFeatures.Add(aFeature)

 End While

 End If

 allFeaturesReader.Close()

 Return retFeatures

 End Function


Any advice will be much appreciated.


Kind Regards


Yiannis



Yiannis,


  I understand your issue and I think I can help you with this.  First let me start with a little background on why we created the API this way and proceed to a solution.  The reason we wanted to get all of the features was that there are a number of styles, or ways of rendering, which need all of the data to render properly.  An example would be the heat style, cluster style, labeling etc.  Since we knew we had to support this it influenced our architecture.  Looking back it might not have been the best choice.  We were fortunate because in most of our products the drawing is done on a per tile basis and tiles are relatively small so it helped by not processing too much data at once.


  As our products progressed we started to notice the memory pressure that you mentioned and we made some changes to our most popular FeatureLayer, the ShapeFileFeatureLayer, to address this.  What we did was to override the DrawCore method on the ShapeFileFeatureLayer and where we would normally call the GetFeaturesForDrawing on the FeatureSource we instead called a new method to get the FeatureIds for drawing in the bounding box.  We then loaded the real shapes from the Ids in chunks and when we had a chunk ready we called the rest of the drawing pipeline with just those features.  When the next chunk was ready we sent that through.  In this way we cut the memory pressure and increased performance.  The price was that some of the styles I mentioned do not work well with the default ShapeFileFeatureLayer.  To address that we made the 'chunk' size configurable so that you can set it to int.max if you wanted to force it to draw the way it did before in one big chunk.


  The plans for the future is to adapt our API in such a way that we can support this by default.  At this moment it is behind some other large API changes such as the elimination of Shapes going with a new version of Features.  This will all be announced pretty soon.  After this is complete I believe this is the next major thing on the list related to the long term API trajectory.


  Back to your situation.  I think you can keep your GetFeatureInBoundingBoxCore intact as we will still need this.  Instead we are going to override the DrawCore and put in some logic to call a new API you are going to add on your FeatureSource.   Here is what you need to do..


1. Create a new method on your FeaureSource that allows you get a check of your data.  Something like GetRecordsForDrawing but instead you pass it how many records you want returned and at what starting location you are on.  In this way on the first call you create your reader and read 100 records then you return back the first 100 but keep the read in memory.  For subsequent call you can go back to the reader and pick up where you left off.


2. Override the DrawCore method on your FeatureLayer and use the code below.  Note where you see the line that has “FeatureSource.GetFeaturesForDrawing” method.  You want to replace that with the call to your new API above.  You also want to put that call in a loop so that you keep calling your API and then the CurrentZoomLevel.Draw method.  The CurrentZoomLevel.Draw is that passes the data off to the styles for drawing.  One thing to note is that there is a new Collection<simpleCanidate> created, you will want to make sure you move the creation of that BEFORE the loop so you keep the same collection.  If you do not then labeling will not work properly.  


3. Let it rip!


I know this is allot to dump on you and I will see if we can have a developer create a code community sample for our wiki that address this.  I figured that as I don’t have time for that at the moment I would try and explain all I could and hope you are super resourceful.  As this is key piece of functionality I will really push for a wiki sample on this as it impacts user created feature sources.  One other thing is that there is allot of code that deals with wrapping below and if you do not use extent wrapping you can dump this code.


 


David


 

 protected override void DrawCore(GeoCanvas canvas, Collection<SimpleCandidate> labelsInAllLayers)
        {
            DrawingQuality tempDrawingQuality = canvas.DrawingQuality;
            canvas.DrawingQuality = drawingQuality;

            try
            {
                ZoomLevel currentZoomLevel = zoomLevelSet.GetZoomLevelForDrawing(canvas.CurrentWorldExtent, canvas.Width, canvas.MapUnit, canvas.Dpi);
                featureSource.DrawingProgressChanged += new EventHandler<DrawingProgressChangedEventArgs>(featureSource_ProgressDrawing);

                if (currentZoomLevel != null)
                {
                    Collection<string> columnNames = currentZoomLevel.GetRequiredColumnNames();
                    RectangleShape marginWorldExtent = MarginAffectWorldExtent(canvas.CurrentWorldExtent);

                    Collection<Feature> features = featureSource.GetFeaturesForDrawing(marginWorldExtent, canvas.Width, canvas.Height, columnNames);
                    DrawingFeaturesEventArgs drawingFeaturesEventArgs = new DrawingFeaturesEventArgs(features);
                    OnDrawingFeatures(drawingFeaturesEventArgs);

                    if (features.Count != 0)
                    {
                        Collection<SimpleCandidate> labeledFeatures = new Collection<SimpleCandidate>();
                        currentZoomLevel.Draw(canvas, features, labeledFeatures, labelsInAllLayers);
                    }

                    features.Clear();
                    features = null;

                    if (WrappingMode == WrappingMode.WrapDateline)
                    {
                        Collection<Feature> wrappingFeaturesLeft = FeatureSource.GetWrappingFeaturesLeft(marginWorldExtent, canvas.Width, canvas.Height, columnNames, WrappingExtent);
                        if (wrappingFeaturesLeft.Count != 0)
                        {
                            while (canvas.CurrentWorldExtent.LowerLeftPoint.X < WrappingExtent.LowerLeftPoint.X)
                            {
                                canvas.CurrentWorldExtent.TranslateByOffset(WrappingExtent.Width, 0);
                                currentZoomLevel.Draw(canvas, wrappingFeaturesLeft, new Collection<SimpleCandidate>(), labelsInAllLayers);
                            }
                        }

                        Collection<Feature> wrappingFeaturesRight = FeatureSource.GetWrappingFeaturesRight(marginWorldExtent, canvas.Width, canvas.Height, columnNames, WrappingExtent);
                        if (wrappingFeaturesRight.Count != 0)
                        {
                            while (canvas.CurrentWorldExtent.LowerRightPoint.X > WrappingExtent.LowerRightPoint.X)
                            {
                                canvas.CurrentWorldExtent.TranslateByOffset(-WrappingExtent.Width, 0);
                                currentZoomLevel.Draw(canvas, wrappingFeaturesRight, new Collection<SimpleCandidate>(), labelsInAllLayers);
                            }
                        }
                    }
                }
            }
            finally
            {
                canvas.DrawingQuality = tempDrawingQuality;
                featureSource.DrawingProgressChanged -= new EventHandler<DrawingProgressChangedEventArgs>(featureSource_ProgressDrawing);
            }
        }