ThinkGeo.com    |     Documentation    |     Premium Support

IDs with Shapefiles

When I create a feature I either specify an Feature ID or Map Suite assigns one for me.  Examples would be


Dim tempFeature As Feature = New Feature(tempMultiPolygon, "12345") ' results in a feature with an id of "12345"


Dim tempFeature As Feature = New Feature(tempMultiPolygon, "taco") ' results in a feature with an id of "taco"


Dim tempFeature As Feature = New Feature(tempMultiPolygon) ' results in a feature with an id that appears to be a GUID


If I attempt to use any of these IDs with Layer.QueryTools.GetFeatureByID or Layer.FeatureSource.GetFeatureByID, I receive an error message.  It appears that what GetFeatureByID is expecting when querying a Shapefile is a record number and therefore if the value is not able to be parsed as an integer a FormatException is thrown and if the value is able to be parsed as an integer but isn't in between 0 and the number of records in the Shapefile-1 (inclusive) an ArgumentOutOfRange exception is thrown.


My question is - what is an efficient way to query a Shapefile for a particular record/feature when what one knows is the Feature.Id?



This is a good observation.   
  
   In many scenarios this behavior of the new records getting a new Id is fine except for one case and I think this is the case you bring up.  Just to ensure I understand the problem.  You may have some data in a Shape File and a user draws a new shape on the screen, you then edit the Shape File and add this new record.  You gave the Feature an Id of “Taco” and once you commit the transaction you want to re-query the Feature for some purpose or save the Id somewhere to be re queried in the future.  The problem is that you cant query by using “Taco” because that is not the Id that it is saved as. 
  
 This is a tricky question because it is a slight chicken and egg problem.  Most of the time users do not know the FeatureId from the start, they do a spatial query and the Id is returned to them in the Features results of the query.  In this way the FeatureId will be in sync with the Id used in the Shape File.  In your case what is happening is that you are setting the Id of the Feature when it is disconnected from the Shape File though editing to something like “Taco”.  When the editing completes the ShapeFileFeatureSource has changed the FeatureId behind your back to make it consistent with how Shape Files store data in their file format, namely as a number like “12”.  
  
   The root of the issue is that for a Feature, the Id can be a string and there is no way to enforce at that level the Id rules of the underlying FeatureSource.  This would be the same issue in Oracle Spatial where you might set a new Feature’s Id to “Taco” but Oracle Spatial needs a Guid, we will check if the Id you specified is a Guid and if so we use it and if not we create a new Guid and use it. 
  
   One possible solution would be to communicate the name change in the TransactionResults object by providing a translation dictionary.  Whenever you commit a transaction we provide a a return type a TransactionResults.  This results will let you know if the transaction was successful, a list of any errors if any, and some other information.  What if we added a dictionary of re-mapped Ids there.  This dictionary would have a key as the original Id you provided in the Feature before it was saved and then value would be the new assigned value we created.  This way there would be a entry like Key=”Taco” : Value=”45”.  This way after you commit the transaction you can know the new Ids of the Features you committed. 
  
 A second part to this solution would be in creating validation check inside of the ShapeFileFeautreSource to give you, the developer, more hints as to what the API wants.  For example if you use the GetFeatureById and pass in a string like “Taco” to a ShapeFileFeatureSource there should be a validation that the Id is a number and if not the validation exception should ready more like “You have attempted to find a feature using an Id that is not numeric.  Shape Files use Ids that are numeric and start with 0.”  Don’t quote me on the message but you can see where I am going.  We can be more friendlier with the messages. 
  
 What do you think? 
  
 David

I believe I made a mistake in my original posting.  After further experimentation it would appear that the valid range for feature IDs for a shapefile is 1 to the number of records in the Shapefile, not zero to the number of records - 1. 
  
 My first choice would be for Layer.EditTools.Add(Feature) to return the id that will be assigned to the feature rather than just echoing back the id that is already set on the feature - information I already know and could query with feature.id if I didn’t.  
  
 If that is not easy/practical then the easiest thing for me to do is to determine what I should use for an ID and assign it up front.  The way I’m doing that now is Layer.FeatureSource.GetCount() + 1.  This appears to work OK.  Is there some not-too-convoluted instance when this simple logic (that a new record when inserted will be assigned a feature ID one greater than the existing number of records in the Shapefile) will not work for a Shapefile?  What happens to IDs when records are deleted?  For instance, say I have a selection I’m tracking that is ids 1, 5, 10, and 15 and something happens in the program which causes feature with id #9 to be deleted.  Are my ids still valid or do I need to adjust my ids - perhaps they should become 1, 5, 9 and 15?  Does rebuilding indexes invalidate the ids I’m tracking?  Is the ID of a feature stored in a Shapefile ALWAYS the record number?

This is an interesting quandary.  The Layer.EditTools.Add could be modified as you mentioned to return what will be the next number.  It will normally be whatever the last record is +1 as you described.  On the delete side of things what we do for speed is to simple mark the deleted record as shape type null and just leave the data so we do not need to rebuild the file.  This means your record numbers will be constant even if you delete records.  The only exception to this is if you call the ShapeFileFeatureSource.RebuildShapeFile() which is a static method that will pack the shape file and get rid of any deleted records and re-number everything.  The spatial index gets updated on the CommitTransaction and will not mind.  You can rebuild the index any time I think just not the entire shape file.  On a side note we need to add this static method to the ShapeFileLayer as well as I didn’t see it there. 
  
 As you can see the shape file is all about relative record numbers and these can change over time if you rebuild the shape file.  There isn’t really a good mechanism to have a guaranteed unique Id that doest change over time.  The closest thing is like we discussed above where you do the edits to the shape file but never rebuild it.  This I think will give you stability you want but if you re-build it all bets are off. 
  
 I’m sorry I don’t have a better answer for you.  I am really wracking my brain on this but I cant seem think up anything better.  Shape files just have some inherent limitations. 
  
 David

You mention that “The only exception to this is if you call the ShapeFileFeatureSource.RebuildShapeFile()” as the only way the ids would be rebuilt and thereby values I’m storing would be invalid.  That doesn’t quite appear to be the case.  I had a Shapefile with 6 records. After I called  
  
 Layer.EditTools.Delete(“1”) 
 Layer.EditTools.CommitTransaction() 
 Layer.Close() 
 Layer.Open() 
 Layer.BeginTransaction() 
  
 and then attempted to fetch the feature with id=“6” by calling 
 Layer.FeatureSource.GetFeatureById(“6”,tempColumns) 
 I received an ArgumentOutOfRangeException. 
  
 It appears to me that Layer.EditTools.Delete causes the ids to be reassigned just as is done by ShapeFileFeatureSource.RebuildShapeFile().

John, 
  
 Thanks for your probing experiments.  We have fixed this in our next Beta which will be released very soon (maybe on Wednesday).  I think David was looking at the code that hasn’t been released yet. Sorry for the confusion. 
  
 Ben