How to compute the bounding box of a mouse click.

Topics: Algorithms, SharpMap v0.9 / v1.x
Jul 30, 2010 at 11:03 PM

Ever tried to click on a Point or a horizontal or vertical line? The method ExecuteIntersectionQuery is designed to find overlapping bounding boxes. But this only works if one of the boxes has an area. Otherwise, the chance of collision is next to nil. So how do you click on a symbol in a point layer or a horizontal/vertical line in a path layer? You need to increase the size of the mouse click bounding box.

The ImageToWorld method returns a point in world coordinates but, in fact, you clicked on an area the size of a pixel. So technically you should get back a bounding box. The point you get back from ImageToWorld is the upper left corner of the pixel you clicked on so you can compute the bounding box as follows

SharpMap.Geometries.Point lowerLeft = mapImage1.Map.ImageToWorld(new PointF(ImagePos.X, ImagePos.Y + 1));
SharpMap.Geometries.Point upperRight = mapImage1.Map.ImageToWorld(new PointF(ImagePos.X + 1, ImagePos.Y));
SharpMap.Geometries.BoundingBox box = new SharpMap.Geometries.BoundingBox(lowerLeft, upperRight);

Now if you click on a pixel that contains the point or line you are trying to select ExecuteIntersectionQuery actually has a chance of finding it. But what if the line is thicker than one pixel or the point has an image larger than one pixel? Clicking anywhere but the pixel containing the point or line will result in a miss. Note that the following only applies to layers where all lines are the same thickness or all points have the same size image.

If a line or image was 3 pixel wide then you would need to increase the size of your mouse click by one pixel in each direction. For even widths we will round up just to be safe. So to compute the correct bounding box for lines:

int lineWidth = 3;
int delta = lineWidth / 2;
SharpMap.Geometries.Point lowerLeft = mapImage1.Map.ImageToWorld(new PointF(ImagePos.X - delta, ImagePos.Y + delta + 1));
SharpMap.Geometries.Point upperRight = mapImage1.Map.ImageToWorld(new PointF(ImagePos.X + delta + 1, ImagePos.Y - delta));
SharpMap.Geometries.BoundingBox box = new SharpMap.Geometries.BoundingBox(lowerLeft, upperRight);

and for points with images:

int deltaX = image.Width / 2;
int deltaY = image.Height / 2;
SharpMap.Geometries.Point lowerLeft = mapImage1.Map.ImageToWorld(new PointF(ImagePos.X - deltaX, ImagePos.Y + deltaY + 1));
SharpMap.Geometries.Point upperRight = mapImage1.Map.ImageToWorld(new PointF(ImagePos.X + deltaX + 1, ImagePos.Y - deltaY));
SharpMap.Geometries.BoundingBox box = new SharpMap.Geometries.BoundingBox(lowerLeft, upperRight);

 Hope this helps anyone else who has encountered this problem.