1

In my map, I have to show annotations from a kml file via an URL. I also need to show only the annotations inside a polygon or circle area (or both of them if the user has both overlays drawn).

I have seen the question How to determine if an annotation is inside of MKPolygonView (iOS), but I have two perplexities:

  1. Regarding the annotation coordinates, should I use the coordinates of the annotations from the addAnnotation method?
  2. In the mentioned question a new overlay is created, but I have two different overlays created elsewhere. So my question is: what is the most suitable place to put this code (or something like that)?

EDIT: I've created some code:

-(IBAction)showKmlData:(id)sender
{
NSString *path = [[NSBundle mainBundle] pathForResource:@"KMLGenerator" ofType:@"kml"];

kml = [[KMLParser parseKMLAtPath:path] retain];

NSArray *annotationsImmut = [kml points];
//[mapview addAnnotations:annotations]; not anymore
NSMutableArray *annotations = [annotationsImmut mutableCopy];
[self filterAnnotations:annotations];

MKMapRect flyTo = MKMapRectNull;

for (id <MKAnnotation> annotation in annotations) {
    MKMapPoint annotationPoint = MKMapPointForCoordinate(annotation.coordinate);
    MKMapRect pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0, 0);
    if (MKMapRectIsNull(flyTo)) {
        flyTo = pointRect;
    } else {
        flyTo = MKMapRectUnion(flyTo, pointRect);
    }
}

mapview.visibleMapRect = flyTo;
}


-(void)filterAnnotations:(NSMutableArray *)annotationsToFilter {

for (int i=0; i<[annotationsToFilter count]; i++) {

    CLLocationCoordinate2D mapCoordinate = [[annotationsToFilter objectAtIndex:i] coordinate];

    MKMapPoint mapPoint = MKMapPointForCoordinate(mapCoordinate);

    MKPolygonView *polygonView = 
        (MKPolygonView *)[mapView viewForOverlay:polygonOverlay];

    MKCircleView *circleView = 
        (MKCircleView *)[mapView viewForOverlay:circleOverlay];

    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 )
            [annotationsToFilter removeObjectAtIndex:i];
}
[mapView addAnnotations:annotationsToFilter];
}

EDIT Nr.2 Here is my implementation of viewForOverlay delegate method.I see the overlays, circles and polygons I create.I see all the annotations.ALL of them, those inside and outside the overlays...

-(MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay 
{
    MKCircleView* circleView = [[[MKCircleView alloc] initWithOverlay:overlay] autorelease];
circleOverlay = circleView;
    circleView.fillColor = [UIColor blueColor];
    circleView.strokeColor = [UIColor blueColor];
    circleView.lineWidth = 5.0;
    circleView.alpha = 0.20;

    MKPolygonView *polygonView = [[[MKPolygonView alloc] initWithOverlay:overlay] autorelease];
polygonOverlay = polygonView;
    polygonView.fillColor = [UIColor blueColor];
    polygonView.strokeColor = [UIColor blueColor];
    polygonView.lineWidth = 5.0;
    polygonView.alpha = 0.20;


if ([overlay isKindOfClass:[MKCircle class]])
{   
    return circleView;
}

else
    return polygonView;

}

Community
  • 1
  • 1
Hari
  • 1,509
  • 15
  • 34
  • So you have added two overlays and you want to check which coordinates out of a list are inside either overlay? In the linked question, it is not creating a new overlay--just getting a reference to an existing overlay's view. –  Nov 15 '11 at 15:04
  • Oh, now I understand that.But what about the annotations? The kml file returns only annotations, how to check if their are inside an overlay? – Hari Nov 15 '11 at 16:36
  • Create the overlays and then loop through the coordinates in the kml and execute that logic for each one. Try it and if any issues, post the code. –  Nov 15 '11 at 16:55
  • For different reasons I cannot try the code until tomorrow, so I've added it for You to check if there are visible problems with it (if possible). – Hari Nov 18 '11 at 13:13

1 Answers1

1

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<MKAnnotation> 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.

  • Thanks a lot!Your modifications are really good and thanks for noticing the problem in the `filterAnnotations` method.The problem with the variable mapview/mapView was a typing error, but you are right, I didn't notice it.As always you were of great help.Thanks again! – Hari Nov 19 '11 at 10:41
  • Just one problem though: where is `MKPolygonView *polygonView = (MKPolygonView *)[mapView viewForOverlay:polygonOverlay];` i get an alert stating: `class 'MKPolygonView' does not implement the 'MKOverlay' protocol`, same for the circleoverlay.What might it be? – Hari Nov 22 '11 at 11:56
  • That's strange because MKPolygonView is not supposed to implement that protocol. Try doing a Clean and re-Build. If there are any other compiler errors or warnings, fix those and it might make this one go away. –  Nov 22 '11 at 13:45
  • There are not other warnings or compiler errors...There is another problem: this doesn't filter annotations...I built a MKolygonOverlay and then "called in" the annotations, but ther were annotations everywhere, not just inside the overlay...Maybe that warning is the cause...I declared polygonOverlay and circleOverlay in the header file, i synthesized them in the .m file and then equalized them with circleView and polygonView objects.Hope not doing anything wrong... – Hari Nov 22 '11 at 17:02
  • That warning doesn't make sense. Show how you declared polygonOverlay and circleOverlay, how you create the overlays and add them to the map by calling addOverlay. Did you implement the viewForOverlay delegate method? Do you see the polygon and circle on the map? By the way, your original if-condition was removing annotations inside the overlays. If you only want annotations that are inside the overlays, reverse the if-condition again. –  Nov 22 '11 at 18:17
  • Your viewForOverlay method is wrong. I'll add details shortly. –  Nov 22 '11 at 22:08
  • Now all works as it should.There were so many problems with my code... But thank you so much!Thank you for your efforts! – Hari Nov 23 '11 at 07:17
  • If you can, please have a look at my question [Regarding Apple's KMLViewer placemarkDescription and annotation subtitle](http://stackoverflow.com/questions/8285153/regarding-apples-kmlviewer-placemarkdescription-and-annotation-subtitle).Thank you very much! – Hari Nov 27 '11 at 18:24
  • Thank you for all you have done.Now my app has everything OK,except one thing that I have observed.I have added one line of code to make possible to show all the pins even if there are not overlays in the map.It works Ok the first time when app loads,when no overlays are drawn already.But after adding overlays and then removing then, no pins are shown in the map.I have to draw an overlay an then pins are shown inside it.The line of code is this `if (![mapview overlays])`and it is set before calling the `filterAnnotations` method.If there is not an overlay on map all the pins should be shown. – Hari Dec 05 '11 at 14:39
  • 1
    Probably should be a new question but try [this answer](http://stackoverflow.com/a/6794087/467105). –  Dec 05 '11 at 14:46
  • I have searched sooo much for that answer.As always it worked, and as always you are great!Thanks! – Hari Dec 05 '11 at 14:55