I've been doing some work with SharpMap v0.9 (from CodePlex) for a windows forms based mapping tool, and while there are quite a few samples which show how to do basic mapping stuff, it was very difficult to find out how to locate features using an attribute query.
While SharpMap has a very obvious method for doing spatial queries -
IProvider.ExecuteIntersectionQuery(Geometry, targetDataSet)
There is no similar method for attribute queries.
After scouring the SharpMap discussions over at CodePlex, I initially resorted to a bit of a hack - I'd get the FeatureDataTable from the SharpMap.Data.FeatureDataSet, cast it to a System.Data.DataTable, and create a DataView with a filter. This worked for showing the attributes in a grid, but I had no way to get the Features because they are not stored in the DataTable.
I decided to put this on hold for a while and work on some other aspects of the application, and while debugging I was stepping through the ExecuteIntersectionQuery code in the ShapeFile provider and saw this:
for (int i = 0; i < objectlist.Count; i++)
{
SharpMap.Data.FeatureDataRow fdr = dbaseFile.GetFeature(objectlist[i], dt);
fdr.Geometry = ReadGeometry(objectlist[i]);
if (fdr.Geometry != null)
if (fdr.Geometry.GetBoundingBox().Intersects(bbox))
if (FilterDelegate == null || FilterDelegate(fdr))
dt.AddRow(fdr);
}
What was this FilterDelegate? A little searching in the source and found a long comment section by Morten that describes how to use delegates to filter the results returned in a DataSource. You can view the highlighted source here (by the way Koders.com rocks for their indexing of open source code!)
Since this was buried, I thought I'd show a little code on how to do it. But first - if you are not familiar with Delegates, they are essentially pointers to functions. Here are some resources: Wikipedia description, how they relate to events, and MSDN article on them.
Here's how the filter works:
As the ExecuteIntersectionQuery is looping over the dataset, it will pass the current row into the delegate and add the row to the results if the delegate returned true. Once you dig into the source code, it's actually pretty clear what's going on, but it would have been nice for this to be exposed in a more obvious manner.
Implementing A Filter
The straight-forward way to do this is create a static method that do the evaluation and assign that method to the SharpMap.Data.Providers.ShapeFile.FilterMethod . The code snippet below shows the IsOwnerNameSet filter being used.
public void DoFilter()
{
//you will need to get the vectorLayer from somewhere...
SharpMap.Data.Providers.ShapeFile shapeProvider = (SharpMap.Data.Providers.ShapeFile)vectorLayer.DataSource;
shapeProvider.FilterDelegate = IsOwnerNameSet;
SharpMap.Data.FeatureDataSet ds = new SharpMap.Data.FeatureDataSet();
vectorLayer.DataSource.ExecuteIntersectionQuery(vectorLayer.Envelope, ds);
vectorLayer.DataSource.Close();
//Be sure to clear the filter delegate our you won't get anything to draw!
shapeProvider.FilterDelegate = null;
}
public static bool IsOwnerNameSet(SharpMap.Data.FeatureDataRow row)
{
if (row["OWNER"] != null)
{
return true;
}
else
{
return false;
}
}
This works pretty well if your filter criteria are static - like "is the ownername set?" But what if your criteria are changing? Since you can't pass additional arguments to the delegate, the other option is to create static member variables that the delegate will use to determine that match. This has a bit of a code-smell to me, so I ended up using Anonymous methods. In the code snippet below I am creating the anonymous method, and at the same time defining the value it is checking against. In this case I'm searching for a parcel by owner.
public int SelectParcelByOwner(string owner)
{
//Create a delegate
SharpMap.Data.Providers.ShapeFile.FilterMethod filter =
new SharpMap.Data.Providers.ShapeFile.FilterMethod(
delegate(SharpMap.Data.FeatureDataRow row)
{
return(row["OWNER1"].ToString().ToLower().Contains(owner.ToLower()));
}
);
This allows you to specify the value (owner) without resorting to static members.
Hope this helps people just jumping into SharpMap