Tuesday, February 12, 2008
Posted on Tuesday, February 12, 2008 8:42:51 PM (Mountain Standard Time, UTC-07:00)  Comments [5] | 
Categories: .NET | SharpMap

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

Tuesday, February 12, 2008 9:42:34 PM (Mountain Standard Time, UTC-07:00)
"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"

How more obvious? Did you look at the CHM help file where this is exposed?
anonymous
Wednesday, February 13, 2008 7:48:08 AM (Mountain Standard Time, UTC-07:00)
I was thinking something like:

IProvider.ExecuteAttributeQuery(filter, targetDataSet)

would be a little more obvious. It's just not obvious that you need to jump down to the class implementing IProvider instead of working at the IProvider level.

As for the CHM file for SharpMap, it's a nice condensation of the API, but it is still no more obvious - you still need to look at "ShapeFile" before you'd know that there is a delegate that you can use for filtering.

I'm not saying this is a bad design or anything, just that it's not immediately clear how to use the API.

Cheers,

Dave
Friday, February 29, 2008 5:19:58 AM (Mountain Standard Time, UTC-07:00)
Hi Dave

I was trying your method

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()));
}

);

to findout the country name in the shapefile countries but unable to do so as i was stuck with a few things, first of all where we are declaring the new delegate how do we pass the feature datarow and how do we trap the returned row from the shapefile or table?

Thanks Kamal
Kamal
Friday, February 29, 2008 7:44:18 AM (Mountain Standard Time, UTC-07:00)
Kamal,

You assign the delegate to the FilterDelegate property of the ShapeFileProvider. The provider handles it from there when you call ExecuteIntersectionQuery Look at the source code for the ShapeFileProvider... specifically the ExecuteFeatureQuery method...

if (FilterDelegate == null || FilterDelegate(fdr))
{
result.AddRow(fdr);
}

If a filterDelegate is set, it is passed the data row. You get the filtered rows back as usual from the ExecuteIntersectionQuery call.

I think your question illustrates my point that it is not exactly obvious how this works.

Cheers,

Dave
Monday, March 03, 2008 8:19:27 AM (Mountain Standard Time, UTC-07:00)
Hi Dave

Thanks a lot, i followed the steps you told me here and i am able to fill the dataset with filtered results using ExecuteIntersectionQuery method. Now i want to render the filtered results on the map, which are polygons. What functions I may use to render these attributes in the dataset on the map?

Thanks in advance

Kamal
Kamal
Comments are closed.