Draw Flag as Overlay on MKMapView - ios

I want to draw a country's flag on top of country boundary. I am using the coordinate data from here
The way I am adding overlay is
[mapView addOverlays:countryName];
Then rendering it as
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay
{
if ([overlay isKindOfClass:[MKPolygon class]])
{
MKPolygonRenderer *renderer = [[MKPolygonRenderer alloc] initWithPolygon:overlay];
renderer.fillColor = [ColorUtil colorFromHexString:#"#EE5A43" withAlpha:0.6];
renderer.strokeColor = [[UIColor whiteColor] colorWithAlphaComponent:1.0];
renderer.lineWidth = 2;
return renderer;
}
return nil;
}
Now my idea was to draw the overlay with the flag. So I subclasses MKPolygonRenderer and added
#implementation MyMKOverlayRenderer
- (instancetype)initWithOverlay:(id<MKOverlay>)overlay overlayImage:(UIImage *)overlayImage {
self = [super initWithOverlay:overlay];
if (self) {
_overlayImage = overlayImage;
}
return self;
}
- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context {
CGImageRef imageReference = self.overlayImage.CGImage;
MKMapRect theMapRect = self.overlay.boundingMapRect;
CGRect theRect = [self rectForMapRect:theMapRect];
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0.0, -theRect.size.height);
CGContextDrawImage(context, theRect, imageReference);
}
#end
Now check the two attached images. I was expecting the flag to be inside the boundary of the polygon. But looks like it is not happening. So may be my approach is wrong. Can some expert help me in correct direction.

I solved this problem. The key was to create a UIBezierPath and clip the overlay image.
I have posted the entire solution here
Now it looks like

Related

How to prevent rotation of all UIImage overlays with MapKit

I am placing overlays with MapOverlayRenderer. I first use a UIImage in a view over the map and can rotate it in its view and then convert the points (define boundingMapRect, coordinate, etc.) and then place it (Renderer/addOverlay) onto the map with the correct inserted image (CGContextDrawImage) and correct rotation. Each time that I create a new overlay [UIImage in view, rotate, convert points, addOverlay) it works just fine. But, on each successive overlay added to the Map, the code also rotates all of the other overlays already on the map. It seems as though my rotation code (below) is being applied to all of the overlays.
Should I be making an array which includes boundingMapRect and coordinates for each overlay - if so, should these be in viewcontroller?
Should I be naming each overlay [overlay.title? - how do I set it and call it?]
Appreciate some guidance - thanks.
MapOverlayView.h
#interface MapOverlayView ()
#property (nonatomic, strong) UIImage *overlayImage;
#end
#implementation MapOverlayView
- (instancetype)initWithOverlay:(id<MKOverlay>)overlay overlayImage:
(UIImage *)overlayImage {
self = [super initWithOverlay:overlay];
if (self) {
_overlayImage = overlayImage;
}
return self;
}
- (void)drawMapRect:(MKMapRect)mapRect zoomScale:
(MKZoomScale)zoomScale inContext:(CGContextRef)context {
CGImageRef imageReference = self.overlayImage.CGImage;
MKMapRect theMapRect = self.overlay.boundingMapRect;
CGRect theRect = [self rectForMapRect:theMapRect];
IonQuestSingleton *tmp = [IonQuestSingleton sharedSingleton];
float new_angle = tmp.image_angle;
NSLog(#"%s%f","New angle is ",new_angle);
{
if (new_angle<90) {
CGContextRotateCTM(context,new_angle*M_PI/180);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0.0, -theRect.size.height);
CGContextDrawImage(context, theRect, imageReference);
NSLog(#"%s%f","New angle 90 is ",new_angle);//tick
}
else if (new_angle >90 && new_angle <=180)
{
CGContextRotateCTM(context,new_angle*M_PI/180);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0.0, 0.0);
CGContextDrawImage(context, theRect, imageReference);
NSLog(#"%s%f","New angle 180 is ",new_angle);//tick
}
else if (new_angle >180 && new_angle <=270)
{
CGContextRotateCTM(context,new_angle*M_PI/180);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, -theRect.size.width, 0.0);
CGContextDrawImage(context, theRect, imageReference);
NSLog(#"%s%f","New angle 270 is ",new_angle);//tick
}
else if (new_angle >270) {
CGContextRotateCTM(context,new_angle*M_PI/180);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, -theRect.size.width,
-theRect.size.height);
CGContextDrawImage(context, theRect, imageReference);//tick
NSLog(#"%s%f","New angle 360 is ",new_angle);
}
}
}
#end
MapOverlay.h
#import "MapOverlay.h"
#import "IonQuestSingleton.h"
#implementation MapOverlay
#define debug 1
#synthesize boundingMapRect;
#synthesize coordinate;
-(CLLocationCoordinate2D)coordinate {
//Image center point
IonQuestSingleton *tmp = [IonQuestSingleton sharedSingleton];
return CLLocationCoordinate2DMake(tmp.image_ctr_lat,
tmp.image_ctr_long);
}
- (instancetype)init
{
self = [super init];
if (self) {
IonQuestSingleton *tmp = [IonQuestSingleton sharedSingleton];
MKMapPoint upperLeft =
MKMapPointForCoordinate(CLLocationCoordinate2DMake(tmp.image_tl_lat,
tmp.image_tl_long));
MKMapPoint boundsLeft = ```MKMapPointForCoordinate(CLLocationCoordinate2DMake(tmp.image_tl_bounds_lat, tmp.image_tl_bounds_long));
boundingMapRect = MKMapRectMake(upperLeft.x, upperLeft.y,
fabs(upperLeft.x - boundsLeft.x), fabs(upperLeft.x - boundsLeft.x));
}
return self;
}
#end
Viewcontroller.h
-(void) loadOverlay
{
MapOverlay *overlay = [[MapOverlay alloc] init];
[self.mapView addOverlay:overlay level:MKOverlayLevelAboveLabels];
}
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:
(id<MKOverlay>)overlay {
NSString *mapNameFromMVCPos = [NSString
stringWithFormat:#"%#%s",self.mapNameFromMVC,"Pos"];//not working
{
if ([overlay isKindOfClass:[MapOverlay class]]) {
UIImage *magicMountainImage = [UIImage
imageNamed:mapNameFromMVCPos];
MKOverlayRenderer *overlayView = [[MapOverlayView alloc]
initWithOverlay:overlay overlayImage:magicMountainImage];
return overlayView;
} etc [evaluate other overlaytypes]

Rotate Annotation Custom Image [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
I am clearly not getting this. I'm trying to work out how to rotate a custom image of map annotations. i.e. multiple icons pointing different directions. I have loop'ed over a whole bunch of airplane data and I want to simply show the direction the plane is heading.
Review the code that I have managed to piece together to get at least half of it working and provide a suggestive idea on how to turn the image based on a variable.
Abstract View of the loop'ed data
NSMutableArray *locations = [[NSMutableArray alloc]init];
CLLocationCoordinate2D location;
MKPointAnnotation *myAnn;
myAnn = [[MKPointAnnotation alloc]init];
location.latitude = nLatitudeFloat;
location.longitude = nLongitudeFloat;
myAnn.coordinate = location;
myAnn.title = nRealname;
//Add the Annotation object to the list so when the map is presented all points are listed on the map.
[locations addObject:myAnn];
//[self.mapView setUserTrackingMode:MKUserTrackingModeFollow animated:YES];
[self.mapView addAnnotations:locations];
Update of the annotation:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
// If it's the user location, just return nil.
if ([annotation isKindOfClass:[MKUserLocation class]]) return nil;
// Handle any custom annotations.
if ([annotation isKindOfClass:[MKPointAnnotation class]])
{
// Try to dequeue an existing pin view first.
MKAnnotationView *pinView = (MKAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:#"CustomPinAnnotationView"];
if (!pinView)
{
// If an existing pin view was not available, create one.
pinView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"CustomPinAnnotationView"];
pinView.canShowCallout = YES;
pinView.image = [UIImage imageNamed:#"airplane21.png"] ;
pinView.calloutOffset = CGPointMake(0, 32);
pinView.transform = CGAffineTransformMakeRotation(30); <------AT THE MOMENT I HAVE JUST HARD CODED THIS TO SEE IF IT WORKS. IT ROTATES THE ENTIRE ANNOTATION
// Add a detail disclosure button to the callout.
//UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
//pinView.rightCalloutAccessoryView = rightButton;
// Add an image to the left callout.
UIImageView *iconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"airplane21.png"]];
pinView.leftCalloutAccessoryView = iconView;
iconView.transform = CGAffineTransformMakeRotation(30);
} else {
pinView.annotation = annotation;
}
return pinView;
}
return nil;
}
Any ideas?
I have seen this by the way. I just wasn't confident to know if it would be suitable or not.
Rotate annotation image on MKMapView
I had the same problem and I have a solution that seems to work.
You need to first make a custom annotation that can hold the data that you want (ex. coordinates, your plane heading). When you loop through your data make sure you're using that custom annotation. For example
CustomAnnotation* annotation = [[CustomAnnotation alloc] init];
annotation.coordinates = ...
annotation.bearing = ...
Then in your viewForAnnotation method, you can get that info by doing something like
if ([annotation isKindOfClass:[CustomAnnotation class]]) {
CustomAnnotation* myAnn = (CustomAnnotation*)annotation;
double bearing = myAnn.bearing; // or whatever it's called
...
}
Hope this helped.
Edit: For rotating your image, I found the following code snippet somewhere. It works but it pixellates your image a little bit.
#interface UIImage (RotationMethods)
- (UIImage *)imageRotatedByDegrees:(CGFloat)degrees;
#end
#implementation UIImage (RotationMethods)
static CGFloat DegreesToRadians(CGFloat degrees) {return degrees * M_PI / 180;};
- (UIImage *)imageRotatedByDegrees:(CGFloat)degrees
{
// calculate the size of the rotated view's containing box for our drawing space
UIView *rotatedViewBox = [[UIView alloc] initWithFrame:CGRectMake(0,0,self.size.width, self.size.height)];
CGAffineTransform t = CGAffineTransformMakeRotation(DegreesToRadians(degrees));
rotatedViewBox.transform = t;
CGSize rotatedSize = rotatedViewBox.frame.size;
// Create the bitmap context
//UIGraphicsBeginImageContext(rotatedSize); // For iOS < 4.0
UIGraphicsBeginImageContextWithOptions(rotatedSize, NO, 0.0);
CGContextRef bitmap = UIGraphicsGetCurrentContext();
// Move the origin to the middle of the image so we will rotate and scale around the center.
CGContextTranslateCTM(bitmap, rotatedSize.width/2, rotatedSize.height/2);
// Rotate the image context
CGContextRotateCTM(bitmap, DegreesToRadians(degrees));
// Now, draw the rotated/scaled image into the context
CGContextScaleCTM(bitmap, 1.0, -1.0);
CGContextDrawImage(bitmap, CGRectMake(-self.size.width / 2, -self.size.height / 2, self.size.width, self.size.height), [self CGImage]);
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
#end
Stick that in your .m, and on any UIImage you can now do [someUIImage imageRotatedByDegrees:yourDegrees];
So now in your viewForAnnotation method you can do something like
UIImage* image = [[UIImage imageNamed:#"yourImage.png"] imageRotatedByDegrees:degrees];
annView.image = image;

dynamically change lineWidth of mkpolyline

I have Map in my app in which is moving as per user location.I have successfully drawn a polyline for source and destination.
Using following code
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id < MKOverlay >)overlay{
MKPolylineView *view1 = [[MKPolylineView alloc] initWithOverlay:overlay];
view1.lineWidth = 27.0;
view1.strokeColor = [UIColor colorWithRed:55.0/255.0 green:168.0/255.0 blue:219.0/255.0 alpha:1];
return view1;
}
But my problem is when map is moving , mean i am changing region of map as user moves , then some times polyline is not shown good some time its show thicker than actual size as you can see in below image.
i am attaching the image below, please let me know what can i do for smooth polyline when map is moving.
EDIT
As Matt suggested i create a subclass of MKPolylineRenderer and implement drawMapRect method as below:
-(void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context{
CGMutablePathRef fullPath = CGPathCreateMutable();
BOOL pathIsEmpty = YES;
//merging all the points as entire path
for (int i=0;i< self.polyline.pointCount;i++){
CGPoint point = [self pointForMapPoint:self.polyline.points[i]];
if (pathIsEmpty){
CGPathMoveToPoint(fullPath, nil, point.x, point.y);
pathIsEmpty = NO;
} else {
CGPathAddLineToPoint(fullPath, nil, point.x, point.y);
}
}
//get bounding box out of entire path.
CGRect pointsRect = CGPathGetBoundingBox(fullPath);
CGRect mapRectCG = [self rectForMapRect:mapRect];
//stop any drawing logic, cuz there is no path in current rect.
if (!CGRectIntersectsRect(pointsRect, mapRectCG))return;
UIColor *darker = [UIColor blackColor];
CGFloat baseWidth = 10 / zoomScale;
// draw the dark colour thicker
CGContextAddPath(context, self.path);
CGContextSetStrokeColorWithColor(context, darker.CGColor);
CGContextSetLineWidth(context, baseWidth * 1.5);
CGContextSetLineCap(context, self.lineCap);
CGContextStrokePath(context);
// now draw the stroke color with the regular width
CGContextAddPath(context, self.path);
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetLineWidth(context, baseWidth);
CGContextSetLineCap(context, self.lineCap);
CGContextStrokePath(context);
[super drawMapRect:mapRect zoomScale:zoomScale inContext:context];
}
But Same problem
See below image:
ok, i think i got problem , my polyline is added once, but as user's speed i changing zoom level of MKMapView, zoom level is changed , but polyline width is not refresh,
SO how can i dynamically change lineWidth of mkpolyline ??
The problem is that you are setting your lineWidth at a fixed width (a really big fixed width). This gets larger or smaller as the map is zoomed larger or smaller.
Instead, implement your own MKOverlayRenderer subclass, and override drawMapRect:zoomScale:inContext:. It receives a zoomScale parameter, and so you can adjust the width of your line to look good at what the current scale may be.
Subclass MKPolylineRenderer and override applyStrokePropertiesToContext:atZoomScale: so that it ignores the scale, and draws lines at constant width:
#interface ConstantWidthPolylineRenderer : MKPolylineRenderer
#end
#implementation ConstantWidthPolylineRenderer
- (void)applyStrokePropertiesToContext:(CGContextRef)context
atZoomScale:(MKZoomScale)zoomScale
{
[super applyStrokePropertiesToContext:context atZoomScale:zoomScale];
CGContextSetLineWidth(context, self.lineWidth);
}
#end
Now use it and admire its smooth rendering:
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay
{
MKPolyline *polyline = (MKPolyline *)overlay;
ConstantWidthPolylineRenderer *renderer = [[ConstantWidthPolylineRenderer alloc] initWithPolyline:polyline];
renderer.strokeColor = [UIColor redColor];
renderer.lineWidth = 40;
return renderer;
}
You need to use MKPolylineRenderer instead of MKPolylineView
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id < MKOverlay >)overlay {
#try {
MKPolylineRenderer *renderer = [[[MKPolylineRenderer alloc] initWithOverlay:overlay];
renderer.lineWidth = 27.0;
renderer.strokeColor = [UIColor colorWithRed:55.0/255.0 green:168.0/255.0 blue:219.0/255.0 alpha:1];
return renderer;
}
#catch (NSException *exception) {
NSLog(#"exception :%#",exception.debugDescription);
}
}
Read this one:
The MKPolylineRenderer class provides the visual representation for an MKPolyline overlay object. This renderer strokes the line only; it does not fill it. You can change the color and other drawing attributes of the polygon by modifying the properties inherited from the parent class. You typically use this class as is and do not subclass it.
From Apple library
The MKPolylineView class provides the visual representation for an MKPolyline annotation object. This view strokes the path represented by the annotation. (This class does not fill the area enclosed by the path.) You can change the color and other drawing attributes of the path by modifying the properties inherited from the MKOverlayPathView class. This class is typically used as is and not subclassed.
From Apple library

mkmapview image overlay zooming Memory issue in ios

When I am adding a imageOverlay with high resolution image and map zooms image in the map gone and got memory issues.some times my app crashes. How can this be solved?
Here is my code
-(void)addOverlay
{
MapOverlay *overlay = [[MapOverlay alloc] initWithRouteOverlay:self.routeOverlayObj];
[self.mapView addOverlay:overlay];
}
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay
{
if([overlay isKindOfClass:[MapOverlay class]])
{
mapOverlay = (MapOverlay *)overlay;
NSLog(#"%# %#",routeInfoObj.routeOverlay,[UIImage imageNamed:routeInfoObj.routeOverlay]);
ATTMapOverLayRender *mapOverlayRender = [[ATTMapOverLayRender alloc] initWithOverlay:mapOverlay overlayImage:[UIImage imageNamed:routeInfoObj.routeOverlay] andImageFile:[NSString stringWithFormat:#"%#.png",routeInfoObj.routeOverlay]];
return mapOverlayRender;
}
else
{
MKPolylineRenderer *routeRenderer = [[MKPolylineRenderer alloc] initWithPolyline:routePolyline];
routeRenderer.strokeColor = [UIColor redColor];
routeRenderer.fillColor = [UIColor redColor];
routeRenderer.lineWidth = 5;
return routeRenderer;
}
return nil;
}
and in "ATTMApOVerlayRender.h"
#interface ATTMapOverLayRender : MKOverlayRenderer
- (instancetype)initWithOverlay:(id<MKOverlay>)overlay overlayImage:(UIImage *)overlayImage andImageFile:(NSString *)file;
#property (nonatomic, weak) UIImage *overlayImage;
#end
"ATTMApOVerlayRender.h"
#import "ATTMapOverLayRender.h"
#implementation ATTMapOverLayRender
- (instancetype)initWithOverlay:(id<MKOverlay>)overlay overlayImage:(UIImage *)overlayImage andImageFile:(NSString *)file {
self = [super initWithOverlay:overlay];
if (self) {
self.overlayImage = overlayImage;
}
return self;
}
- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context {
CGImageRef image = _overlayImage.CGImage;
MKMapRect overlayMapRect = [self.overlay boundingMapRect];
CGRect overlayRect = [self rectForMapRect:overlayMapRect];
// draw image
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0.0, -overlayRect.size.height);
CGContextDrawImage(context, overlayRect, image);
}
#end
I'm running into similar problems on a tile overlay project as well.
I was able to significantly improve the memory usage by maintaining a single MKMapOverlayRenderer.
Rather than allocating memory for a new MKOverlayRenderer (or MKPolylineRenderer) every time the mapView: renderForOverlay: method is called, create a class variable and rewrite to it.
So in your ViewController.h or whatever class contains your mapView that calls -(MKOverlayRenderer*)mapView:(MKMapView*)mapView rendererForOverlay:(id<MKOverlay>)overlay have,
#property (nonatomic, strong) MKOverlayRenderer *renderer;
And in the declaration of -(MKOverlayRenderer*)mapView:(MKMapView*)mapView rendererForOverlay:(id<MKOverlay>)overlay
use
self.renderer = [[MKOverlayRenderer alloc] init...

How to overlay a circle on an iOS map

I've got a radius and a location.
This is how I'm trying to get the bounding rectangle of the circle.
- (MKMapRect)boundingMapRect{
CLLocationCoordinate2D tmp;
MKCoordinateSpan radiusSpan = MKCoordinateRegionMakeWithDistance(self.coordinate, 0, self.radius).span;
tmp.latitude = self.coordinate.latitude - radiusSpan.longitudeDelta;
tmp.longitude = self.coordinate.longitude - radiusSpanSpan.longitudeDelta;
MKMapPoint upperLeft = MKMapPointForCoordinate(tmp);
MKMapRect bounds = MKMapRectMake(upperLeft.x, upperLeft.y, self.radius * 2, self.radius * 2);
return bounds;
}
MKMapRectMake(...) seems to want width and height measured in Map points. How do I convert the radius to that?
In the end I'm rendering it like this:
MKMapRect theMapRect = [self.overlay boundingMapRect];
CGRect theRect = [self rectForMapRect:theMapRect];
CGContextAddEllipseInRect(ctx, theRect);
CGContextFillPath(ctx);
The radius doesn't seem to equal meters on the map in the end and also the distance doesn't seem to be measured correctly. How to do it right?
I would be really thankful for every hint.
You should be using MKCircle instead. Do something like:
CLLocationDistance fenceDistance = 300;
CLLocationCoordinate2D circleMiddlePoint = CLLocationCoordinate2DMake(yourLocation.latitude, yourLocation.longitude);
MKCircle *circle = [MKCircle circleWithCenterCoordinate:circleMiddlePoint radius:fenceDistance];
[yourMapView addOverlay: circle];
And adopt the MKMapViewDelegate Method below and do something like this:
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay
{
MKCircleView *circleView = [[[MKCircleView alloc] initWithCircle:(MKCircle *)overlay] autorelease];
circleView.fillColor = [[UIColor redColor] colorWithAlphaComponent:0.9];
return circleView;
}
For iOS7 :
Same as tdevoy in viewDidLoad :
CLLocationDistance fenceDistance = 300;
CLLocationCoordinate2D circleMiddlePoint = CLLocationCoordinate2DMake(yourLocation.latitude, yourLocation.longitude);
MKCircle *circle = [MKCircle circleWithCenterCoordinate:circleMiddlePoint radius:fenceDistance];
[yourMapView addOverlay: circle];
But the delegate method (because mapView:viewForOverlay: is deprecated) :
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id < MKOverlay >)overlay
{
MKCircleRenderer *circleR = [[MKCircleRenderer alloc] initWithCircle:(MKCircle *)overlay];
circleR.fillColor = [UIColor greenColor];
return circleR;
}

Resources