How to create a path using MKOverlayPathView? - ios

I've been looking at Apple's iOS Class Reference documentation, and am unfortunately none the wiser. I have downloaded their sample code KMLViewer but they've overcomplicated it... All I really want to know is how to generate a path and add it to the MKMapView. The documentation talks of using a CGPathRef, but doesn't really explain how.

Here's how to generate a path and add it as an overlay to an MKMapView. I'm going to use an MKPolylineView, which is a subclass of MKOverlayPathView and shields you from having to refer to any CGPath since you instead create an MKPolyline (containing the data of the path) and use that to create the MKPolylineView (the visual representation of the data on the map).
The MKPolyline has to be created with a C array of points (MKMapPoint), or a C array of coordinates (CLLocationCoordinate2D). It's a shame that MapKit doesn't use more advanced data structures such as NSArray, but so be it! I'm going to assume that you have an NSArray or NSMutableArray of CLLocation objects to demonstrate how to convert to a C array of data suitable for the MKPolyline. This array is called locations and how you fill it would be determined by your app - e.g. filled in by processing touch locations by the user, or filled with data downloaded from a web service etc.
In the view controller that is in charge of the MKMapView:
int numPoints = [locations count];
if (numPoints > 1)
{
CLLocationCoordinate2D* coords = malloc(numPoints * sizeof(CLLocationCoordinate2D));
for (int i = 0; i < numPoints; i++)
{
CLLocation* current = [locations objectAtIndex:i];
coords[i] = current.coordinate;
}
self.polyline = [MKPolyline polylineWithCoordinates:coords count:numPoints];
free(coords);
[mapView addOverlay:self.polyline];
[mapView setNeedsDisplay];
}
Note that self.polyline is declared in the .h as:
#property (nonatomic, retain) MKPolyline* polyline;
This view controller should also implement the MKMapViewDelegate method:
- (MKOverlayView*)mapView:(MKMapView*)theMapView viewForOverlay:(id <MKOverlay>)overlay
{
MKPolylineView* lineView = [[[MKPolylineView alloc] initWithPolyline:self.polyline] autorelease];
lineView.fillColor = [UIColor whiteColor];
lineView.strokeColor = [UIColor whiteColor];
lineView.lineWidth = 4;
return lineView;
}
You can play with the fillColor, strokeColor and lineWidth properties to ensure that they are appropriate for your app. I've just gone with a simple, moderately wide plain white line here.
If you want to remove the path from the map, e.g. to update it with some new coordinates, then you would do:
[mapView removeOverlay:self.polyline];
self.polyline = nil;
and then repeat the above process to create a new MKPolyline and add it to the map.
Although on first glance MapKit can look a bit scary and complex, it can be easy to do some things as illustrated in this example. The only scary bit - for non-C programmers at least - is the use of malloc to create a buffer, copy the CLLocationCoordinates into it using array syntax, and then freeing the memory buffer afterwards.

Related

MKMapView Draw connected lines between all points

I have a list of 50+ coordinates. What is the most efficient way to draw lines between all these coordinates (should create a "circular" path because they all have a display order) that is also easy to customize (line thickness, color, etc...)?
Thanks!
I am not sure I understand your question for certain. If you are looking for a list of points to display from end to end, then you will want to create a MKPolyline object from those points, making sure the points are added to the myPoints array in the order you want to connect them:
CLLocationCoordinate2D coordinates[[myPoints count]];
int i = 0;
for (Checkpoint *point in myPoints)
{
coordinates[i] = CLLocationCoordinate2DMake([point.lat floatValue] , [point.lon floatValue]);
i++;
}
self.polyline = [MKPolyline polylineWithCoordinates:coordinates count: [myPoints count]];
[mapView addOverlay:self.polyline];
Then make sure you are implementing the delegate method - mapView:rendererForOverlay:. Here's an example, but tailor it to your needs:
-(MKOverlayRenderer*)mapView:(MKMapView*)mapView rendererForOverlay:(id <MKOverlay>)overlay
{
MKPolylineRenderer* lineView = [[MKPolylineRenderer alloc] initWithPolyline:self.polyline];
lineView.strokeColor = [UIColor blueColor];
lineView.lineWidth = 7;
return lineView;
}
However, if you really want a closed loop (circular) object, then you will want to create a MKPolygon object instead. The process is quite similar; in that case replace the self.polyline initializer above with this code:
self.polygon = [MKPolygon polygonWithCoordinates:coordinates count: [myPoints count]];
[mapView addOverlay:self.polygon];
The - mapView:rendererForOverlay: code should remain the same I think. I haven't tested this code, but hopefully it gets you moving in the right direction.

Remove travelled GMSPolyline segment

I am using google map sdk for ios to provide directions between current user location and an end location. I have so far achieved to draw a GMSPolyline between the current user location and the end location using the code below and it's working great.
GMSPath *encodedPath = [GMSPath pathFromEncodedPath:encodedPathSting];
self.polyline = [GMSPolyline polylineWithPath:encodedPath];
self.polyline.strokeWidth = 4;
self.polyline.strokeColor = [UIColor colorWithRed:55.0/255.0 green:160.0/255.0 blue:250.0/255.0 alpha:1.0];;
self.polyline.map = self.mapView;
Is it possible to remove a part of the GMSPolyline that has been covered by the user through driving/walking? The GMSPolyline must gradually decrease in length as we trace the path.
One way to achieve this is by redrawing the path repeatedly but this is not or may not be efficient.
Thanks.
So get the latlng point of the polyline in an array as described here:
//route is the MKRoute in this example
//but the polyline can be any MKPolyline
NSUInteger pointCount = route.polyline.pointCount;
//allocate a C array to hold this many points/coordinates...
CLLocationCoordinate2D * routeCoordinates = malloc(pointCount * sizeof(CLLocationCoordinate2D));
//get the coordinates (all of them)...
[route.polyline getCoordinates: routeCoordinates
range: NSMakeRange(0, pointCount)
];
//this part just shows how to use the results...
NSLog(#"route pointCount = %d", pointCount);
for (int c = 0; c < pointCount; c++) {
NSLog(#"routeCoordinates[%d] = %f, %f",
c, routeCoordinates[c].latitude, routeCoordinates[c].longitude);
}
//free the memory used by the C array when done with it...
free(routeCoordinates);
Then, implement a while loop for the first point as you move along the path like this:
int c = 0;
while (pointCount.size() > 0)
{
pointCount.get(0).remove();
}
Note: I'm not that experienced with iOS and haven't tested this solution. Treat it as a suggestion rather than a fix. Thanks!
Hope it helps!

How to find if two google map routes are being intersected in an IOS project

I want to achieve the following and I am unaware whether it is possible or not. I am having two points on a road (imagine like a finishing line - they are the two edges of the pavements - they are in a straight line) and I want to check whether a user's route has passed between these points.
So I thought, I can do something like this:
get the route between these two points (they are quite near - the road is 20 m wide at most)
get the users route
check if these routes are interecting, if there is any crossing between them.
If it was pure geometry, it would be easy. I am having two lines and I want to know if there is any intersection between them.
I want to use the following in an IOS project, if it makes any difference. For example, I thought that maybe there is a programmatically way of drawing the MKPolylines and see if there is intersection. I do not want to see it visually, I just need it programmatically.
Is it possible? Can you suggest me anything else?
There is no direct method to check for that intersection.
You can turn the problem into a geometry problem by converting the lat/long positions into map positions (applying the transform to flatten onto a normalised grid). This can be done with MKMapPointForCoordinate.
One thing to consider is the inaccuracy of the GPS reported positions. In a number of cases you will find that the reported track from the GPS isn't actually on the road but running beside the road. Particularly when turning (tight) corners you will often get a large curve in the track. As such you may want to extend the width of the 'finishing line' to compensate for this.
If you just care about whether the user is within a certain distance of a set point then you can create a CLRegion to represent the target location and then call containsCoordinate: with the current user location. This removes any projection and uses the lat/long directly. You can also get the system to monitor this for you and give you a callback when the user enters or exits the region with startMonitoringForRegion:desiredAccuracy:. Again, you need to consider GPS accuracy when setting the radius.
I would try to solve this problem in three steps:
Step 1. Convert each coordinate user's track has to CGPoint and save it an array.
// in viewDidLoad
locManager = [[CLLocationManager alloc] init];
[locManager setDelegate:self];
[locManager setDesiredAccuracy:kCLLocationAccuracyBest];
[locManager startPdatingLocation];
self.userCoordinatePoints = [NSMutableArray alloc]init];
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
CLLocationCoordinate2D loc = [newLocation coordinate];
CGPoint *currentPoint = [self.mapView convertCoordinate:loc toPointToView:self.mapView];
// add points to array
self.userCoordinatePoints addObject:currentpoint];
}
Step 2. Convert MKPolylineView to CGPathRef
Create a class variable of type CGPathRef
{
CGPathRef path;
}
This method you must have implemented to create the route between two points:
- (MKOverlayView*)mapView:(MKMapView*)theMapView
viewForOverlay:(id <MKOverlay>)overlay
{
MKPolylineView *overlayView = [[MKPolylineView alloc]
initWithOverlay:overlay];
overlayView.lineWidth = 3;
overlayView.strokeColor = [[UIColor blueColor]colorWithAlphaComponent:0.5f];
// overlayView.fillColor = [[UIColor purpleColor] colorWithAlphaComponent:0.1f];
path = overlayView.path;
return overlayView;
}
Step 3: Create a custom method to check whether point lies in CGPath or not
- (BOOL)userRouteIntersectsGoogleRoute
{
// Loop through all CGPoints
for(CGPoint point in self.userCoordinatePoints)
{
BOOL val = CGPathContainsPoint(path, NULL, point);
if(val)
{
return YES;
}
}
return NO;
}

Cocos2D - Better to keep array of CGRects or array of CCSprites to track hit areas?

I am putting together a simple board game in cocos2d to get my feet wet.
To track user clicks I intend to listen for click on game squares, not the game pieces to simplify tracking game pieces. It would be 8x8 board.
Is it more efficient to:
A. Make an array of CGRects to test against and need to put the struct in an NSObject before adding to array. Seams simple but looks like a lot of work going into access the CGRects every time they are needed.
or
B. Make actual CCSprites and test against their bounding rectangle. Simple to code, but that there are an extra 64 unneeded visual objects on the screen, bloating memory use.
or even
C. Some other method, and I am fundamentally misunderstanding this tool.
I agree it seems unnecessary to create a sprite for each game square on your board if your board is totally static.
However CGRect is not an object type so it can not be added to an NSMutableArray, and I will also assume that at some point you will want to do other things with your game square, such as highlighting them and other stuff. What I suggest you do is that you create a class called GameSquare that inherits from CCNode and put them into an array:
// GameSquare.h
#interface GameSquare : CCNode {
//Add nice stuff here about gamesquares and implement in GameSquare.m
}
After that, you can create gamesquares as nodes:
// SomeLayer.h
#interface SomeLayer : CCLayer {
NSMutableArray *myGameSquares;
GameSquare *magicGameSquare;
}
#property (nonatomic, strong) GameSquare *magicGameSquare;
// SomeLayer.m
/* ... (somewhere in the layer setup after init of myGameSquares) ... */
GameSquare *g = [[GameSquare alloc] init];
g.position = CGPointMake(x,y); //replace x,y with your coordinates
g.size = CGSizeMake(w,h); //replace w,h with your sizes
[myGameSquares addObject:g];
self.magicGameSquare = [[GameSquare alloc] init];
magicGameSquare.position = CGPointMake(mX,mY); //replace with your coordinates
magicGameSquare.size = CGSizeMake(mW,mH); //replace with your sizes
After that, you can do hit test against the gamesquares like this (in your CCLayer subclass):
// SomeLayer.m
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint location = [self convertTouchToNodeSpace: touch];
// Example of other objects that user might have pressed
if (CGRectContainsPoint([magicSquare getBounds], location)) {
// User pressed magic square!
[magicSquare doHitStuff];
} else {
for (int i=0; i<myGameSquares.count; i++) {
GameSquare *square = [myGameSquares objectAtIndex:i];
if (CGRectContainsPoint(square.boundingBox, location)) {
// This is the square user has pressed!
[square doHitStuff];
break;
}
}
}
return YES;
}
Yes, you will have to look through the list, but unless the player can press many squares in one touch, you can stop the search as soon as the right one is found, as demonstrated in the example.
(Assumed use of ARC)
PS. If you at some point need to add a any sprite for the GameSquare, simply add a CCSprite member in your GameSquare class and refer to that.

To draw polygon on google map with MapKit framework

I wanted to display Google map in a map view on which I want to draw a polygon/circle.
Any advice?
The way I'm reading your question is that you want to programmatically draw the polygon on the map. For this, consult the Apple docs on MapKit.
You don't need to add transparent views over the MapKit map (MKMapView). You create an overlay object, in this case an MKPolygon. (in the following example, the variable map will be the MKMapView instance owned by the view controller that you put this code in):
CLLocationCoordinate2D points[4];
points[0] = CLLocationCoordinate2DMake(41.000512, -109.050116);
points[1] = CLLocationCoordinate2DMake(41.002371, -102.052066);
points[2] = CLLocationCoordinate2DMake(36.993076, -102.041981);
points[3] = CLLocationCoordinate2DMake(36.99892, -109.045267);
MKPolygon* poly = [MKPolygon polygonWithCoordinates:points count:4];
poly.title = #"Colorado";
[map addOverlay:poly];
Then, if you want to customize the look (colors, stroke, etc.) of the overlay, you implement the MKMapViewDelegate protocol in the view controller you have that owns the MKMapView object and provide an implementation of mapView:viewForOverlay:
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
{
if ([overlay isKindOfClass:[MKPolygon class]])
{
MKPolygonView* aView = [[[MKPolygonView alloc] initWithPolygon:(MKPolygon*)overlay] autorelease];
aView.fillColor = [[UIColor cyanColor] colorWithAlphaComponent:0.2];
aView.strokeColor = [[UIColor blueColor] colorWithAlphaComponent:0.7];
aView.lineWidth = 3;
return aView;
}
return nil;
}
Of course, always remember to actually assign the map instance's delegate to your view controller (MKMapViewDelegate), either in the interface builder, or in code (e.g. viewDidLoad).
I used ideas from this persons blog post to accomplish this. It basically involves adding a transparent view over the map. The map then allows you to convert locations to points on the view. Let me know if the site does not help you and I can try and dig up an example from my code.
http://spitzkoff.com/craig/?p=65

Resources