ThinkGeo.com    |     Documentation    |     Premium Support

Filtering shape file on several columns

Hi,



I need to filter a shape file on a predicate as in an sql where clause.



e.g filter = "[COL1] IN (1,2) AND [COL2]=3"



Looking through the ShapeFileFeatureSource we have a 

GetFeaturesByColumnValue(…) and an ApplyFilters(…) (didn’t find much spec. on that one in the documentation).



None of those take a simple where clause as input.



Do you have a solution for me?



Thanks,

Peter








Hi Peter, 
  
 GetFeaturesByColumnValue only support pass one column in. 
  
 ApplyFilters looks don’t support the grammar like you mentioned. 
  
 Have you tried this API layer.QueryTools.ExecuteQuery? 
  
 Regards, 
  
 Don

Hi Don,



QueryTools.ExecuteQuery doesn’t seem to do it (actually it didn’t return anything where a featureSource.ExecuteScalar worked on a similar sql where ‘SELECT *’ was replaced with ‘SELECT COUNT(*)’).



My conclusion: QueryTools doesn’t do the job.



I’m looking for a signature a la FeatureSource.GetFeaturesByFilter(filter).



It would also be nice to be able to mix spatial predicates and column predicates in the same query.



thanks,

Peter

Hi Peter,



I agree that QueryTools.ExecuteQuery doesn’t do your job. Here is a clue for you to filter the features by the T-SQL where clause. Please feel free to make changes on my extension.


static class QueryToolExtension
{
    public static Collection<Feature> GetFeaturesByFilter(this FeatureLayer featureLayer, string whereClause, IEnumerable<string> returningColumnNames)
    {
        string sql = string.Format(CultureInfo.InvariantCulture, "SELECT RECID FROM {0} WHERE {1}", featureLayer.Name, whereClause);
        DataTable dataTable = featureLayer.QueryTools.ExecuteQuery(sql);
        string[] ids = dataTable.Rows.Cast<DataRow>().Select(r => r[0].ToString()).ToArray();
        return featureLayer.QueryTools.GetFeaturesByIds(ids, returningColumnNames);
    }
}


With this extension, we could use it like following code (The data is using "USSTATES.shp" in our How Do I Samples). There are three things before using it.


        
  1. Set the name of the FeatureLayer in case we could find the table name.

  2.     
  3. Open the layer.

  4.     
  5. Make sure there is a RECID column in the DBF; actually, most of the DBF files contain this column. If not, we might need to find another column name to indicate the row_number.





ShapeFileFeatureLayer statesLayer = (ShapeFileFeatureLayer)wpfMap1.FindFeatureLayer("StatesLayer");
statesLayer.Name = "USStates";
 
statesLayer.Open();
Collection<Feature> features = statesLayer.GetFeaturesByFilter("PERIMETER > 30 Order by PERIMETER", new[] { "STATE_ID", "STATE_NAME" });
 
Console.WriteLine(features);




Thanks and feel free to let us know if you have more queries.
Howard

Hi Howard,



You hit the soft spot with #3 of your three things before using it.



My DBF files are not guaranteed to have a column with the row_number.



Can this situation be handled with MapSuite?



Thanks,

Peter



Hi Peter,



I think it is possible, but need more memory. I tried some FoxPro Sql (RECNO(), @@IDENTITY, REC_NUMBER()… are all not support) to fetch the record number, but failed. It seems the OLEDB doesn’t support it when loading Dbf files. So I used a workaround to add an id column on the datatable. If we could directly use “select” statement to add the record number column, I’m sure it will be much faster and lower memory usage. Here is a modified version of my extension method.


// param: featureLayer: FeatureSource that is going to be queried.
// param: filter: Filter clause. Doesn’t support “order by”. Because it will mess up the records order.
// param: filterColumnNames: Indicates the column names in the filter statement. You could also parse it from the filter to make the API simpler.
// param: returningColumnNames: Indicates the returning column names.
public static Collection<Feature> GetFeaturesByFilter(this FeatureLayer featureLayer, string filter, IEnumerable<string> filterColumnNames, IEnumerable<string> returningColumnNames)
{
    string sql = @“select {0} from {1}”;
    sql = string.Format(CultureInfo.InvariantCulture, sql, string.Join(", ", filterColumnNames), featureLayer.Name);
    DataTable dataTable = featureLayer.QueryTools.ExecuteQuery(sql);
 
    // Build the TempId column for the DataTable.
    dataTable.Columns.Add(new DataColumn(“TEMPID”, typeof(int)));
    int id = 1;
    foreach (var row in dataTable.Rows.Cast<DataRow>())
    {
        row[“TEMPID”] = id;
        id++;
    }
 
    // Filter the DataTable.
    DataRow[] rows = dataTable.Select(filter);
    string[] ids = rows.Select(r => r[“TEMPID”].ToString()).ToArray();
    return featureLayer.QueryTools.GetFeaturesByIds(ids, returningColumnNames);
}





Here is the code using it.


ShapeFileFeatureLayer statesLayer = (ShapeFileFeatureLayer)wpfMap1.FindFeatureLayer(“StatesLayer”);
statesLayer.Name = “USStates”;
statesLayer.Open();
Collection<Feature> features = statesLayer.GetFeaturesByFilter(“PERIMETER > 30”, new[] { “PERIMETER” }, new[] { “STATE_ID”, “STATE_NAME” });
Console.WriteLine(features);


Hope it helps.



Thanks,
Howard