ThinkGeo.com    |     Documentation    |     Premium Support

Quering big shp files results in out of memory

 Hello again,


I have ended up with a 266Mb shp file (it's dbf is 580M) that I need to query using GetFeaturesByColumnValue from the querytools. I think that I followed all recommendations on optimal and fast reading. I even created an Index as recommended in the "Build Custom index example". However, when I execute the GetFeaturesByColumnValue it seems that MapSuite tries to load first the whole shp file in memory which causes an OutOfMemory exception sooner or later.


The actual rows that I was expecting to get back were about 8500 in 3 columns, i.e not something out of this world. So I am wondering if, quite possible, I am doing something wrong.


Kind Regards


Yiannis



Hello Ioannis, 
  
 Could you please provide some codes you are using? 
  
 Regards, 
  
 Gary

Gladly.... Please review the VB.net project I have attached. The code sample, uses OldDB to get some grouping info from the shp/dbf file that are necessary. Then it normally Open the layer and issues Queries requesting rows by Column values. The code runs out of memory as it executes the


Dim allFeatures = aTrackLayer.QueryTools.GetFeaturesByColumnValue("obj_id", objID, ReturningColumnsType.AllColumns)


Kind Regards


Yiannis


p.s My sample data file is about 250Mb. If you want it I can put it in an FTP for you to download.



QueryGISFiles.zip (13 KB)

Hello Ioannis, 
  
 Sorry for delay, and thanks for the sample code. 
  
 In your code, you have built the indexfile with obj_id before you query the features, so why don’t you use the GetFeaturesByIds function instead of GetFeaturesByColumnValue? That will be much faster. 
  
 Second thing is please try to avoid using ReturningColumnsType.AllColumns, just return the columns you really need. 
  
 Wait for your further information, Regards, 
  
 Gary

Hello Garry, 
  
 Sorry for the delay, I had to switch to something else thus this issue went to the background. Do you imply that If I had used GetFeaturesByIds, MapSuite would not load the whole layer in memory before queering it? Can you please confirm it with some numbers as I see no difference here. Some applies for the ReturningColumnsType.AllColumns. I only have 3 columns in my huge shp file and I need all of them. 
  
 Looking forward to hearing from you 
 Kind Regards 
 Yiannis

Hello Ioannis, 
  
 Thanks for your further information. 
  
 I didn’t imply that you must use those functions, just some recommend to test if it can resolve this problem, because I didn’t have the data, so I don’t know more details, just some guesses. 
  
 So could you please provide a data download? I can make some more test on it. 
  
 Regards, 
  
 Gary

Hello Gary,


after quite some time I got time to come back to this issue as well hoping that we can find a solution. So in the last post my complain was as follows:


1. I use a shape file.


2. This shape file is huge as it has 364988 features.


3. One column in it has the name "TrackID". But this is not the unique column.


4. What I want to do is to select all features with TrackID = x


The only way that I have found out to query it is by using:


Collection<ThinkGeo.MapSuite.Core.Feature> points = mTabLayer.FeatureSource.GetFeaturesByColumnValue("TrackID", x, ReturningColumnsType.AllColumns);


If I use this, it takes around 35 second to execute and essenatially the whole shape file is loaded in the memory of the computer (in this example I have an application memory foot print of 544.020K)


What I though you had suggested is to use the GetFeaturesByIDs providing that I had indexed my file. So I indexed my file as follows:


ShapeFileFeatureLayer.BuildIndexFile("xx.shp", "track.idx", "TrackID", "", BuildIndexMode.DoNotRebuild);
ShapeFileFeatureLayer shpLayer = new ShapeFileFeatureLayer("xx.shp","track.idx", ShapeFileReadWriteMode.ReadOnly);


And I executed this:


Collection<ThinkGeo.MapSuite.Core.Feature> points = mTabLayer.FeatureSource.GetFeaturesByIds(new String[] { trackID }, ReturningColumnsType.AllColumns);


The result is that the memory foot print is small (although it keeps on growing after every iteration which makes me believe that there is some memory leak there) the execution is fast but it does not return the TrackID but the Feature ID!!


So I am back to square one. How can I query a shape file using MapSuite for a NonID column fast and without killing my memory.


Any help will be much appreciated.


Kind Regards


Yiannis


 


 


 



Ioannis,



 I can help you with this and have created a small sample to help explain.  First I think there is some confusion about the Id, or to say when you see a methods that end with "ById".  The Id, for shapefiles are the physical order of the records in the file starting with 1.  So if you say GetFeatureVyId and pick 100 then you will get the 100th record in the shapefile.  The DBF file is also ordered in the exact same way so record 100 in the shapefile lines up with record 100 in the DBF file.  When building an index what the ID column related to is t actually put the record number in a filed in the dbf.  Here is why, let's say for example you use SQL qo query the DBF file, you may get the rows back that match but you don't know what their physical row number was so you can't cross reference it with the shape file.  Imagine now that you have an ID column and you do the query you can now know what record to look up in the shapefile.



  In your case I have created a small sample that should be very efficient.  The sample loads the DBF through our DBF engine and first starts by just finding records that match the criteria, next it get only those features from the shapefile.  I am not sure at this point why our built in methods on the Query tools are using so much memory but I will look into that next.  At least this gives you a clear path to do what you need.



David




            // Open the shapefile, this is where we will pull the feature what match
            ShapeFileFeatureLayer worldLayer = new ShapeFileFeatureLayer(@"C:\Program Files (x86)\ThinkGeo\Map Suite Wpf Desktop Full Edition 5.5\Samples\CSharp Samples\SampleData\Data\countries02.shp", ShapeFileReadWriteMode.ReadOnly);
            worldLayer.Open();
 
            // Open the DBF, we will search this to get aorund of the overhead of the layer and geometries
            GeoDbf geoDbf = new GeoDbf(@"C:\Program Files (x86)\ThinkGeo\Map Suite Wpf Desktop Full Edition 5.5\Samples\CSharp Samples\SampleData\Data\countries02.dbf", DbfReadWriteMode.ReadOnly);
            geoDbf.Open();
 
            GeoCollection<Feature> results = new GeoCollection<Feature>();
            int recordCount = geoDbf.RecordCount;
            
            // Loop through all of the record.  Remember that records in the DBF and Shapefile are 1 based so they start at 1 not 0
            for (int i = 1; i <= recordCount; i++)
            {
                // Call the function that just gets the one column we want.  
                string trackId = geoDbf.ReadFieldAsString(i, "CURR_TYPE");
                // If the column matches then we go to the shapefile and pull it out by feature Id.  The DBF and Shapefile are always ordered the
                // same so record 5 in the shapefile is always record 5 in the DBF
                if (trackId == "Riyal")
                {
                    results.Add(worldLayer.QueryTools.GetFeatureById(i.ToString(), ReturningColumnsType.AllColumns));
                }
            }
 
            //Close everything
            geoDbf.Close();
            worldLayer.Close();
 

 



Hi David,


This is exactly the answer I was expecting so problem solved and thank you very much. However I am still confused regarding the usage of the GetFeaturesByColumnValue. I mean that if it is that slow then essentially nobody can use it effciently and instead your code must be used. But then why don't you embed your workaround in the code of the  GetFeaturesByColumnValue


A final point is about the confusion with the:


ShapeFileFeatureLayer.BuildIndexFile("xx.shp", "track.idx", "TrackID", "", BuildIndexMode.DoNotRebuild);

ShapeFileFeatureLayer shpLayer = new ShapeFileFeatureLayer("xx.shp","track.idx", ShapeFileReadWriteMode.ReadOnly);


So these two lines have nothing to do with the searching index we are dealing here and instead is "another" kind of index used to "select" certain features from a shape file during presentation. Is this a fair assumption? I tried to combine the above lines with my data hopeing to reduce the size and the iterations but the geoDbf.RecordCount returned the full number of record.


I would really be glad if you could provide an insight.


Kind Regards


Yiannis




 


 



Ioannis, 
  
   I’m glad this is working for you.  We will look to embed the code I provided in method to speed it up.   
  
 The confusion if around what the build index does and what that parameters “ColumnName” and RegularExpression" are for.  The build index creates an spatial R-Tree index for all of the features in the shapefile unless you specify these parameters.  Then what it does it to, for the column name apply the regular expression to match the values in the “ColumnName” field.  If the record matches the regular expression then it is added to the index otherwise it is not.  When you load a shapefile and specify a custom idx file then this will only “find” record in the index file when drawing.  It is most often used to have one shapefile like an “AllRoads” and then create multiple indexes like “Highways.idx” and “LocalRoads.idx” .  This way you can load the shapefile twice with different indexes and they seem like separate shapefiles.  One thing to remember is that while the shapefile drawing uses the idx file the geodbf goes directly against the dbf and doesn’t see the index. 
  
 David

David,


This is great thank you for the explanation :) All is clear and I hope that time spend on this thread helped a lot of people (unless if it had been covered as a topic somewhere else and I missed it... in that case sorry).


I will be looking forward for the version with the "improved search By column value"...


Kind Regards


Yiannis



Ioannis, 
  
   I’m glad I could help.  I have added the optimization to the core now it should be in there for developers build 5.5.78.0 and if not the next version.  Our build process kicks off really soon so it might have been to late.  Some insight into why this was not optimal relates to the design of the core components.  At the base class level like FeatureSource we tried to create as many Core methods with virtual implementations as possible and tried to avoid abstract methods.  The reason is that abstract methods are hard for developers to implement and many times it might be something they don’t care about. If you can provide some kind of implementation via a virtual method then you should.  Imagine if you wanted to create a new feature source based on some proprietary file format.  You inherited from FeatureSource and you saw a bunch of abstract methods you had to implement like GetFeatureByX, GetFeaturesByY etc…  You would pull your hair out.  Instead we boiled it down to just a few and if you do those few then we can deduce the rest.  The default implementation may be slow and use lots of memory but it will be accurate.  In the case of a new feature source you would have to implement a GetAllFeaturesCore method, if you do that we can handle GetFeaturesInsideBoundingBox, GetFeaturesById, GetFeaturesByColumnName, and a bunch more.  If you don’t care about many of these then you just ignore them.  You implement one method and the rest are not optimal but free and they work.  In the real case the GetFeaturesByColumnName for the ShapeFileFeatureSource, it was defaulting to the base class which in effect gets all of the features in memory, loops through them, and just looks at their column values.  Not very optimal but it works.  I went in and overrode the method and used some shapefile specific things like the GeoDbf etc. to make it perform really well.  I think the reason this was overlooked in the first place is that this method, to look things up by column name, was added after the fact and no one bothered to look at the inherited classes and write optimized versions.  This is a bit of a trade off, you get a slow but useful thing for free, but that also mean you might forget to write the optimized ones.  Ok, end of rant. :-) 
  
 David