How to select feature by attribute?

Topics: Algorithms, Data Access, General Topics, SharpMap v0.9 / v1.x
Editor
Feb 5, 2010 at 8:23 PM

I've been trying to wrap my head around SharpMap v0.9.  I'm trying to incorporate it into a .Net 2.0 WinForms application using Visual Studio 2005 Pro. 

What I haven't been able to fathom is how to select a feature based on an attribute.

I would like the user to enter a string and search the displayed layer for any records that match that value in a particular attribute.  Currently I am using shape files.  So far, the only thing I can come up with is to loop through the entire layer checking every record for ones that match the query string.

Is there a better way to do this (I hope)?

I was looking for something like a .Find(<SQL Where Predicate>) that would return a feature data table.

Any thoughts?

Using SharpMap.dll v0.9.2423.31552

 

Thanks,

rick

 

 

 

 

 

Coordinator
Feb 6, 2010 at 2:49 AM

Hi rick, set the FilterDelegate before querying

shapefileProivder.FilterDelegate =  delegate(FeatureDataRow row){ return row["somecolumn"] = "Tokyo";}

hth jd

Editor
Feb 9, 2010 at 6:57 PM

john,

Thanks for all your help so far. 

 

I've attempted to select a single polygon in a shape file and display the selected polygon using a FilterDelegate  Unfortunately I don't get anything to draw on the map.  Below is the code for event of a button that takes the text the user enters and tried to find the matching polygon.

I've based it on your previous posting;

"Hi siddhesh123, you dont need to executeintersectionquery yourself unles you are trying to get the data for soe non rendering purpose..

the following should run- but I havent tested it

SharpMap.Map sharp = new SharpMap.Map(pictureBox1.Size);
ShapeFile sf = new ShapeFile(path);

sf.FilterDelegate = delegate(FeatureDataRow row)
{
return row["Name"] == "11";
};

SharpMap.Layers.VectorLayer testlayer = new SharpMap.Layers.VectorLayer("TestLayer");

testlayer.DataSource = sf;
sharp.Layers.Add(testlayer); // Adds layer to sharp object
sf.Open();
sharp.ZoomToExtents();

pictureBox1.Image = sharp.GetMap(); // diplay"

 

Is there something else I am forgetting?

 

Thanks again,

rick.

 

/* ----- Code below ------ */

private void searchButton_Click(object sender, EventArgs e)
        {
            //////search against lotid [field 0] based on the contents of the text box
            
            string searchText = this.LotIDText.Text;

            Console.WriteLine(searchText);

            int lotID = Convert.ToInt32(searchText);


            //Delete the previous 'Results' layer
            for (int i = 0; i < map.Layers.Count; i++)
            {
                Console.WriteLine(map.Layers[i].LayerName);
                //if (map.Layers[i].LayerName == "Results")
                //{
                    map.Layers.RemoveAt(i);
                //}
            }

            //User wants to Select a feature by LotId
            Console.WriteLine("entering ResultsLayer");

            ShapeFile sf = new ShapeFile(PATH);

            Console.WriteLine("Found {0} Features in Results Layer Prefilter", sf.GetFeatureCount());

            sf.FilterDelegate = delegate(FeatureDataRow row)
            {
                return (int) row["lotid"] == lotID;
            };


            SharpMap.Layers.VectorLayer testLayer = new SharpMap.Layers.VectorLayer("Results");
            testLayer.DataSource = sf;

            Console.WriteLine("Found {0} Features in Results Layer", testLayer.DataSource.GetFeatureCount());

            testLayer.Style.Fill = new System.Drawing.SolidBrush(System.Drawing.Color.Red);
            testLayer.Style.Outline = Pens.Yellow;
            testLayer.Style.EnableOutline = true;

            testLayer.SRID = 102683;

            Console.WriteLine("finished making ResultsLayer");

            //Add it to the map
            map.Layers.Add(testLayer); //map is a gloabal variable which type is "Map"
            sf.Open();

            RefreshMap();

            Console.WriteLine("Redrew after SearchBy Attribute");
        }

 

private void RefreshMap()
        {
            this.ImageMainMap.Image = map.GetMap(); //Renders the map
            this.ZoomValLabel.Text = this.map.Zoom.ToString();
        }

Editor
Feb 9, 2010 at 8:54 PM
Edited Feb 9, 2010 at 8:56 PM

john,

 

After much trial and error I've managed to get 'select by attribute' working.

There doesn't seem to be a way to get to the FilterAttribute from an existing layer, so I have to pass around the PATH to the shapefile in question.

I'll probably also have to make a few more variants for looking up string, or double, or bools, with different signatures for the various types.  Times like these I miss optional arguments.

It feels cumbersome, having to go all the way back to the provider and executing a 'dummy' spatial query to get around the fact that ShapeMap (v0.9 at least) doesn't support non-spatial queries.

If there's a cleaner way that this can be done [in VS 2005 / .Net 2.0] please let me know.

 

Thanks again,

 

rick.

//* -----    code ------- */

public SharpMap.Layers.VectorLayer ResultsLayer(string path, string attrib, int lotID)
        {
                      
            //* New Code to search by attribute
            // Version to look up ints.

            ShapeFile sf = new ShapeFile(path);

            sf.FilterDelegate = delegate(FeatureDataRow row)
            {
                return Convert.ToInt32(row[attrib]) == lotID;
            };

            SharpMap.Data.FeatureDataSet ds = new SharpMap.Data.FeatureDataSet();

            sf.Open();
            SharpMap.Geometries.BoundingBox bbox = sf.GetExtents();
            sf.ExecuteIntersectionQuery(bbox, ds);
            sf.Close();

            SharpMap.Data.FeatureDataTable tbl = ds.Tables[0] as SharpMap.Data.FeatureDataTable;
            SharpMap.Layers.VectorLayer testLayer = new SharpMap.Layers.VectorLayer("Results");
            testLayer.DataSource = new SharpMap.Data.Providers.GeometryFeatureProvider(tbl);

            return testLayer;  
        }

 

Coordinator
Feb 9, 2010 at 10:29 PM

Hello Rick,

I don't know how addicted you are to Shapefiles. If you could consider a different provider
(e.g. PostgreSql/Postgis, SqlServer2008, SpatiaLite, ...) you can use the DefinitionQuery
property which adds an optional where clause to the spatial select command.

Another possibility might be the use of the Select(...) command of the FeatureDataTable class.
True intersection test with NTS would look like this:

        public FeatureDataTable GetIntersectingFeaturesUsingFilterDelegate(string pathToShapefile, Geometry testGeometry)
        {
            //create a new shapefile provider 
            using (ShapeFile shapefile = new ShapeFile(pathToShapefile))
            {
                //create an nts GeometryFactory
                GeometryFactory geometryFactory = new GeometryFactory();

                //convert the testGeometry into the equivalent NTS geometry
                GisSharpBlog.NetTopologySuite.Geometries.Geometry testGeometryAsNtsGeom =
                    GeometryConverter.ToNTSGeometry(testGeometry, geometryFactory);

                
                //set the shapefile providers' FilterDelegate property to a new anonymous method
                //this delegate method will be called for each potential row
                shapefile.FilterDelegate = delegate(FeatureDataRow featureDataRow)
                                               {
                                                   //get the geometry from the featureDataRow
                                                   Geometry rowGeometry = featureDataRow.Geometry;
                                                   //convert it to the equivalent NTS geometry
                                                   GisSharpBlog.NetTopologySuite.Geometries.Geometry
                                                       compareGeometryAsNtsGeometry =
                                                           GeometryConverter.ToNTSGeometry(rowGeometry,
                                                                                           geometryFactory);
                                                   //do the test. Note that the testGeometryAsNtsGeometry is available here because it is 
                                                   //declared in the same scope as the anonymous method.
                                                   bool intersects =
                                                       testGeometryAsNtsGeom.Intersects(compareGeometryAsNtsGeometry);
                                                   //return the result
                                                   return intersects;
                                               };
                

                //create a new FeatureDataSet
                FeatureDataSet featureDataSet = new FeatureDataSet();
                //open the shapefile
                shapefile.Open();
                //call ExecuteIntersectionQuery. The FilterDelegate will be used to limit the result set
                shapefile.ExecuteIntersectionQuery(testGeometry, featureDataSet);
                //close the shapefile
                shapefile.Close();
                //return the populated FeatureDataTable
                FeatureDataTable fdt = featureDataSet.Tables[0];
                FeatureDataTable fdtSelect = fdt.Clone();
                fdtSelect.BeginLoadData();
                foreach (DataRow row in fdt.Select("NUM=1"))
                    fdtSelect.ImportRow(row);
                fdtSelect.EndLoadData();
                return fdtSelect;
            }
        }

I havn't tested this for performance though.

Hth FObermaier

Editor
Feb 10, 2010 at 3:16 PM

Felix,

 

Thanks for the suggestion.  Your code looks nice.  What I am interested in though is pure non-spatial queries.  Apparently SharpMap v0.9 (with your help I'm using 0.9.3691.27741) can't actually do a non-spatial query.  There doesn't seem to be a way to get the FilterDelegate to actual run unless it's part of the ExecuteIntersectionQuery, what would be nice would be an ExecuteQuery method.  To make matters even more frustrating, it doesn't look like there's a way to access the 'live' FeatureDataTable of a displayed layer in any significant way.  You can't take an existing layer and select one or more geometries, even if it's a spatial query (ExecuteIntersectionQuery) within that layer.  You need to create yet another layer for every selection set.   SharpMap layers are apparently very, painfully, static.   I've written code so that every time the user does a selection (spatial or not) I delete the previous layer and replace it with the current selection.  Being as this is .Net code and that the FeatureDataSet inherits from the DataSet (I assume) I would have expected to be able to utilize at least some of the underlying ADO.Net query capabilities.  Unfortunately I get the feeling that even if I could, the only time I would be able to do anything with it would be at creation.  Oh well, perhaps v2.0 will be more dynamic.

As for shape files, the project I am working on at the moment will allow the user to edit a large quantity of non-spatial data in a series of related tables.  The only spatial component is to allow the users the ability to optionally see the location of the data they are editing (hence the need for a non-spatial query - to sync the display to the record the user is currently editing) or review/edit the data of a location that they select spatially.  The spatial data will be created and maintained in ESRI's ArcGIS.  There is a requirement not to install anything other than the program itself.  If SharpMap supported ESRI's personal geodatabase I would have been tempted to use that.  Of the options listed here:

"http://sharpmap.codeplex.com/wikipage?title=Data%20formats%20supported&referringTitle=Home"    

Shape file seemed to be the simplest that wouldn't require installing anything on the client's machine.

SpatialLite looks promising, if it can support/load geometry from ESRI and handle the non-spatial data as well, (is it well supported by .Net 2.0) we might have a winner.

When I was looking to support my modest spatial needs, SharpMap looked like an adequate .Net solution.  ESRI's licensing costs would have been prohibitive for a small project like this with it's minimalistic spatial requirements.  

 

Thanks again for all the help you've been providing.

 

 

 

 

Coordinator
Feb 10, 2010 at 5:53 PM
Edited Feb 10, 2010 at 5:55 PM

Hello Rick,

for plain queries you can certainly use Microsofts OleDb provider to access the dbase file related
to the shapefile in question (see this). You might even be able to update your data, which you can't otherwise
do with sharpmap v0.9.

SpatiaLite has just recently been added to the project, I can't say how many other people use it. It
is a native library loaded into the SQLite.Net Data provider.

In order to use SpatiaLite with sharpmap, you need to download the windows binaries from here,
unzip them all to one and the same folder and update the path variable to include that directory.
Then you should be all set to use the provider. There are gui/commandline tools that are capable
of loading shapefiles into spatialite/sqlite databases. As spatialite is an extension to sqlite you
can add non-spatial data to the database as well.

Perhaps you want to join us at the weekly irc,. It is usually wednesday ~9PM CET.

Hth

FObermaier

Editor
Feb 10, 2010 at 6:59 PM

Felix,

Thanks for the invite, but that's about 3PM EST (where I'm at) and they don't let us access the IRC from the office. :(

Although that explains why, the few times I've dropped in, no one's been home.

I'm using the native methods (ADO.Net) to edit the non spatial data.  In this instance the only attribute the shape file has is a unique key in the form of a long int.  I need to non-spatially query the shape file to display/pan/zoom to the correct geometry.  Presently if I query the shape file using ADO.Net I can't have SharpMap select or display the correct geometry, nor can it pan or zoom to the resultant set.  SharpMap's map is blissfully ignorant of any ADO.Net operations performed on the spatial data.  Perhaps I've just got an ESRI bias, having worked from Arc/Info -> ArcView 3.x -> MapObjects -> ArcGIS (ArcObjects).  I still think it's important for SharpMap to be able to access/reselect/etc. geometry non-spatially as well as spatially.  It would also help if the data wasn't so static.  If you could apply a DefQuery or FilterDelegate to a layer already attached to a map and have it update on the next Refresh().  An associated 'IsSelected' bit mask would be really handy as well.  True for currently selected records, false otherwise.   Then you could display geometry with whatever renderer for those records whose IsSelected is False and define a separate Selected symbology for those geometries where IsSelected is True.  Just think about how much faster/simpler selecting geometries would be.  You would load a layer into your map, the bit mask would be all 0's and so it would draw as defined.  You would use either an SQL query, a spatial query (ExecuteIntersectionQuery, etc.) or both, to select a subset and reset the mask for those values to '1'.  On the next refresh '0' draws the same, '1' is drawn highlighted.  ZoomToSelected, PanToSelected, etc. would be operations that would be based on IsSelected = '1'.  No need to reload layers from disk.  No need to delete layers from the map, only to add the same layer back in.  Simple, clean, fast.

Well, perhaps the developers are already working on that in v2.0.

I've taken your advice and started looking at the MapBox as opposed to the MapImage.  I'm surprised the default wasn't to build both MapImage and MapBox.  It's definitely much faster than the MapImage.  Would you have any sample code that illustrates the use of the MapBox/MapBox tools?  The default demos all use the MapImage.

 

Thanks again for your help,

rick.