Kimberly,
I have some sample code for you. The code will go through all of the overlays and then look for layer overlays and under that look for feature layers. I coded two ways of doing this, one with nearest neighbor and one with a tolerance in pixels using the GetRecordsInBoundingBox. The nearest neighbor is slower so I do not recommend that one however it is the only choice if you really need the closes record and the record is very far away from where the user clicked. Most of the time a tolerance in pixels is pretty good and faster anyway.
The methods are pretty self explanatory. I included the code to call them as an example. Just past it in the Desktop Edition sample get feature a user clicked on or something like that in the Getting Started tab. I also get a list of many candidates and near the end of the methods I get the real closest one. This can be fairly slow as we have to inspect every vertex and if you have complex polygons then it might not be good. You could skip that part and just go the first results which might be a few records, but they will all be close.
The reason this is hard to build in by default is that there are many caveats. First what kind of search you want, nearest enighbor etc. The next is do you really want it to search all layers, I mean are you sure? What if you only wanted it to search a few then how would that look. Also it only handles layer and overlay types we know. If you inherit from LayerOverlay or FeatureLayer then you are ok but if you have your own custom layers then there is no way for us to know how to query them. It also doesn't tell you what layer or overlay they came from, this could be very critical and we could enhance the code to do this. It could pass back a structure including the overlay and layer along with the feature. This might be more handy. Many times after you get it back you want to highlight it etc and if the API only got the closest record is that enough? I think there is some good ideas in putting this as a standard map API but we need to think about all the scenerios and not rush to put in something well thought out.
Anyway these are just some of my random thoughts about it. i agree we need something in there for the 80% of cases and for the 20% they need to write code. I think along with this method we need something like a HighlightInteractiveOverlay that has some highlighting stuff built in to make the process really easy. Anyway here is the code.
David
void winformsMap1_MapClick(object sender, MapClickWinformsMapEventArgs e)
{
Feature featureByTolerance = GetNearestFeatureByTolerance(winformsMap1, new ScreenPointF(e.ScreenX, e.ScreenY), 3);
Feature featureNearest = GetNearestFeatureNearestNeighbor(winformsMap1, new ScreenPointF(e.ScreenX, e.ScreenY));
if (featureByTolerance.ColumnValues != null)
{
MessageBox.Show(featureByTolerance.ColumnValues["cntry_name"].ToString(), "By Tolerance", MessageBoxButtons.OK, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1, (MessageBoxOptions)0);
}
if (featureNearest.ColumnValues != null)
{
MessageBox.Show(featureNearest.ColumnValues["cntry_name"].ToString(), "Nearest", MessageBoxButtons.OK, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1, (MessageBoxOptions)0);
}
}
private Feature GetNearestFeatureNearestNeighbor(WinformsMap map, ScreenPointF screenSearchPoint)
{
Collection<Feature> features = new Collection<Feature>();
// Convert the screen point to a world point
PointShape worldSearchPoint = ExtentHelper.ToWorldCoordinate(map.CurrentExtent, screenSearchPoint, map.Width, map.Height);
// Loop through the overlays.
// If you use static and dynamic you would need to change this code a bit
foreach (Overlay overlay in map.Overlays)
{
// Make sure the overlay is a LayerOverlay.
if (overlay is LayerOverlay)
{
foreach (Layer layer in ((LayerOverlay)overlay).Layers)
{
// Make sure the layer in the overlay is a FeatureLayer so we can
// do a spatial query
if (layer is FeatureLayer)
{
FeatureLayer featureLayer = (FeatureLayer)layer;
// Use the GetFeaturesNearestTo. This method has some good and bad sides to it.
// It will get the closest record but if the record is far away it might be slow.
// Also this may not return back the closest record 100% of the time but will return
// very close records. Making the return number (10) higher will be slower but more accurate
Collection<Feature> nearestFeatures = featureLayer.QueryTools.GetFeaturesNearestTo(worldSearchPoint, map.MapUnit, 10, ReturningColumnsType.AllColumns);
foreach(Feature feature in nearestFeatures)
{
// Add the result of that layer to a collection of possible canidates
features.Add(feature);
}
}
}
}
}
// Call this method that will find the real closest feature
// If you remove this line you could have a group of the closest features
// instead of just one
Feature closestFeature = GetClosestFeature(features, worldSearchPoint, map.MapUnit);
return closestFeature;
}
private Feature GetNearestFeatureByTolerance(WinformsMap map, ScreenPointF screenSearchPoint, int pixelTolerance)
{
Collection<Feature> features = new Collection<Feature>();
// Find out how many meters are in a pixel
double pixelDistanceInMeters = ExtentHelper.GetWorldDistanceBetweenTwoScreenPoints(map.CurrentExtent, screenSearchPoint, new ScreenPointF(screenSearchPoint.X - 1, screenSearchPoint.Y - 1), map.Width, map.Height, map.MapUnit, DistanceUnit.Meter);
// Convert the screen point to a world point
PointShape worldSearchPoint = ExtentHelper.ToWorldCoordinate(map.CurrentExtent, screenSearchPoint, map.Width, map.Height);
// Create a search rectangle using the pixel tolerance
RectangleShape searchRectangle = new EllipseShape(worldSearchPoint, pixelDistanceInMeters * pixelTolerance, map.MapUnit, DistanceUnit.Meter).GetBoundingBox();
// Loop through the overlays.
// If you use static and dynamic you would need to change this code a bit
foreach (Overlay overlay in map.Overlays)
{
// Make sure the overlay is a LayerOverlay.
if (overlay is LayerOverlay)
{
foreach (Layer layer in ((LayerOverlay)overlay).Layers)
{
// Make sure the layer in the overlay is a FeatureLayer so we can
// do a spatial query
if (layer is FeatureLayer)
{
FeatureLayer featureLayer = (FeatureLayer)layer;
// Here we find the records that are within the tolerance using
// the fast GetFeaturesInsideBoundingBox. This is going to be faster
// that the nearest neighbor but will not find items far away even if it is the closest
Collection<Feature> nearestFeatures = featureLayer.QueryTools.GetFeaturesInsideBoundingBox(searchRectangle, ReturningColumnsType.AllColumns);
foreach (Feature feature in nearestFeatures)
{
features.Add(feature);
}
}
}
}
}
// Call this method that will find the real closest feature
// If you remove this line you could have a group of the closest features
// instead of just one
Feature closestFeature = GetClosestFeature(features, worldSearchPoint, map.MapUnit);
return closestFeature;
}
private Feature GetClosestFeature(Collection<Feature> features, PointShape worldSearchPoint, GeographyUnit mapUnit)
{
Feature closestFeature = new Feature();
// This there is only one feature then just use it
if (features.Count == 1)
{
closestFeature = features[0];
}
else
{
double shortestDistance = double.MaxValue;
// Loop through all of the features look for the shortest distance
foreach (Feature feature in features)
{
double distance = feature.GetShape().GetDistanceTo(worldSearchPoint, mapUnit, DistanceUnit.Meter);
if (distance < shortestDistance)
{
shortestDistance = distance;
closestFeature = feature;
}
}
}
return closestFeature;
}