MKPolyline polylineWithPoints bug? - ios

I want to display a route from my location to a destination.
I took this code http://code.google.com/p/octomapkit and I have added a few logging messages.
Go got the route coordinates ( around 103) properly. They are on the way and filling the route from my location to destination, so the google call and parsing the elements is good.
But when I want to display in MKMapView than it shows only the starting the polyline. Like 15 or 20 not more.
I will try to post the code from top to bottom:
The original code took the first element only and I was thinking maybe if I get the last I will see something else, if yes, than I will add all overlays - that's why the for loop.
otherwise : MKPolyline *polyLine = [self.mapView.overlays objectAtIndex:0];
#pragma mark MKMapViewDelegate
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
{
MKPolylineView *routeLineView = nil;
// should take the objectAtIndex:0 , but for test I will check if it has more
for(int i=0; i <self.mapView.overlays.count; i++ ){
MKPolyline *polyLine = [self.mapView.overlays objectAtIndex:i];
routeLineView = [[[MKPolylineView alloc] initWithPolyline:polyLine] autorelease];
routeLineView.fillColor = [UIColor redColor];
routeLineView.strokeColor = [UIColor redColor];
routeLineView.lineWidth = 3;
}
return routeLineView;
}
-(void) routeLoadSucceededWithRoutePoints:(MKMapPoint*)routePoints count:(int)pointNumber {
//NSLog(#"MKMapVew+OctoRoute.routeLoadSucceededWithRoutePoints count: %d", pointNumber);
MKPolyline* routeLine = nil;
routeLine = [MKPolyline polylineWithPoints:routePoints count:pointNumber];
// add the overlay to the map
if (nil != routeLine) {
// added zoom support:
if(shouldZoom){
MKCoordinateRegion region = [self coordinateRegion];
[self setRegion:region animated:YES];
}
//[self removeOverlays:self.overlays];
[self addOverlay:routeLine];
}
}
the //[self removeOverlays:self.overlays]; has no effect if is commented or not, Iwas hoping it will create more :) -but not.
Inside mapPointCArrayFromJSONString I see the coordinates properly:
-(void) jsonLoadSucceededWithData:(NSData*)loadedData {
self.routeParser.jsonStr = [[[NSString alloc] initWithData:loadedData encoding:NSUTF8StringEncoding] autorelease];
MKMapPoint *mapPointCArray = [self.routeParser mapPointCArrayFromJSONString];
//NSLog(#"OctoRouteService.jsonLoadSucceededWithData : %d"+ mapPointCArray.);
[delegate routeLoadSucceededWithRoutePoints:mapPointCArray count:[self.routeParser numberOfPoints]];
}
The steps has the coordinated for sure. Checked many times.
-(MKMapPoint*) mapPointCArrayFromJSONString {
NSArray *steps = [self routeStepsArrayFromJSONString];
//NSLog(#"OctoRouteParser.mapPointCArrayFromJSONString steps:%d ", steps.count);
if(steps.count == 0){
return nil;
}
MKMapPoint *mapPointCArray = malloc(sizeof(CLLocationCoordinate2D) * [steps count]*2 -1);
numberOfPoints = [steps count]-1;
int index=0;
for (NSDictionary *stepDict in steps) {
[self addRouteStepDict:stepDict toMapPointCArray:mapPointCArray atIndex:index];
index = index+2;
}
return mapPointCArray;
}
I can't see a reason why is only the first fraction of the route on my map, with red line.
Any suggestion?

The viewForOverlay delegate method will be called by the map view for each overlay it needs to display a view for. It may also call it more than once for each overlay.
Your code only needs to worry about creating and returning a view for the single overlay passed as a parameter to that method.
The code there should be something like this:
MKPolylineView *routeLineView = [[[MKPolylineView alloc] initWithPolyline:overlay] autorelease];
routeLineView.fillColor = [UIColor redColor];
routeLineView.strokeColor = [UIColor redColor];
routeLineView.lineWidth = 3;
return routeLineView;
The existing code in your question returns the view corresponding to the last overlay that happens to be in the overlays array for every overlay the map view calls viewForOverlay for.
Additionally, that octomapkit has a bug in the mapPointCArrayFromJSONString method. These lines:
MKMapPoint *mapPointCArray = malloc(sizeof(CLLocationCoordinate2D)
* [steps count]*2 -1);
numberOfPoints = [steps count]-1;
should be:
MKMapPoint *mapPointCArray = malloc(sizeof(CLLocationCoordinate2D)
* [steps count]*2);
numberOfPoints = [steps count]*2;
The original first line is wrong because it excludes the end point of the last line segment.
The original second line is very wrong because numberOfPoints is supposed to reflect the number of points in the mapPointCArray (not the last index in the steps array). The way it was, the overlay would only show half the route.
It would be cleaner to change that code so the calculation is done only once:
numberOfPoints = [steps count]*2;
MKMapPoint *mapPointCArray = malloc(sizeof(CLLocationCoordinate2D)
* numberOfPoints);
The viewForOverlay method should still be coded as explained earlier. It should only work with the overlay parameter passed to it and not directly with the map view's overlays array.

Related

Why This Code Does Not Draw Polyline on MKMapView

I have the following code, with which i am trying to draw a polyline between a set of coordinates (which are correct as I also use them to add pins to the map, and those work fine).
I call a drawing method to initiate the drawing like so (the array in the method call contains the necessary coordinates):
[self drawRoute:[[transportData objectForKey:#"19"] objectForKey:#"stops"]];
This is the actual method that is supposed to draw the line on the map (selectedRoute is an MKPolyline object):
- (void)drawRoute:(NSArray *)routePointsArray {
if (selectedRoute) {
[mapView removeOverlay:selectedRoute];
selectedRoute = nil;
}
CLLocationCoordinate2D routeCoordinates[routePointsArray.count];
for (int i = 0; i < routePointsArray.count; i++) {
float latitude = [[[routePointsArray objectAtIndex:i] objectForKey:#"lat"] floatValue];
float longitude = [[[routePointsArray objectAtIndex:i] objectForKey:#"lon"] floatValue];
CLLocationCoordinate2D routePoint = CLLocationCoordinate2DMake(latitude, longitude);
routeCoordinates[i] = routePoint;
}
selectedRoute = [MKPolyline polylineWithCoordinates:routeCoordinates count:routePointsArray.count];
[mapView addOverlay:selectedRoute];
[mapView setVisibleMapRect:[selectedRoute boundingMapRect]];
}
And this is my delegate:
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay
{
MKPolylineRenderer *routeLineView = [[MKPolylineRenderer alloc] initWithPolyline:selectedRoute];
if(overlay == selectedRoute)
{
if(nil == routeLineView)
{
routeLineView = [[MKPolylineRenderer alloc] initWithPolyline:selectedRoute];
routeLineView.fillColor = [UIColor redColor];
routeLineView.strokeColor = [UIColor redColor];
routeLineView.lineWidth = 5;
}
return routeLineView;
}
return nil;
}
I kind of narrowed it down to the routeCoordinates array not getting filled up with coordinates, but I do not understand why.
Also, if you spot any mistakes in the code I would really appreciate if you could point those out to me (possibly with a solution) as I am just learning this part of iOS and can use any help I can get.
You have an error in your rendererForOverlay method.
The first thing it does is assign an instance of MKPolylineRenderer to routeLineView, but later you only actually add the overlay if routeLineView is nil, which it won't be.
Remove the line that assigns the initial value to routeLineView.

iOS Google Maps, different custom images for Clusters and individual markers

I am using Google Maps SDK in my iOS app. I am populating the map using the clustering methods.
I have set custom images for the different clustering buckets ex. 10,20...
The individual markers however have the default (google maps red marker icon).
I would like a custom icon for clustering and a different one for single markers.
However inside the methods that render the Cluster that add markers, if you set the marker icons it changes all of the images not just singles.
How do I set different icons for singles and clusters?
this adds the items to clusterManager
id<GMUClusterItem> item =
[[POIItem alloc] initWithPosition:CLLocationCoordinate2DMake([bay.latitude doubleValue], [bay.longitude doubleValue]) name:bay.name status:bay.marker_status];
[clusterManager addItem:item];
Here I add the icons for the cluster buckets
- (id<GMUClusterIconGenerator>)iconGeneratorWithImages {
return [[GMUDefaultClusterIconGenerator alloc] initWithBuckets:#[ #10, #50, #100, #200, #1000 ]
backgroundImages:#[
[UIImage imageNamed:#"big_parking_pin_img"],
[UIImage imageNamed:#"big_parking_pin_img"],
[UIImage imageNamed:#"big_parking_pin_img"],
[UIImage imageNamed:#"big_parking_pin_img"],
[UIImage imageNamed:#"big_parking_pin_img"]
]];
}
This is where the google cluster class adds markers
- (void)renderCluster:(id<GMUCluster>)cluster animated:(BOOL)animated {
float zoom = _mapView.camera.zoom;
if ([self shouldRenderAsCluster:cluster atZoom:zoom]) {
CLLocationCoordinate2D fromPosition;
if (animated) {
id<GMUCluster> fromCluster =
[self overlappingClusterForCluster:cluster itemMap:_itemToOldClusterMap];
animated = fromCluster != nil;
fromPosition = fromCluster.position;
}
UIImage *icon = [_clusterIconGenerator iconForSize:cluster.count];
GMSMarker *marker = [self markerWithPosition:cluster.position
from:fromPosition
userData:cluster
clusterIcon:icon
animated:animated];
[_markers addObject:marker];
} else {
for (id<GMUClusterItem> item in cluster.items) {
CLLocationCoordinate2D fromPosition;
BOOL shouldAnimate = animated;
if (shouldAnimate) {
GMUWrappingDictionaryKey *key = [[GMUWrappingDictionaryKey alloc] initWithObject:item];
id<GMUCluster> fromCluster = [_itemToOldClusterMap objectForKey:key];
shouldAnimate = fromCluster != nil;
fromPosition = fromCluster.position;
}
GMSMarker *marker = [self markerWithPosition:item.position
from:fromPosition
userData:item
clusterIcon:nil
animated:shouldAnimate];
[_markers addObject:marker];
[_renderedClusterItems addObject:item];
}
}
[_renderedClusters addObject:cluster];
}
// Returns a marker at final position of |position| with attached |userData|.
// If animated is YES, animates from the closest point from |points|.
- (GMSMarker *)markerWithPosition:(CLLocationCoordinate2D)position
from:(CLLocationCoordinate2D)from
userData:(id)userData
clusterIcon:(UIImage *)clusterIcon
animated:(BOOL)animated {
CLLocationCoordinate2D initialPosition = animated ? from : position;
GMSMarker *marker = [GMSMarker markerWithPosition:initialPosition];
marker.userData = userData;
if (clusterIcon != nil) {
marker.icon = clusterIcon;
marker.groundAnchor = CGPointMake(0.5, 0.5);
}
marker.map = _mapView;
if (animated) {
[CATransaction begin];
[CATransaction setAnimationDuration:kGMUAnimationDuration];
marker.layer.latitude = position.latitude;
marker.layer.longitude = position.longitude;
[CATransaction commit];
}
return marker;
}
I had the similar problem 2 days ago and I just found the solution. Hope it will be useful for you.
For example you have a mapView and you set a delegate to it in right place:
[self.mapView setDelegate:self];
Then you need to implement the optional method from GMSMapViewDelegate protocol:
- (void)mapView:(GMSMapView *)mapView idleAtCameraPosition:(GMSCameraPosition *)position {
[self performSelector:#selector(updateMarkers) withObject:nil afterDelay:0.2];
}
I use delay 0.2 seconds, because markers wouldn't update their icons if you'll use smaller value.
The next step is implement method for updating icons:
-(void) updateMarkers {
// "mapView" property in your self.mapView has type GMSVectorMapView,
//and it is hidden, so you can't get like self.mapView.mapView
id vectorMap = [self.mapView valueForKey:#"mapView"];
// "accessibilityItems" - property that have all items in visible part of map.
NSMutableArray* GMSMarkersArray = [vectorMap mutableArrayValueForKey:#"accessibilityItems"];
// Very often you'll get object of GMSPointOfInteretUIItem class, and you don't need it =)
NSMutableArray *discardedItems = [NSMutableArray array];
for (id item in GMSMarkersArray) {
if (![item isKindOfClass:[GMSMarker class]])
[discardedItems addObject:item];
}
[GMSMarkersArray removeObjectsInArray:discardedItems];
// If marker don't have icon image, he use default red pin, but property is still have nil-value ...
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"icon = nil"];
NSArray* singleMarkers = [GMSMarkersArray filteredArrayUsingPredicate:predicate];
// ... and here you can setup any icon you want, for all singles markers in visible part of map.
for(GMSMarker* marker in singleMarkers) {
marker.icon = [UIImage imageNamed:#"yourIcon.png"];
}
}
Also if you create your own marker and add it to cluster, you can get it from userData property of GMSMarker object in last loop. And for example you have there your custom marker with icon you want, just change last loop for something like:
for(GMSMarker* marker in singleMarkers) {
YourMarkerClass* yourMaker = marker.userData;
marker.icon = yourMaker.icon;
}
Sorry for possible mistakes and ask the questions if you don't understand something =)

MapBox. layerForAnnotation not being called. Drawing shape

I am trying to draw a path for the user from point A to point B with given coordinates.
LayerForAnnotation isn't being called after I add the annotation. I am new to using MapBox SDK and don't really know what I'm doing wrong. I looked for instructions on adding Shapes on the MapBox documentation. I tried changing to RMPointAnnotation but did not work and isn't supposed to according to this: Issue GitHub RMAnnotation.
I have checked if there is any info about the implementation of this delegate but haven't found a lot on the MapBox documentation page. I downloaded the example project that illustrates annotations from here: Weekend Picks sample.
This is the code I am using:
- (void) makeRoutingAnnotations
{
// Translate updated path with new coordinates.
NSInteger numberOfSteps = _path.count;
NSMutableArray *coordinates = [[NSMutableArray alloc] init];
for (NSInteger index = 0; index < numberOfSteps; ++index) {
CLLocation *location = [_path objectAtIndex:index];
[coordinates addObject:location];
}
RMAnnotation *startAnnotation = [[RMAnnotation alloc] initWithMapView:mapView coordinate:((CLLocation *)[coordinates objectAtIndex:0]).coordinate andTitle:#"Start"];
startAnnotation.userInfo = coordinates;
[startAnnotation setBoundingBoxFromLocations:coordinates];
[mapView addAnnotation:startAnnotation];
}
- (RMMapLayer *)mapView:(RMMapView *)mView layerForAnnotation:(RMAnnotation *)annotation
{
if (annotation.isUserLocationAnnotation)
return nil;
RMShape *shape = [[RMShape alloc] initWithView:mView];
// set line color and width
shape.lineColor = [UIColor colorWithRed:0.224 green:0.671 blue:0.780 alpha:1.000];
shape.lineWidth = 8.0;
for (CLLocation *location in (NSArray *)annotation.userInfo)
[shape addLineToCoordinate:location.coordinate];
return shape;
}
What could I be missing? I made a droppoint on the layerForAnnotation method but it isn't being called.
I found the problem I hadn't properly implemented the RMMapViewDelegate. Since it wasn't properly implemented it wasn't called.
Suffices to add it on the header file and assign it in code.
mapView.delegate = self;

Mapbox iOS - Fill polygon created dynamically

I am using Mapbox framework for maps and i want to fill polygons and the vertices of the polygon has given from the user touch.
Here is my code on user touch
- (void)singleTapOnMap:(RMMapView *)map at:(CGPoint)point
{
CLLocationCoordinate2D coord;
coord.latitude = [map pixelToCoordinate:point].latitude;
coord.longitude = [map pixelToCoordinate:point].longitude;
RMAnnotation *annotation = [[RMAnnotation alloc] initWithMapView:map coordinate:coord andTitle:#""];
annotation.userInfo = [[NSArray alloc]initWithObjects:[[CLLocation alloc] initWithLatitude:coord.latitude longitude:coord.longitude], nil];
[map addAnnotation:annotation];
}
Delegate method
- (RMMapLayer *)mapView:(RMMapView *)mapView layerForAnnotation:(RMAnnotation *)annotation
{
if (annotation.isUserLocationAnnotation)
return nil;
CLLocation *location = [annotation.userInfo objectAtIndex:0];
RMShape *line = [[RMShape alloc] initWithView:annotation.mapView];
line.lineWidth = 3.0;
line.position = annotation.position;
line.lineColor = [UIColor redColor];
line.fillColor = [UIColor greenColor];
[line moveToCoordinate:location.coordinate];
[line addLineToCoordinate:lastLocation.coordinate];
return line;
}
I can draw the polygon but unable to fill it.
It seems to me that, according to the way that you are currently doing it, for each tap on the map you are creating a NEW annotation, each containing one line segment from the last location to the current tap location, which will not create a polygon, but rather a series of individual annotations containing only one line segment.
You will need to create a separate array of locations of vertices. As you add more locations via tapping it adds to the location array:
// #1 create the following iVars for keeping state of drawing and polygon vertices
bool isDrawingPolygon;
NSMutableArray *savedPolygonVertices;
// #2 in viewDidLoad or your init code, be sure to set the initial states
-(void)viewDidLoad
{
isDrawingPolygon = FALSE;
savedPolygonVertices = nil;
...
... REST OF VIEW DID LOAD OR INIT METHOD
// #3 Create an IBAction button method to trigger beginning of drawing dynamic polygon (a start button)
-(void)startCreatingPolygon
{
isDrawingPolygon = TRUE;
savedPolygonVertices = [[NSMutableArray alloc] initWithCapacity:50]; // Some arbitrary number of points
}
// #4 Begin adding location vertices whenever singleTapOnMap
-(void)singleTapOnMap:(RMMapView *)map at:(CGPoint)point
{
if (isDrawingPolygon)
{
CLLocationCoordinate2D coord;
coord.latitude = [map pixelToCoordinate:point].latitude;
coord.longitude = [map pixelToCoordinate:point].longitude;
[savedPolygonVertices addObject:coord];
RMAnnotation *annotation = [[RMAnnotation alloc] initWithMapView:map coordinate:coord andTitle:#"tempPolygon"]; // Give each temporary line annotation some common identifier "tempPolygon"
annotation.userInfo = [[NSArray alloc]initWithObjects:[[CLLocation alloc] initWithLatitude:coord.latitude longitude:coord.longitude], nil];
[map addAnnotation:annotation];
}
}
// #5 When you tap the "stop" button, you would need to roll the location vertices in the array into ONE annotation object containing an RMShape polygon containing all the vertices for the polygon (and fill/line color attributes), and an identifier for that polygon.
-(void)stopCreatingPolygon // IBAction method for stop making polygon
{
isDrawingPolygon = FALSE;
RMAnnotation *annotation = [[RMAnnotation alloc] initWithMapView:self.mapView coordinate:coord andTitle:#"Polygon"];
annotation.userInfo = savedPolygonVertices;
[self.mapView addAnnotation:annotation];
savedPolygonVertices = nil;
for (RMAnnotation *ann in self.mapView.annotations)
{
if ([ann.title isEqualToString:#"tempPolygon"])
[self.mapView removeAnnotation:ann]; // Get rid of the temporary line segments
}
}
// #6 Then in layerForAnnotation, you would need to check for that identifier (annotation.title), and put the polygon wrapped in an if statement
- (RMMapLayer *)mapView:(RMMapView *)mapView layerForAnnotation:(RMAnnotation *)annotation
{
if (annotation.isUserLocationAnnotation)
return nil;
if ([annotation.title isEqualToString:#"tempPolygon"])
{
CLLocation *location = [annotation.userInfo objectAtIndex:0];
RMShape *line = [[RMShape alloc] initWithView:annotation.mapView];
line.lineWidth = 3.0;
line.position = annotation.position;
line.lineColor = [UIColor redColor];
line.fillColor = [UIColor greenColor];
[line moveToCoordinate:location.coordinate];
[line addLineToCoordinate:lastLocation.coordinate];
return line;
}
if ([annotation.title isEqualToString:#"Polygon"])
{
RMShape *shape = [[RMShape alloc] initWithView:self.mapView];
shape.lineWidth = 3.0;
shape.lineColor = [UIColor redColor];
shape.fillColor = [UIColor greenColor];
shape.fillRule= kCAFillRuleNonZero;
shape.lineJoin = kCALineJoinRound;
shape.lineCap = kCALineCapRound;
for (CLLocationCoordinate2D * location in annotation.userInfo){
// userInfo now contains all vertices between start & stop
[shape addLineToCoordinate:location];
}
return shape;
}
}
That should give you what you are looking for.
/blee/

Same MKOverlayView delegate called in one UIView but not another. What's missing?

My app tracks GPS movement as a MKPolyline routepath on a MKMapView as an MKOverlayRenderer in the HomeVC, saves the data, and displays it later, as a saved routepath a few VCs deeper, on DisplayVC. I can confirm that the data is identical to the original data on the second VC, and the proper routeBounds are used when the map is shown, but the OverlayRenderer is never called on the second VC. Why not? I'm thinking delegate problems, but I can't find anything wrong.
Both homeVC.h
#interface homeVC : UIViewController <CLLocationManagerDelegate, MKMapViewDelegate> {
and displayVC.h are the same, except for the name:
#interface displayVC : UIViewController <CLLocationManagerDelegate, MKMapViewDelegate> {
CLLocationManager *locationManager;
// the data representing the route points
MKPolyline* _routePath;
// the view we create for the line on the map
MKPolylineView* _routePathVw;
// the rect that bounds the loaded points
MKMapRect _routeBounds;
}
#property (nonatomic, weak) IBOutlet MKMapView *mapView;
#end
And both homeVC.m and displayVC.m are set up the same:
- (void)viewDidLoad {
[super viewDidLoad];
// Add the Map
[_mapView setDelegate:self];
_mapView.mapType = MKMapTypeStandard;
}
Lots of good-working code here. Then,
-(void) buildRoute {
CLLocationCoordinate2D thisCoord;
int i = [arrayLa count] - 1; // keep growing the array size
MKMapPoint *tmpArr = realloc(pointArr, sizeof(CLLocationCoordinate2D)*(arrayLa.count));
pointArr = tmpArr;
thisCoord.latitude = [[arrayLa objectAtIndex:i] floatValue];
thisCoord.longitude = [[arrayLo objectAtIndex:i] floatValue];
MKMapPoint point = MKMapPointForCoordinate(thisCoord);
pointArr[i] = point;
// Reset Map View Boundaries
if( point.x > ne_Pt.x - 500 ) ne_Pt.x = point.x + 1000;
if( point.y > ne_Pt.y - 500 ) ne_Pt.y = point.y + 1000;
if( point.x < sw_Pt.x + 500 ) sw_Pt.x = point.x - 1000;
if( point.y < sw_Pt.y + 500 ) sw_Pt.y = point.y - 1000;
// create the polyline based on the C-array of map Points
_routePath = [MKPolyline polylineWithPoints:pointArr count:arrayLa.count];
_routeBounds = MKMapRectMake(sw_Pt.x, sw_Pt.y, ne_Pt.x-sw_Pt.x, ne_Pt.y-sw_Pt.y);
// add the routePath overlay to the map, if it isn't empty
if (recState == REC && _routePath != nil) {
// zoom in on the route with the fresh bounding box, routeBounds
[self zoomInOnRoute];
[_mapView addOverlay:_routePath];
}
}
-(void) zoomInOnRoute {
[_mapView setVisibleMapRect:_routeBounds];
}
#pragma mark MKMapViewDelegate
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay {
if ([overlay isKindOfClass:[MKPolyline class]]) {
MKPolyline *route = overlay;
MKPolylineRenderer *routeRenderer = [[MKPolylineRenderer alloc] initWithPolyline:route];
routeRenderer.lineWidth = 3;
routeRenderer.strokeColor = [UIColor redColor];
return routeRenderer;
}
else return nil;
}
Can anyone help solve my problem?
Thanks!
It does look like a delegate issue. Have you tried putting a breakpoint on the addOverLay call just in case the 'if' is skipping it?
I do something similar and all works fine using MKOverlay and MKOverlayRender (based on the apple Breadcrumbs sample app but updated). The app displays a route that the user can save to CoreData. They can select from a table of saved routes and the route is rendered using MKOverlayRenderer.
Set the delegate
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.mapView setDelegate:self];
}
Create a MKOverlay and add to mapView
- (void)createRoute2
{
// Some CoreData stuff. Iterate through map points and create an overlay.
// Order by date ascending so we draw in sequential order
NSSortDescriptor *timeStampDescriptor = [[NSSortDescriptor alloc] initWithKey:#"pointDate" ascending:YES];
NSArray *sortDescriptors = #[timeStampDescriptor];
NSArray *routePts = [[self.selectedRoute mapDetail] sortedArrayUsingDescriptors:sortDescriptors];
// A long is a bit excessive just use an int is fine ( BUT might be a huge number of route points)
long nbrPts = routePts.count;
if (nbrPts < 2){
return;
}
CLLocationCoordinate2D mapPointLoc;
MKMapRect updateRect; // The map area
// Init the route
// FtfmapDetail is a managed object holding lat long (and other stuff)
FtfMapDetail *currentPt = (FtfMapDetail *)[routePts objectAtIndex:0];
mapPointLoc = CLLocationCoordinate2DMake([currentPt.latitude floatValue], [currentPt.longitude floatValue]);
// self.route is a subclassed MapOverlay
if (!self.route)
{
// BGSMapOverlay is a subclassed MapOverlay
self.route = [[BGSMapOverlay alloc] initWithCenterCoordinate:mapPointLoc];
[self.mapView addOverlay:self.route];
}
// Add subsequent points. Kick off the for loop at int position 1 not 0
for (int i=1; i <nbrPts; i++){
currentPt = (FtfMapDetail *)[routePts objectAtIndex:i];
mapPointLoc = CLLocationCoordinate2DMake([currentPt.latitude floatValue], [currentPt.longitude floatValue]);
// AddCoordinate is a method in MKOverlay subclass that returns a bounding MKMaprect for all points in MkOverlay
updateRect = [self.route addCoordinate:mapPointLoc];
}
MKCoordinateRegion region = MKCoordinateRegionForMapRect(self.route.boundingMapRectCompleteRoute);
[self.mapView setRegion:region animated:YES];
}
And make sure you add the delegate method
#pragma mark - MKMapView delegate
// self.routeViewRenderer is a sub-classed MKOverlayRendered (based on the Breadcrumbs app from apple CrumbPathView subclassed MKOverlayView)
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay{
if (!self.routeViewRenderer)
{
_routeViewRenderer = [[BGSMKOverlayRender alloc] initWithOverlay:overlay];
}
return self.routeViewRenderer;
}
Or it could be that you are not sending any coordinates to the MkPolyLine. In the following snippet if I uncomment the "NSArray *routeCoordinates = [[NSArray alloc]init];" to send a Nil array to the MKPolyLine then the code will run but the delegate doesn't get called. If routeCoordinates contains points then delegate is called and route displayed.
-(void)buildRouteOverlays
{
for (int i=0; i< _routeHeaders.count;i++)
{
_selectedRoute = (FtfMaps*) [_routeHeaders objectAtIndex:i];
NSLog(#"DEBUG route date : %#", _selectedRoute.dateMap);
NSArray *routeCoordinates = [self arrayRoutePointCoordinates];
// if a nil array is produce then MapOverlayRenerder is not called - nothing to render
// Test this by uncommenting:
// NSArray *routeCoordinates = [[NSArray alloc]init];
NSLog(#"DEBUG number of point in Route : %lu",(unsigned long)routeCoordinates.count);
// Just a quick test only process the first route
if (i==0){
MKPolyline *routePolyLine = [self polyLineFromArray:routeCoordinates];
[self.mapView addOverlay:routePolyLine];
}
}
}
-(MKPolyline*)polyLineFromArray:(NSArray*)routePoints
{
NSInteger pointsCount = routePoints.count;
CLLocationCoordinate2D pointsToUse[pointsCount];
for(int i = 0; i < pointsCount; i++) {
FtfMapDetail *mapPt = (FtfMapDetail *) [routePoints objectAtIndex:i];
pointsToUse[i] = CLLocationCoordinate2DMake([mapPt.latitude doubleValue], [mapPt.longitude doubleValue]);
}
MKPolyline *myPolyline = [MKPolyline polylineWithCoordinates:pointsToUse count:pointsCount];
return myPolyline;
}
#pragma mark MKMapViewDelegate
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay {
if ([overlay isKindOfClass:[MKPolyline class]]) {
MKPolyline *route = overlay;
MKPolylineRenderer *routeRenderer = [[MKPolylineRenderer alloc] initWithPolyline:route];
routeRenderer.lineWidth = 3;
routeRenderer.strokeColor = [UIColor redColor];
return routeRenderer;
}
else return nil;
}

Resources