I am working on a project where I need to draw a line on map with each update of user location to show a full route where user has travelled.
I am currently using Mapbox iOS SDK (v4.0.2) for this. To achieve above goal I am using MGLPolyline to draw a line over map.
With the following code provided, I am facing an issue as some default shape automatically get drawn before the route gets started.
Following is the code which I have implemented in my project :
#interface ViewController () <MGLMapViewDelegate>
{
MGLPolyline *polyline;
}
#end
- (void)mapView:(MGLMapView *)mapView didUpdateUserLocation:(MGLUserLocation *)userLocation
{
CLLocationCoordinate2D coord[1];
coord[0] = userLocation.coordinate;
if (polyline)
{
[polyline appendCoordinates:coord count:sizeof(coord)];
}
else
{
polyline = [MGLPolyline polylineWithCoordinates:coord count:sizeof(coord)];
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^(void) {
[weakSelf.mapView addAnnotation:polyline];
});
}
}
- (CGFloat)mapView:(MGLMapView *)mapView alphaForShapeAnnotation:(MGLShape *)annotation
{
return 1.0f;
}
- (CGFloat)mapView:(MGLMapView *)mapView lineWidthForPolylineAnnotation:(MGLPolyline *)annotation
{
return 5.0f;
}
- (UIColor *)mapView:(MGLMapView *)mapView strokeColorForShapeAnnotation:(MGLShape *)annotation
{
return [UIColor redColor];
}
i've done similar project before with swift.
user selects two point on map for beginning and destination then we use a google service which takes two point and gives list of point for best route and i was using this method to draw a polyLine
i think you have two way to do this
1_using didupdate func of mapkit for saving all point that user moves then use the func i put below for drawing poly line
2_take the beginning and destination points then draw a polyline
func addPolyLineToMap(googlemaplist: [CLLocation?]){
var coordinates = googlemaplist.map({ (location: CLLocation!) -> CLLocationCoordinate2D in
return location.coordinate
})
print("locatios count")
print(googlemaplist.count)
var polyline = MKPolyline(coordinates: &coordinates, count: googlemaplist.count)
DispatchQueue.main.async {
self.MapKit.add(polyline)
}
}
Related
I am already using the method GMSGeometryContainsLocation in order to determine if the coordinate (from a tap user) is within GMSPolygon, but I can't make it work for GMSPolyline.
-(void) didTapOnPolyline:(CLLocationCoordinate2D)coordinate andMap:(GMSMapView *)mapView {
if (_pathVS != nil) {
for (id key in _pathVS) {
if (GMSGeometryIsLocationOnPath(coordinate, [_pathVS objectForKey:key], YES)) {
_myMarker = [GMSMarker markerWithPosition:coordinate];
_myMarker.opacity = 1.f;
// _myMarker.icon = [UIImage imageNamed:#"marker-maps"];
_myMarker.map = mapView;
_myMarker.userData = key;
[_mapView setSelectedMarker:_myMarker];
}
}
}
}
I am calling this method from the triggered delegate method :
-(void) mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate
{
if (_myMarker)
_myMarker.map = nil;
[self didTapOnPolygon:coordinate andMap:mapView];
[self didTapOnPolyline:coordinate andMap:mapView];
}
If the tapped point is not exactly on the poly line then GMSGeometryIsLocationOnPath condition will never return true. So you have to use GMSGeometryIsLocationOnPathTolerance
Google Map SDK document says
A point that is not equal to a vertex is on one side or the other of any path segment -- it can never be "exactly on the border"
BOOL GMSGeometryIsLocationOnPathTolerance (CLLocationCoordinate2D point, GMSPath *path, BOOL geodesic, CLLocationDistance tolerance)
Returns whether point lies on or near path, within the specified tolerance in meters.
I'm creating an array representing a path made by the user by adding the user's current location to an array. Each time a new point is being added, I remove the previous annotation, and add a new one. then the new path is being drawn on a mapView with RMShape and layerForAnnotation. The problem is that every time the RMSape layer is being drawn, it has like a slide transition.
I have 2 questions:
How do I fix this "slide" effect, and draw a continuous line representing the user's path smoothly?
Is there a better way for tracing user's path?
.
-(void)mapView:(RMMapView *)mapView didUpdateUserLocation:(RMUserLocation *)userLocation
{
if (path) {
[self.mapView removeAnnotation:path];
}
path = [[RMShapeAnnotation alloc] initWithMapView:mapView points:points];
[mapView addAnnotation:path];
}
- (RMMapLayer *)mapView:(RMMapView *)mapView layerForAnnotation:(RMAnnotation *)annotation
{
if (annotation.isUserLocationAnnotation) {
return nil;
}
if ([annotation isKindOfClass:[RMShapeAnnotation class]]) {
for (int i = 0; i < points.count; i++) {
CLLocation *location = points[i];
[self.path addLineToCoordinate:location.coordinate];
}
return self.path;
}
return nil;
}
I would update your annotation layer directly as opposed to constantly replacing your annotation and thus its layer. You can do this by obtaining annotation.layer, casting it to RMShape, and then using methods like -addLineToCoordinate: to update it.
How would I create an overlay that colors the entire map a certain color? Then I need to be able to place annotations on top of it. Any ideas? Thanks.
What you want is MKOverlay and MKOverlayView.
You can find apple's code in one of the apps mentioned in 'Related sample code'. in above protocol and class reference page.
EDIT : As Per the comments previous code was not working as-is. Heres a MKMapDimOverlay GitHub project which you can simply integrate using CocoaPods. I have also made the relevant changes in following code in the answer.
To explain briefly, following is code for adding a dark overlay on entire map.
You need to create an overlay and add it to the map view.
MKMapDimOverlay *dimOverlay = [[MKMapDimOverlay alloc] initWithMapView:MapView];
[mapView addOverlay: dimOverlay];
Create and return MKOverlayView for the specific MKOverlay in 'viewForOverlay' delegate method
-(MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay {
if([overlay isMemberOfClass:[MKMapDimOverlay class]]) {
MKMapDimOverlayView *dimOverlayView = [[MKMapDimOverlayView alloc] initWithOverlay:overlay];
return dimOverlayView;
}
}
Since all you want is a colored overlay covering the map, your overlay and overlay view implementation will be very simple.
DimOverlay.m
#interface DimOverlay ()
#property (nonatomic) CLLocationCoordinate2D dimOverlayCoordinates;
#end
#implementation DimOverlay
-(id)initWithMapView:(MKMapView *)mapView {
self = [super init];
if(self)
{
self.dimOverlayCoordinates = mapView.centerCoordinate;
}
return self;
}
-(CLLocationCoordinate2D)coordinate {
return self.dimOverlayCoordinates;
}
-(MKMapRect)boundingMapRect {
return MKMapRectWorld;
}
#end
DimOverlayView.m
#implementation DimOverlayView
- (void)drawMapRect:(MKMapRect)mapRect
zoomScale:(MKZoomScale)zoomScale
inContext:(CGContextRef)ctx {
/*
You can allow custom colors and opacity values.
Simply add UIColor and CGFloat properties in the overlay view class
and use those properties instead of the default hardcodes values below.
*/
CGContextSetAlpha(ctx, 0.85);
CGContextSetFillColorWithColor(ctx, [UIColor blackColor].CGColor);
CGContextFillRect(ctx, [self rectForMapRect:mapRect]);
}
#end
So I want to display where my app's user walked on a MKMapView, I collect datas with the following code :
#pragma mark - CLLocationManagerDelegate
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
// calc. distance walked
CLLocationDistance meters = [newLocation distanceFromLocation:oldLocation];
self.totalMetters += meters;
[[self labelDistance] setText:[self formatDistanceIntoString:self.totalMetters]];
// create another annotation
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
annotation.coordinate = newLocation.coordinate;
// Also add to our map so we can remove old values later
[self.locations addObject:annotation];
// Remove values if the array is too big
while (self.locations.count > 100)
{
annotation = [self.locations objectAtIndex:0];
[self.locations removeObjectAtIndex:0];
// Also remove from the map
[self.map removeAnnotation:annotation];
}
Once it's finished, I call my draw method :
[self drawRoute];
Which contains the following :
- (void)drawRoute {
NSLog(#"drawRoute");
NSInteger pointsCount = self.locations.count;
CLLocationCoordinate2D pointsToUse[pointsCount];
for(int i = 0; i < pointsCount; i++) {
MKPointAnnotation *an = [self.locations objectAtIndex:i];
pointsToUse[i] = CLLocationCoordinate2DMake(an.coordinate.latitude,an.coordinate.latitude);
}
MKPolyline *myPolyline = [MKPolyline polylineWithCoordinates:pointsToUse count:pointsCount];
[self.map addOverlay:myPolyline];
}
Finally my mapView delegate :
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay {
NSLog(#"route");
if ([overlay isKindOfClass:MKPolyline.class]) {
MKPolylineView *lineView = [[MKPolylineView alloc] initWithOverlay:overlay];
lineView.strokeColor = [UIColor greenColor];
return lineView;
}
return nil;
}
Obviously my controller is MKMapView Delegate conform
#interface NewWalkViewController : UIViewController <CLLocationManagerDelegate, MKMapViewDelegate>
And the mapView in the Storyboard is linked to the controller (outlet and delegate)
I use the "bike road" debug tool and there is the output :
2014-01-25 20:27:30.132 The walking dog[2963:70b] new location : 37.330435
2014-01-25 20:27:30.133 The walking dog[2963:70b] drawRoute
As I can see the method for drawing the overlay is never called, and I don't have a single clue how to fix it.
The main problem is that in drawRoute, this line is passing latitude for both parameters to CLLocationCoordinate2DMake:
pointsToUse[i] = CLLocationCoordinate2DMake
(an.coordinate.latitude,an.coordinate.latitude);
This results in the line being drawn in a different part of the world than where the actual an.coordinate is. For example, if an.coordinate is 37,-122 (somewhere near San Francisco), the line is being drawn instead at 37,37 (somewhere in southern Turkey).
Since you are not actually positioning the map at the wrong location (you are looking for the line at the "right" location), viewForOverlay is never called because the map only calls it when it's possible that the overlay will be visible.
Change that line to:
pointsToUse[i] = CLLocationCoordinate2DMake
(an.coordinate.latitude,an.coordinate.longitude);
or simply:
pointsToUse[i] = an.coordinate;
As James Frost mentions in the comments, as of iOS 7, you should be using rendererForOverlay instead of viewForOverlay though the map view will still call viewForOverlay in iOS 7 if rendererForOverlay has not been implemented. Though this isn't preventing your overlay from displaying in the current case, you should implement the new delegate method as well as the old one (if the iOS 7 app will also be running on iOS 6 or earlier).
Another important but unrelated issue is that you are unnecessarily creating multiple, overlapping overlays. In drawRoute, since the overlay you are creating includes all the locations, you should remove any existing overlays before adding the new one. Otherwise, the map ends up with an overlay for location 0 to 1, an overlay for location 0 to location 2, an overlay for location 0 to location 3, etc. The previous overlays are not obviously visible since they have the same color and line width. Before the addOverlay line, put:
[self.map removeOverlays:self.map.overlays];
I've looked at several StackOverflow posts and Apple documentation on how to implement overlays in MKMapView. For me, I'm interested specifically in displaying MKPolygon objects on my map. I've found that fundamentally, the process boils down to the following:
Link to MapKit and CoreLocation frameworks
Make an outlet to an MKMapKit object and declare view controller as delegate
Declare a CLLocationCoordinate2D array containing the points of a polygon and create an MKPolygon object with the class method polygonWithCoordinates:count:
Call addOverlay: of map and pass the newly created MKPolygon object as the parameter
Implement (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay
Later on, I'll likely be having to display 20-30 polygons at a given time on the map. However, in my exploration of how to display overlays (hardcoding test examples right now, rather than reading in data from a file), I've found that I can get some overlays to appear, but not others. Reading the Location Awareness Programming Guide by Apple, I came across an example of a polygon overlaid above the state of Colorado. That worked. But when I tried to make a polygon that covered Kansas, I couldn't get it to work. It seems that any polygon that I tried to make on my own (Embry-Riddle Aeronautical University polygon and Kansas polygon) won't display, but those that I got online work perfectly. I used Google Earth to create the polygons and then exported them as KML files to get the coordinates.
Code for the implementation of my ViewController is below. Just trying to find out what I may be unintentionally doing wrong to create this problem. Thanks in advance for help.
#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
#interface ViewController ()
#end
#implementation ViewController
#synthesize mapView;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// Array of coordinates for polygon covering state of Colorado ... DISPLAYS PERFECTLY
CLLocationCoordinate2D points[4];
points[0] = CLLocationCoordinate2DMake(41.000512, -109.050116);
points[1] = CLLocationCoordinate2DMake(36.99892, -109.045267);
points[2] = CLLocationCoordinate2DMake(36.993076, -102.041981);
points[3] = CLLocationCoordinate2DMake(41.002371, -102.052066);
MKPolygon *polygon = [MKPolygon polygonWithCoordinates:points count:4];
[mapView addOverlay:polygon];
[polygon release];
// Array of coordinates for polygon covering state of Kansas ... DOESN'T DISPLAY
CLLocationCoordinate2D kansasPoints[9];
kansasPoints[0] = CLLocationCoordinate2DMake(-102.0595440241806, 39.99774930940907);
kansasPoints[1] = CLLocationCoordinate2DMake(-102.0424467175215, 36.99846609483674);
kansasPoints[2] = CLLocationCoordinate2DMake(-94.62550551403953, 36.98936020770036);
kansasPoints[3] = CLLocationCoordinate2DMake(-94.58798745384412, 39.11683771419185);
kansasPoints[4] = CLLocationCoordinate2DMake(-94.79955391183, 39.21290793052091);
kansasPoints[5] = CLLocationCoordinate2DMake(-95.13489191971419, 39.51613476830012);
kansasPoints[6] = CLLocationCoordinate2DMake(-94.86553124171813, 39.78380472206268);
kansasPoints[7] = CLLocationCoordinate2DMake(-95.02618283417986, 39.89072859904893);
kansasPoints[8] = CLLocationCoordinate2DMake(-95.31904155494097, 39.99390420513669);
MKPolygon *kansasPolygon = [MKPolygon polygonWithCoordinates:kansasPoints count:9];
[mapView addOverlay:kansasPolygon];
[kansasPolygon release];
// Array of coordinates for polygon covering part of Daytona Beach, FL campus
// of Embry-Riddle Aeronautical University... DOESN'T DISPLAY
CLLocationCoordinate2D erauPoints[7];
erauPoints[0] = CLLocationCoordinate2DMake(-81.05176, 29.18492);
erauPoints[1] = CLLocationCoordinate2DMake(-81.04409, 29.18801);
erauPoints[2] = CLLocationCoordinate2DMake(-81.05166, 29.19293);
erauPoints[3] = CLLocationCoordinate2DMake(-81.05365, 29.19536);
erauPoints[4] = CLLocationCoordinate2DMake(-81.05465, 29.19493);
erauPoints[5] = CLLocationCoordinate2DMake(-81.05376, 29.19323);
erauPoints[6] = CLLocationCoordinate2DMake(-81.05506, 29.19188);
MKPolygon *erauPolygon = [MKPolygon polygonWithCoordinates:erauPoints count:7];
[mapView addOverlay:erauPolygon];
[erauPolygon release];
// Array of coordinates taken from http://www.shawngrimes.me/2011/04/adding-polygon-map-overlays/
// for commuter parking lot at Capitol College in Maryland ... DISPLAYS PERFECTLY
CLLocationCoordinate2D commuterLotCoords[5]={
CLLocationCoordinate2DMake(39.048019,-76.850535),
CLLocationCoordinate2DMake(39.048027,-76.850234),
CLLocationCoordinate2DMake(39.047407,-76.850181),
CLLocationCoordinate2DMake(39.047407,-76.8505),
CLLocationCoordinate2DMake(39.048019,-76.850535)
};
MKPolygon *commuterPoly1 = [MKPolygon polygonWithCoordinates:commuterLotCoords count:5];
[mapView addOverlay:commuterPoly1];
[commuterPoly1 release];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay {
if ([overlay isKindOfClass:[MKPolygon class]]) {
MKPolygonView *polygonView = [[[MKPolygonView alloc] initWithOverlay:overlay] autorelease];
polygonView.fillColor = [[UIColor cyanColor] colorWithAlphaComponent:0.3f];
polygonView.strokeColor = [UIColor redColor];
polygonView.lineWidth = 1.0f;
return polygonView;
}
return nil;
}
#end
It looks like the latitude and longitude parameters of the coordinates for the polygons that don't display are backwards.
For example, this:
kansasPoints[0] = CLLocationCoordinate2DMake(-102.0595440241806, 39.99774930940907);
should be
kansasPoints[0] = CLLocationCoordinate2DMake(39.99774930940907, -102.0595440241806);
Also, you should not be calling release on the MKPolygon objects you are creating using polygonWithCoordinates since they will be autoreleased.