天天看点

地图与定位(四)导航划线

本文我们介绍一下如何实现导航划线。

1. 自定义导航划线

地图开发中,常常需要我们为用途提供行进路线,在MapKit框架中提供了MKDirectionRequest对象用于计算路线,提供了MKDirections用于计算方向,这样一来只需要调用MKMapView的addOverlay等方法添加覆盖物即可实现类似的效果,下面我们来试一下。

下面是添加导航路线的具体步骤,当然在计算路线之前,我们需要对地理名称做地理编码获取地标,用于初始化MKPlacemark:

  • 对地理名称做地理编码获取地标
  • 初始化方向的请求对象
  • 设置方向的起点 –> 初始化地标对象MKPlacemark(类似于CLPlacemark,只是它在MapKit框架中,可以根据CLPlacemark创建MKPlacemark)。
  • 设置方向请求的终点
  • 通过方向的请求对象获得导航方向
  • 计算路径
  • 添加路线覆盖,必须实现代理方法

示例代码:

#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
#import <MapKit/MapKit.h>
#import "Annotation.h"

@interface ViewController ()<CLLocationManagerDelegate,MKMapViewDelegate>

@property (nonatomic, strong)CLLocationManager *locationManager;// 定位管家
@property (nonatomic, strong)CLGeocoder *geocoder;// 地理编码器
@property (nonatomic, strong)MKMapView *mapView;

@end

@implementation ViewController

- (CLLocationManager *)locationManager {

    if (!_locationManager) {

        _locationManager = [[CLLocationManager alloc] init];
        _locationManager.delegate = self;
    }
    return _locationManager;
}
- (CLGeocoder *)geocoder {

    if (!_geocoder) {
        _geocoder = [[CLGeocoder alloc] init];
    }
    return _geocoder;
}
- (MKMapView *)mapView {

    if (!_mapView) {

        _mapView = [[MKMapView alloc] initWithFrame:self.view.bounds];
        _mapView.delegate = self;
        _mapView.mapType =  MKMapTypeStandard;
        _mapView.userTrackingMode = MKUserTrackingModeFollow;
    }
    return _mapView;
}
- (void)viewDidLoad {
    [super viewDidLoad];

    [self.view addSubview:self.mapView];

    //1.判断手机定位服务是否打开
    if (![CLLocationManager locationServicesEnabled]) {
        NSLog(@"手机定位服务没有打开");
        return;
    }

    //2.iOS8.0以上的用户需要授权
    if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined) {
        if ([[[UIDevice currentDevice]  systemVersion] floatValue] >= ) {
            //调用此方法之前必须在plist文件中添加NSLocationWhenInUseUsageDescription --string-- 后面跟的文字就是提示信息
            [self.locationManager requestWhenInUseAuthorization];
        }
    }

    [self getSoucePlaceMark:@"北京" andDestinationMark:@"济南"];
}

// 获取地理位置在地图上的地标
- (void)getSoucePlaceMark:(NSString *)soucePlace andDestinationMark:(NSString *)destinationPlace {

    // 由地理位置通过地理编码获取地标
    [self.geocoder geocodeAddressString:soucePlace completionHandler:^(NSArray *placemarks, NSError *error) {
        if (error) {
            return;
        }
        // 1.获取起点的地标
        CLPlacemark *source = [placemarks firstObject];

        // 由于每次只能对一个地理位置做地理编码,所以更多的地理编码任务要嵌套在block中执行
        [self.geocoder geocodeAddressString:destinationPlace completionHandler:^(NSArray *placemarks, NSError *error) {

            if (error) {
                return;
            }
            // 2.获取终点的地标
            CLPlacemark *destination = [placemarks firstObject];

            // 3.获取起点和终点的地标后开始计算路线并做导航画线
            [self addLineFromSource:source toDestination:destination];
        }];

        // 3.设置地图显示的区域
        CLLocationCoordinate2D center = source.location.coordinate;// 获取中心点位置
        MKCoordinateSpan span = MKCoordinateSpanMake(, );// 显示跨度
        MKCoordinateRegion region = MKCoordinateRegionMake(center,  span);// 区域
        [self.mapView setRegion:region];
    }];
}

/**
 *  在sourcePm 和 desPm 之间添加线
 *
 *  @param source 起始位置
 *  @param destination    终点位置
 */
- (void)addLineFromSource:(CLPlacemark *)source toDestination:(CLPlacemark *)destination {

    // 1.初始化方向的请求对象
    MKDirectionsRequest *request = [[MKDirectionsRequest alloc] init];

    // 2.设置方向的起点 --> 初始化地标对象MKPlacemark
    // 根据地理坐标初始化地图坐标MKPlacemark -->传入地标CLPlacemark(地理编码获得)
    MKPlacemark *sourcePM = [[MKPlacemark alloc] initWithPlacemark:source];
    request.source = [[MKMapItem alloc] initWithPlacemark:sourcePM];

    // 3.设置方向请求的终点
    MKPlacemark *destinationPM = [[MKPlacemark alloc] initWithPlacemark:destination];
    request.destination = [[MKMapItem alloc] initWithPlacemark:destinationPM];

    // 4.通过方向的请求对象获得导航方向
    MKDirections *directions = [[MKDirections alloc] initWithRequest:request];

    // 5.计算路径
    [directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response, NSError *error) {

        NSLog(@"可能的路线条数:%ld",response.routes.count);
        for (MKRoute *route  in response.routes) {

            // 6.添加路线覆盖,必须实现代理方法
            [self.mapView addOverlay:route.polyline];
        }
    }];

    // 添加两个大头针标记起点和重点
    Annotation *fromAnno = [[Annotation alloc] init];
    fromAnno.coordinate = source.location.coordinate;
    fromAnno.title = source.name;
    [self.mapView addAnnotation:fromAnno];

    Annotation *toAnno = [[Annotation alloc] init];
    toAnno.coordinate = destination.location.coordinate;
    toAnno.title = destination.name;
    [self.mapView addAnnotation:toAnno];

}

#pragma mapViewDelegate
/**
 *  方法说明:添加导航路径时调用,必须复写该方法后才能完成地图画线
 *
 *  @param overlay:路线
 *
 *  @return MKOverlayRenderer:路线视图
 */
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay
{
    // MKPolylineRenderer
    //->MKOverlayPathRenderer
    //->MKOverlayRenderer
    //初始化 导航线
    MKPolylineRenderer *render = [[MKPolylineRenderer alloc] initWithOverlay:overlay];
    render.strokeColor = [UIColor greenColor];

    return render;
}
// 添加导航路线的时候调用
- (void)mapView:(MKMapView *)mapView didAddOverlayRenderers:(NSArray *)renderers {

    NSLog(@"+++++");
}

@end
           

2. 系统地图应用

除了可以使用MapKit框架进行地图开发,对地图有精确的控制和自定义之外,如果对于应用没有特殊要求的话选用苹果自带的地图应用也是一个不错的选择。使用苹果自带的应用时需要用到MapKit中的MKMapItem类,这个类有一个openInMapsWithLaunchOptions:动态方法和一个openMapsWithItems: launchOptions:静态方法用于打开苹果地图应用。第一个方法用于在地图上标注一个位置,第二个方法除了可以标注多个位置外还可以进行多个位置之间的驾驶导航,使用起来也是相当方便。在熟悉这两个方法使用之前有必要对两个方法中的options参数做一下简单说明:

常量 说明
MKLaunchOptionsDirectionsModeKey 路线模式,常量 MKLaunchOptionsDirectionsModeDriving 驾车模式MKLaunchOptionsDirectionsModeWalking 步行模式
MKLaunchOptionsMapTypeKey 地图类型,枚举 MKMapTypeStandard :标准模式MKMapTypeSatellite :卫星模式MKMapTypeHybrid :混合模式
MKLaunchOptionsMapCenterKey 中心点坐标,CLLocationCoordinate2D类型
MKLaunchOptionsMapSpanKey 地图显示跨度,MKCoordinateSpan 类型
MKLaunchOptionsShowsTrafficKey 是否 显示交通状况,布尔型
MKLaunchOptionsCameraKey 3D地图效果,MKMapCamera类型注意:此属性从iOS7及以后可用,前面的属性从iOS6开始可用

下面我们来演示一下如何在苹果自带地图应用上标记一个位置,首先根据反地理编码获得一个CLPlacemark位置对象,然后将其转换为MKPlacemark对象用于MKMapItem初始化,最后调用其openInMapsWithLaunchOptions:打开地图应用并标记:

示例代码:

#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
#import <MapKit/MapKit.h>

@interface ViewController ()

@property (nonatomic, strong)CLGeocoder *geocoder;

@end

@implementation ViewController

- (CLGeocoder *)geocoder {

    if (!_geocoder) {
        _geocoder = [[CLGeocoder alloc] init];
    }
    return _geocoder;
}

- (void)viewDidLoad {
    [super viewDidLoad];

    [self locationAddress:@"济南"];
}

- (void)locationAddress:(NSString *)address {

    [self.geocoder geocodeAddressString:address completionHandler:^(NSArray *placemarks, NSError *error) {
        if (error) return;

        // 1.取出地理编码的目的地的地标CLPlacemark
        CLPlacemark *placemark = [placemarks firstObject];

        // 2.将定位地标转化为地图的地标
        MKMapItem *toLocation = [[MKMapItem alloc] initWithPlacemark:[[MKPlacemark alloc] initWithPlacemark:placemark]];

        // 3.设置系统地图应用的显示模式
        NSMutableDictionary *options = [NSMutableDictionary dictionary];
        // 地图样式
        options[MKLaunchOptionsMapTypeKey] = @(MKMapTypeStandard);

        // 打开苹果自带地图应用
        [toLocation openInMapsWithLaunchOptions:options];
    }];
}
@end

       如果要标记多个位置需要调用MKMapItem的静态方法,下面的代码演示中需要注意,使用CLGeocoder进行定位时一次只能定位到一个位置,所以第二个位置定位放到了第一个位置获取成功之后。
- (void)locationAddressList {

    [self.geocoder geocodeAddressString:@"济南" completionHandler:^(NSArray *placemarks, NSError *error) {
        if (error) return;

        CLPlacemark *clPlacemark = [placemarks firstObject];//获取第一个地标
        // 将地理坐标转化成地图坐标
        MKPlacemark *mkPlacemark = [[MKPlacemark alloc]initWithPlacemark:clPlacemark];

        // 注意地理编码一次只能定位到一个位置,不能同时定位,所在放到第一个位置定位完成回调函数中再次定位

        // 根据“北京市”进行地理编码
        [self.geocoder geocodeAddressString:@"北京市" completionHandler:^(NSArray *placemarks, NSError *error) {
            if (error) return;

            CLPlacemark *clPlacemark1 = [placemarks firstObject];//获取第一个地标
            MKPlacemark *mkPlacemark1 = [[MKPlacemark alloc]initWithPlacemark:clPlacemark1];

            // 根据“天津市”进行地理编码
            [_geocoder geocodeAddressString:@"天津市" completionHandler:^(NSArray *placemarks, NSError *error) {

                if (error) return;

                CLPlacemark *clPlacemark2 = [placemarks firstObject];//获取第一个地标
                MKPlacemark *mkPlacemark2 = [[MKPlacemark alloc]initWithPlacemark:clPlacemark2];

                // 设置地图显示属性
                NSDictionary *[email protected]{MKLaunchOptionsMapTypeKey:@(MKMapTypeStandard)};

                // 获取标记位置
                MKMapItem *currentItem = [MKMapItem mapItemForCurrentLocation];//当前位置
                MKMapItem *mapItem = [[MKMapItem alloc] initWithPlacemark:mkPlacemark];
                MKMapItem *mapItem1 = [[MKMapItem alloc]initWithPlacemark:mkPlacemark1];
                MKMapItem *mapItem2 = [[MKMapItem alloc]initWithPlacemark:mkPlacemark2];

                // 打开地图并标记多个位置
                [MKMapItem openMapsWithItems:@[currentItem,mapItem,mapItem1,mapItem2] launchOptions:options];
            }];
        }];


    }];
}
       要使用地图导航功能在自带地图应用中相当简单,只要设置参数配置导航模式即可,例如在上面代码基础上设置驾驶模式,则地图应用会启动驾驶模式计算两点之间的距离同时对路线进行规划。
- (void)navagationToAddress:(NSString *)address {

    // 根据“天津市”进行地理编码
    [self.geocoder geocodeAddressString:address completionHandler:^(NSArray *placemarks, NSError *error) {

        if (error) return;

        CLPlacemark *clPlacemark = [placemarks firstObject];//获取第一个地标
        MKPlacemark *mkPlacemark = [[MKPlacemark alloc]initWithPlacemark:clPlacemark];

        // 设置地图显示属性
        NSDictionary *[email protected]{MKLaunchOptionsMapTypeKey:@(MKMapTypeStandard),MKLaunchOptionsDirectionsModeKey:MKLaunchOptionsDirectionsModeDriving};

        // 获取标记位置
        MKMapItem *currentItem = [MKMapItem mapItemForCurrentLocation];// 当前位置
        MKMapItem *mapItem = [[MKMapItem alloc]initWithPlacemark:mkPlacemark];// 目标位置

        // 打开地图并标记多个位置
        [MKMapItem openMapsWithItems:@[currentItem,mapItem] launchOptions:options];
    }];

}
           

总结:

由于定位和地图框架中用到了诸多类,有些初学者容易混淆,下面简单对比一下。

  • CLLocation:用于表示位置信息,包含地理坐标、海拔等信息,包含在CoreLoaction框架中。
  • MKUserLocation:一个特殊的大头针,表示用户当前位置。
  • CLPlacemark:定位框架中地标类,封装了详细的地理信息。
  • MKPlacemark:类似于CLPlacemark,只是它在MapKit框架中,可以根据CLPlacemark创建MKPlacemark。

继续阅读