I'm developing an app with a map in which the user can draw a polygon area.
My issue is what it's possible drawing polygons with knots (see the image) (I don't know if knot is the right word). I didn't find a simply way preventing the polygon to get knots.
For the case of the attached image, I would like the small curl to be removed and even the outline to be smoothed
Do you know a way to make that?
The process of drawing the polygon while the user is touching the screen, does use MKPolyline, MKPolygon and MKOverlay as follows:
- (void)touchesBegan:(UITouch*)touch
{
CGPoint location = [touch locationInView:self.mapView];
CLLocationCoordinate2D coordinate = [self.mapView convertPoint:location toCoordinateFromView:self.mapView];
[self.coordinates addObject:[NSValue valueWithMKCoordinate:coordinate]];
}
- (void)touchesMoved:(UITouch*)touch
{
CGPoint location = [touch locationInView:self.mapView];
CLLocationCoordinate2D coordinate = [self.mapView convertPoint:location toCoordinateFromView:self.mapView];
[self.coordinates addObject:[NSValue valueWithMKCoordinate:coordinate]];
}
- (void)touchesEnded:(UITouch*)touch
{
CGPoint location = [touch locationInView:self.mapView];
CLLocationCoordinate2D coordinate = [self.mapView convertPoint:location toCoordinateFromView:self.mapView];
[self.coordinates addObject:[NSValue valueWithMKCoordinate:coordinate]];
[self didTouchUpInsideDrawButton:nil];
}
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
{
MKOverlayPathView *overlayPathView;
if ([overlay isKindOfClass:[MKPolygon class]])
{
// create a polygonView using polygon_overlay object
overlayPathView = [[MKPolygonView alloc] initWithPolygon:overlay];
overlayPathView.fillColor = [UIColor redColor];
overlayPathView.lineWidth = 1.5;
return overlayPathView;
}
else if ([overlay isKindOfClass:[MKPolyline class]])
{
overlayPathView = [[MKPolylineView alloc] initWithPolyline:(MKPolyline *)overlay];
overlayPathView.fillColor = [UIColor redColor];
overlayPathView.lineWidth = 3;
return overlayPathView;
}
return nil;
}
MKOverlayPathView was deprecated since iOS 7.0. You'd use MKOverlayRenderer instead of it and also related map delegate method.
Try to play with miterLimit property of MKOverlayRenderer.
Example:
-(MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay {
if ([overlay isKindOfClass:[MKPolygon class]]) {
MKPolygonRenderer *polygonRenederer = [[MKPolygonRenderer alloc] initWithPolygon:overlay];
polygonRenederer.fillColor = [UIColor redColor];
polygonRenederer.lineWidth = 1.5;
polygonRenederer.miterLimit = 10;
return polygonRenederer;
} else if ([overlay isKindOfClass:[MKPolyline class]]) {
MKPolylineRenderer *lineRenderer = [[MKPolylineRenderer alloc] initWithPolyline:overlay];
lineRenderer.strokeColor = [UIColor redColor];
lineRenderer.lineWidth = 3;
return lineRenderer;
}
return nil;
}
Related
I'm struggling with a issue related to MapKit.
I create a list of MKPolygon based on my geofence's data from the server.
+ (MKPolygon *)polygonFromPoints:(NSArray *)points interiorPolygons:(NSArray *)polygons{
NSInteger numberOfCoordinates = [points count];
CLLocationCoordinate2D *polygonPoints = malloc(numberOfCoordinates * sizeof(CLLocationCoordinate2D));
NSInteger index = 0;
for (NSArray *pointArray in points) {
polygonPoints[index] = CLLocationCoordinate2DMake([pointArray[1] floatValue], [pointArray[0] floatValue]);
index++;
}
MKPolygon *polygon;
if (polygons) {
polygon = [MKPolygon polygonWithCoordinates:polygonPoints count:numberOfCoordinates interiorPolygons:polygons];
} else {
polygon = [MKPolygon polygonWithCoordinates:polygonPoints count:numberOfCoordinates];
}
free(polygonPoints);
return polygon; }
And add it to the map as MKOverlayRender
- (MKOverlayRenderer *) mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay{
if([overlay isKindOfClass: [MKCircle class]]){
MKCircleRenderer *circleRender = [[MKCircleRenderer alloc] initWithCircle:(MKCircle *)overlay];
circleRender.fillColor = [ [Common colorWithHexString:BlueGeoFence] colorWithAlphaComponent:0.3];
return circleRender;
}else if([overlay isKindOfClass: [MKPolygon class]]){
MKPolygonRenderer *polygonRenderer = [[MKPolygonRenderer alloc] initWithPolygon:(MKPolygon *)overlay];
polygonRenderer.fillColor = [ [Common colorWithHexString:BlueGeoFence] colorWithAlphaComponent:0.3];
return polygonRenderer;
}
return nil;
}
However, when i zoom in or change the map's position the overlays are cut and have some kind of blur effect.
Any idea how to solve this?
Thanks in advance
I was having the same issue. The source of the problem was that MKPolygon was being created with interior polygons that were not actually interior polygons. Check your data and ensure that your interior polygon coordinates are actually within the bounds of your larger polygon.
In my situation I have to placed more than one polygon as the overlay on the MKMapView, those polygon are actually creating from the JSON response, the response actually containing the polygon_id along with coordinate for the to form that polygon. I just want to some how merge that polygon_id with the overlay, so that whenever user clicked on that overlay it will return the polygon_id.
This is my code:
-(void)darwPolyGon:(NSMutableArray *)polyArr polyGonAreaId:(NSString
*)areaID isAssign:(BOOL)isAssign{
CLLocationCoordinate2D *coordinates =
(CLLocationCoordinate2D*)malloc(sizeof(CLLocationCoordinate2D) *
[polyArr count]);
for (int i=0; i<polyArr.count; i++) {
ModelPolygon *poly=[polyArr objectAtIndex:i];
coordinates[i] = CLLocationCoordinate2DMake(poly.lat,poly.lon);
}
MKPolygon *polygon = [MKPolygon polygonWithCoordinates:coordinates
count:polyArr.count];
strTapAreaId=areaID;
polygon.title=strTapAreaId;
[MyMapView addOverlay:polygon];
}
-(MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id
<MKOverlay>)overlay
{
if([overlay isKindOfClass:[MKPolygon class]]){
MKPolygonView *viewPoly = [[MKPolygonView alloc]
initWithOverlay:overlay];
viewPoly.lineWidth=3;
if (isAssignUser) {
viewPoly.strokeColor=[UIColor colorWithRed:255/255.0f
green:30/255.0f blue:0/255.0f alpha:1.0f];
}else
viewPoly.strokeColor=[UIColor colorWithRed:132/255.0f
green:0/255.0f blue:255/255.0f alpha:1.0f];
viewPoly.tag=[strTapAreaId integerValue];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(handleMapTap:)];
tap.cancelsTouchesInView = NO;
tap.numberOfTapsRequired = 1;
[MyMapView addGestureRecognizer:tap];
return viewPoly ;
}
return nil;
}
-(void)handleMapTap:(UIGestureRecognizer*)tap{
CGPoint tapPoint = [tap locationInView:MyMapView];
CLLocationCoordinate2D tapCoord = [MyMapView convertPoint:tapPoint
toCoordinateFromView:MyMapView];
MKMapPoint mapPoint = MKMapPointForCoordinate(tapCoord);
CGPoint mapPointAsCGP = CGPointMake(mapPoint.x, mapPoint.y);
for (id<MKOverlay> overlay in MyMapView.overlays) {
if([overlay isKindOfClass:[MKPolygon class]]){
MKPolygon *polygon = (MKPolygon*) overlay;
CGMutablePathRef mpr = CGPathCreateMutable();
MKMapPoint *polygonPoints = polygon.points;
for (int p=0; p < polygon.pointCount; p++){
MKMapPoint mp = polygonPoints[p];
if (p == 0)
CGPathMoveToPoint(mpr, NULL, mp.x, mp.y);
else
CGPathAddLineToPoint(mpr, NULL, mp.x, mp.y);
}
if(CGPathContainsPoint(mpr , NULL, mapPointAsCGP, FALSE)){
MKPolygonView *viewPoly = [[MKPolygonView alloc]
initWithOverlay:overlay];
NSLog(#"tag=%d",viewPoly.tag);
}
CGPathRelease(mpr);
}
}
}
The only methods I could think off are:
1- You can try sneaking in the overlay's ID in its "title" or "subtitle" attribute
2- You can create a class that holds and MKOverlay and its ID and whenever it is selected you can loop over your array of overlays(the ones inside the class) and you add an if statement in case the selected overlay is equal to the overlay in the loop, return its ID
In my app I have a MapView with a few MKGeodesicPolylines. I want to be able to recognize touch gestures on these lines.
The overlay are added using:
[_mapView addOverlay:polyline];
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id < MKOverlay >)overlay
{
if ([overlay class] == [MKGeodesicPolyline class])
{
MKPolylineRenderer *renderer = [[[MKPolylineRenderer alloc] initWithPolyline:overlay] autorelease];
renderer.lineWidth = 4.0;
renderer.strokeColor = [UIColor blackColor];
return renderer;
}
return nil;
}
I have tried WildcardGestureRecognizer which should fit my purpose. Here is the code I am using:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
.... MapView init ....
WildcardGestureRecognizer * tapInterceptor = [[WildcardGestureRecognizer alloc] init];
tapInterceptor.touchesBeganCallback = ^(NSSet * touches, UIEvent * event) {
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:_mapView];
CLLocationCoordinate2D coord = [_mapView convertPoint:point toCoordinateFromView:self.mapView];
MKMapPoint mapPoint = MKMapPointForCoordinate(coord);
for (id overlay in _mapView.overlays)
{
if ([overlay isKindOfClass:[MKGeodesicPolyline class]])
{
MKGeodesicPolyline *poly = (MKGeodesicPolyline*) overlay;
id view = [_mapView viewForOverlay:poly];
NSLog(#"view class: %#",[view class]);
if ([view isKindOfClass:[MKPolylineRenderer class]])
{
MKPolylineRenderer *polyView = (MKPolylineRenderer*) view;
CGPoint polygonViewPoint = [polyView pointForMapPoint:mapPoint];
BOOL mapCoordinateIsInPolygon = CGPathContainsPoint(polyView.path, NULL, polygonViewPoint, NO);
if (mapCoordinateIsInPolygon) {
NSLog(#"hit!");
} else {
NSLog(#"miss!");
}
}
}
}
};
[_mapView addGestureRecognizer:tapInterceptor];
}
return self;
}
The problem is the point of the code above where the first Log gets called. The class always seems to return null. Log output:
2014-01-06 13:50:41.106 App[11826:60b] view class: (null)
I hope somebody could point me into the right direction.
Thanks a lot in advance!
WORKING CODE:
I got it working for me. Hope it helps somebody else:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
.... MapView init ....
WildcardGestureRecognizer * tapInterceptor = [[WildcardGestureRecognizer alloc] init];
tapInterceptor.touchesBeganCallback = ^(NSSet * touches, UIEvent * event) {
CGPoint point = [tapInterceptor locationInView:_mapView];
CLLocationCoordinate2D coord = [_mapView convertPoint:point toCoordinateFromView:self.mapView];
MKMapPoint mapPoint = MKMapPointForCoordinate(coord);
for (id overlay in _mapView.overlays)
{
if ([overlay isKindOfClass:[MKGeodesicPolyline class]])
{
MKGeodesicPolyline *poly = (MKGeodesicPolyline*) overlay;
id view = [_mapView viewForOverlay:poly];
NSLog(#"view class: %#",[view class]);
if ([view isKindOfClass:[MKPolylineRenderer class]])
{
MKPolylineRenderer *polyView = (MKPolylineRenderer*) view;
[polyView invalidatePath];
CGPoint polygonViewPoint = [polyView pointForMapPoint:mapPoint];
BOOL mapCoordinateIsInPolygon = CGPathContainsPoint(polyView.path, NULL, polygonViewPoint, NO);
if (mapCoordinateIsInPolygon)
{
NSLog(#"hit!");
} else {
NSLog(#"miss!");
}
}
}
}
};
[_mapView addGestureRecognizer:tapInterceptor];
}
return self;
}
ALTERNATIVE:
Add UITapGestureRecognizer:
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
[_mapView addGestureRecognizer:recognizer];
[recognizer release];
Handle gesture:
- (void)handleGesture:(UIGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateEnded)
{
for (int i=0; i<recognizer.numberOfTouches; i++)
{
//CGPoint point = [recognizer locationInView:_mapView];
CGPoint point = [recognizer locationOfTouch:i inView:_mapView];
CLLocationCoordinate2D coord = [_mapView convertPoint:point toCoordinateFromView:self.mapView];
MKMapPoint mapPoint = MKMapPointForCoordinate(coord);
for (id overlay in _mapView.overlays)
{
if ([overlay isKindOfClass:[MKGeodesicPolyline class]])
{
MKGeodesicPolyline *poly = (MKGeodesicPolyline*) overlay;
id view = [_mapView rendererForOverlay:poly];
if ([view isKindOfClass:[MKPolylineRenderer class]] && [[poly title] isEqualToString:#"fullRouteAbove"])
{
MKPolylineRenderer *polyView = (MKPolylineRenderer*) view;
[polyView invalidatePath];
CGPoint polygonViewPoint = [polyView pointForMapPoint:mapPoint];
NSLog(#"polyView: %#",polyView);
BOOL mapCoordinateIsInPolygon = CGPathContainsPoint(polyView.path, NULL, polygonViewPoint, NO);
if (mapCoordinateIsInPolygon)
{
NSLog(#"hit!");
}else{
NSLog(#"miss!");
)
}
}
}
}
}
}
SWITCHING BETWEEN TOUCHABLE AREAS:
Replace
BOOL mapCoordinateIsInPolygon = CGPathContainsPoint(polyView.path, NULL, polygonViewPoint, NO);
with
BOOL mapCoordinateIsInPolygon = CGRectContainsPoint(CGPathGetBoundingBox(polyView.path), polygonViewPoint);
or
BOOL mapCoordinateIsInPolygon = CGRectContainsPoint(CGPathGetPathBoundingBox(polyView.path), polygonViewPoint);
I think you need this:
Add an NSMutableArray called arrPolylineViews to your class.
Then add a tapGestureRecognizer to your mapView:
UITapGestureRecognizer *gr = [[UITapGestureRecognizer alloc] init];
gr.numberOfTapsRequired = 1;
gr.numberOfTouchesRequired = 1;
gr.delegate = self;
[gr addTarget:self action:#selector(didTap:)];
[myMapView addGestureRecognizer:gr];
In didTap:
- (void)didTap:(UITapGestureRecognizer *)gr
{
if (gr.state == UIGestureRecognizerStateEnded)
{
// convert the touch point to a CLLocationCoordinate & geocode
CGPoint touchPoint = [gr locationInView:myMapView];
MKPolylineView *touchedPolyLineView = [self polylineTapped:touchPoint];
if (touchedPolyLineView)
{
//touched a polyLineView
}
}
}
Then:
- (MKOverlayView*)mapView:(MKMapView*)theMapView viewForOverlay:(id <MKOverlay>)overlay
{
if([overlay isKindOfClass:[MKPolyline class]]){
//create your polyLineView
[arrPolylineViews addObject:polyLineView];
}
}
Then add this method:
- (MKPolylineView *)polylineTapped:(CGPoint)point
{
// Check if the overlay got tapped
for (MKPolylineView *polyView in arrPolylineViews)
{
// Get view frame rect in the mapView's coordinate system
CGRect viewFrameInMapView = [polyView.superview convertRect:polyView.frame toView:myMapView];
// Check if the touch is within the view bounds
if (CGRectContainsPoint(viewFrameInMapView, point))
{
return polyView;
}
}
return nil;
}
Don't forget to -
[arrPolylineViews removeAllObjects];
before adding the new list of points on map.
I got it working using the WildcardGestureRecognizer and UIGestureRecognizer. See my post for code.
i want set a rect on top of the polyline route on my map.
this is what exactly i'm trying to do:
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay
{
if ([overlay isKindOfClass:[MKPolyline class]]) {
MKPolyline *route = overlay;
MKPolylineRenderer *routeRenderer = [[MKPolylineRenderer alloc] initWithPolyline:route];
routeRenderer.strokeColor = [[UIColor blueColor] colorWithAlphaComponent:0.7];
routeRenderer.lineWidth = 5.0;
[self.mapView.visibleMapRect = route.boundingMapRect];
return routeRenderer;
}
else return nil;
}
i have problem with this line of code :
[self.mapView.visibleMapRect = route.boundingMapRect];
i get the "Expected identifier" error. what is wrong with this line of code?
is that the correct way to set an Mkrect for an MKPolyline route?
thanks!
That's not how you write objective-C, try this
self.mapView.visibleMapRect = route.boundingMapRect;
or
[self.mapView setVisibleMapRect:route.boundingMapRect animated:YES];
i have solved with this tow line of code:
MKMapRect test = MKMapRectInset(route.boundingMapRect, -route.boundingMapRect.size.height/2, -route.boundingMapRect.size.width/2);
[self.mapView setVisibleMapRect:test animated:YES];
I am trying to add a circle overlay to a map, but it is not appearing:
- (void) displayOverlayOnMap:(double) lat andlng: (double) lng
{
CLLocationCoordinate2D bostonCoord = CLLocationCoordinate2DMake(lat,lng);
//add MKCircle overlay...
MKCircle *circle = [MKCircle circleWithCenterCoordinate:bostonCoord radius:1000];
[self.mapView addOverlay:circle];
}
Any body have a clue why it's not showing?
here is an example.. you can also find one using polygons on apple's sites: https://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/LocationAwarenessPG/AnnotatingMaps/AnnotatingMaps.html#//apple_ref/doc/uid/TP40009497-CH6-SW15
or you can use this example
Create overlay from user interaction on MKMapView?
- (void)handleLongPress:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer.state != UIGestureRecognizerStateBegan)
return;
CGPoint touchPoint = [gestureRecognizer locationInView:mapView];
CLLocationCoordinate2D touchMapCoordinate = [mapView convertPoint:touchPoint toCoordinateFromView:mapView];
//add pin where user touched down...
MKPointAnnotation *pa = [[MKPointAnnotation alloc] init];
pa.coordinate = touchMapCoordinate;
pa.title = #"Hello";
[mapView addAnnotation:pa];
[pa release];
//add circle with 5km radius where user touched down...
MKCircle *circle = [MKCircle circleWithCenterCoordinate:touchMapCoordinate radius:5000];
[mapView addOverlay:circle];
}
-(MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay
{
MKCircleView* circleView = [[[MKCircleView alloc] initWithOverlay:overlay] autorelease];
circleView.fillColor = [UIColor redColor];
return circleView;
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
static NSString *AnnotationIdentifier = #"Annotation";
MKPinAnnotationView* pinView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationIdentifier];
if (!pinView)
{
pinView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier] autorelease];
pinView.pinColor = MKPinAnnotationColorGreen;
pinView.animatesDrop = YES;
}
else
{
pinView.annotation = annotation;
}
return pinView;
}
Use the code
CLLocationDistance radiusInMeters = 1000;
MKCircle *circle = [MKCircle circleWithCenterCoordinate:bostonCoord radius:radiusInMeters];
Instead of
MKCircle *circle = [MKCircle circleWithCenterCoordinate:bostonCoord radius:1000];
It worked for me.