How to determine if an annotation is inside of MKPolygonView (iOS)
The following converts the coordinate to a CGPoint in the polygon view and uses CGPathContainsPoint to test if that point is in the path (which may be non-rectangular):
CLLocationCoordinate2D mapCoordinate = ...; //user location or annot coord
MKMapPoint mapPoint = MKMapPointForCoordinate(mapCoordinate);
MKPolygonView *polygonView =
(MKPolygonView *)[mapView viewForOverlay:polygonOverlay];
CGPoint polygonViewPoint = [polygonView pointForMapPoint:mapPoint];
BOOL mapCoordinateIsInPolygon =
CGPathContainsPoint(polygonView.path, NULL, polygonViewPoint, NO);
This should work with any overlay view that is a subclass of MKOverlayPathView. You can actually replace MKPolygonView with MKOverlayPathView in the example.
Showing annotations only inside the overlays
Overall, that looks fine but there is a problem in the filterAnnotations
method with how the annotations are being removed from the annotationsToFilter
array.
What will happen is that some annotations will be skipped and never go through the check.
For example:
- Say there are 5 annotations (0=A, 1=B, 2=C, 3=D, 4=E)
- The for-loop starts at index 0 and "A" meets the condition for removal
- "A" is removed from the array and now the other annotations are moved down one index so after the removal the array is: (0=B, 1=C, 2=D, 3=E)
- Now the for-loop goes to the next index which is 1 (so annotation C is checked)
- So annotation B is skipped and is never checked
One way to fix this is to collect the annotations you want to keep in another array "annotationsToAdd" instead of removing them from the original and pass annotationsToAdd to the addAnnotations
method.
Below is the suggested modification. I'd also recommend moving the viewForOverlay
calls outside the for-loop since those references won't change during the loop so there's no need to call them repeatedly.
-(void)filterAnnotations:(NSMutableArray *)annotationsToFilter
{
NSMutableArray *annotationsToAdd = [NSMutableArray array];
//Get a reference to the overlay views OUTSIDE the for-loop since
//they will remain constant so there's no need to keep calling
//viewForOverlay repeatedly...
MKPolygonView *polygonView = (MKPolygonView *)[mapView viewForOverlay:polygonOverlay];
MKCircleView *circleView = (MKCircleView *)[mapView viewForOverlay:circleOverlay];
for (int i=0; i < [annotationsToFilter count]; i++)
{
//get a handy reference to the annotation at the current index...
id currentAnnotation = [annotationsToFilter objectAtIndex:i];
CLLocationCoordinate2D mapCoordinate = [currentAnnotation coordinate];
MKMapPoint mapPoint = MKMapPointForCoordinate(mapCoordinate);
CGPoint polygonViewPoint = [polygonView pointForMapPoint:mapPoint];
CGPoint circleViewPoint = [circleView pointForMapPoint:mapPoint];
BOOL mapCoordinateIsInPolygon = CGPathContainsPoint(polygonView.path, NULL, polygonViewPoint, NO);
BOOL mapCoordinateIsInCircle = CGPathContainsPoint(circleView.path, NULL, circleViewPoint, NO);
if ( !mapCoordinateIsInPolygon && !mapCoordinateIsInCircle )
//Note the reversed if-condition because now
//we are finding annotations we want to KEEP
{
[annotationsToAdd addObject:currentAnnotation];
}
}
[mapView addAnnotations:annotationsToAdd];
}
Also, I noticed that in the showKmlData
method you are using the variable mapview
but in filterAnnotations
it's mapView
(uppercase V
). Hopefully, the compiler will give you a warning about that.
Additional Info:
Based on your comments and the
viewForOverlay
code you added to your question...First, the compiler warning class 'MKPolygonView' does not implement the 'MKOverlay' protocol
that you are getting implies that the variables polygonOverlay
and circleOverlay
are declared as MKPolygonView
and MKCircleView
instead of MKPolygon
and MKCircle
.
Second, the code in the viewForOverlay
delegate method is wrong. It tries to create both a circle and polygon view for whatever overlay
is passed in and then checks what kind of class the overlay is. It also seems to be saving a reference to the overlay view but the rest of the code assumes that we are keeping a reference to the overlay (the MKOverlay
object--not the MKOverlayView
).
Try the following changes...
//polygonOverlay and circleOverlay should be declared as MKOverlay objects...
@property (nonatomic, retain) MKPolygon *polygonOverlay;
@property (nonatomic, retain) MKCircle *circleOverlay;
//save a reference to them when you call addOverlay...
self.polygonOverlay = [MKPolygon polygonWithCoordinates:polyCoords count:coordsCount];
[mapView addOverlay:polygonOverlay];
self.circleOverlay = [MKCircle circleWithCenterCoordinate:cirleCenter radius:circleRadius];
[mapView addOverlay:circleOverlay];
//the viewForOverlay delegate method...
-(MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay
{
if ([overlay isKindOfClass:[MKCircle class]])
{
MKCircleView* circleView = [[[MKCircleView alloc] initWithOverlay:overlay] autorelease];
circleView.fillColor = [UIColor blueColor];
circleView.strokeColor = [UIColor blueColor];
circleView.lineWidth = 5.0;
circleView.alpha = 0.20;
return circleView;
}
else
if ([overlay isKindOfClass:[MKPolygon class]])
{
MKPolygonView *polygonView = [[[MKPolygonView alloc] initWithOverlay:overlay] autorelease];
polygonView.fillColor = [UIColor blueColor];
polygonView.strokeColor = [UIColor blueColor];
polygonView.lineWidth = 5.0;
polygonView.alpha = 0.20;
return polygonView;
}
return nil;
}
You also mention in your edit that "I see the overlays, circles and polygons". That sounds like you are creating more than one circle and/or polygon overlay. In that case, having just one polygonOverlay and circleOverlay variable won't work.
If you really do have multiple overlays of each type, then don't store references to the overlays. Instead, in the filterAnnotations
method, for each annotation, loop through the mapView.overlays
array and execute the viewForOverlay
and point-in-polygon test in the nested loop.
Detecting a point in a MKPolygon broke with iOS7 (CGPathContainsPoint)
For some reason (possibly a bug), the path
property returns NULL
in the current release of iOS 7.
A workaround is to construct your own CGPathRef
from the points
of the polygon.
With this method, you don't need a reference to the MKPolygonView
or the MKPolygonRenderer
.
For example:
CGMutablePathRef mpr = CGPathCreateMutable();
MKMapPoint *polygonPoints = myPolygon.points;
//myPolygon is the MKPolygon
for (int p=0; p < myPolygon.pointCount; p++)
{
MKMapPoint mp = polygonPoints[p];
if (p == 0)
CGPathMoveToPoint(mpr, NULL, mp.x, mp.y);
else
CGPathAddLineToPoint(mpr, NULL, mp.x, mp.y);
}
CGPoint mapPointAsCGP = CGPointMake(mapPoint.x, mapPoint.y);
//mapPoint above is the MKMapPoint of the coordinate we are testing.
//Putting it in a CGPoint because that's what CGPathContainsPoint wants.
BOOL pointIsInPolygon = CGPathContainsPoint(mpr, NULL, mapPointAsCGP, FALSE);
CGPathRelease(mpr);
This should work on iOS 6 as well.
However, you may want to do this manual construction only if the overlay view's path
property returns NULL
.
Check if a point on map lies within an overlay
Well, I figured it out finally. The code above should work as is, except that there is a missing break statement in the loop. The code as is returns only the last checked point of the poly. Inserting a test to mapCoordinateIsInPolygon and then a break statement in the inner loop leaves the loop as soon as the first test is positive, thus giving the correct result. ;-)
How to determine if an GMSMarker is inside of GMSPolygon (iOS Google Map SDK)
I got the answer.
There is an inline function defined in GMSGeometryUtils.h file in Google SDK:
//Returns whether |point|,CLLocationCoordinate2D lies inside of path, GMSPath
BOOL GMSGeometryContainsLocation(CLLocationCoordinate2D point, GMSPath *path,
BOOL geodesic);
It contains a function called GMSGeometryContainsLocation, which determines whether location point lies inside of polygon path.
if (GMSGeometryContainsLocation(locationPoint, polygonPath, YES)) {
NSLog(@"locationPoint is in polygonPath.");
} else {
NSLog(@"locationPoint is NOT in polygonPath.");
}
Source: GMSGeometryContainsLocation
How to retrieve Map Annotation coordinates?
The simplest way is to store a reference to the annotation when you add it.
For example, you could declare a retain property called pinToRemember
of type myAnnotation
and after adding the annotation, do this to save it for later:
[mapview addAnnotation:pinLoc];
self.pinToRemember = pinLoc;
Then in findPin:
, you would do:
if (pinToRemember == nil)
return;
region.center = pinToRemember.coordinate;
Alternatively, you could search through the map view's annotations
array property and look for the annotation you are interested in by looking for its title
or some other custom property. This could be done by using a simple for-loop.
You say you have the "way to tell if the pin is within a polygon" working but this answer may also be useful:
How to determine if an annotation is inside of MKPolygonView (iOS)
Related Topics
Detecting Sheet Was Dismissed on iOS 13
Replacement For Deprecated -Sizewithfont:Constrainedtosize:Linebreakmode: in iOS 7
How Does View Controller Containment Work in iOS 5
Swrevealviewcontroller Without Using Navigationcontroller
Get Notified When Uitableview Has Finished Asking for Data
Converting Hex String to Nsdata in Swift
How to Open PDF File Using Uiwebview on iOS
iOS 7: Mpmusicplayercontroller Volume Deprecated. How to Change Device Volume Now
How to Detect Whether a User Has an iPhone 6 Plus in Standard or Zoomed Mode
Determine on Iphone If User Has Enabled Push Notifications
How to Programmatically Sense the Iphone Mute Switch
Proper Use of Beginbackgroundtaskwithexpirationhandler
Programmatically Align a Toolbar on Top of the Iphone Keyboard
Set Initial Viewcontroller in Appdelegate - Swift
Outlets Cannot Be Connected to Repeating Content Ios
Uitapgesturerecognizer Breaks Uitableview Didselectrowatindexpath