[iOS 7] [MapKit] ほんの数行で2地点間の経路を探索するMKDirections

2地点間の経路探索は、iOS 7以前でもやろうとすればできましたが、iOS 7から新しく追加されたMKDirectionsを使えばより簡単に実装できます!いつも通り、まずは実際に手を動かしてみましょう。

実際にソースコードを書いてみよう

今回はクラスメソッドの最寄り駅である秋葉原駅から、私の故郷の最寄り駅である黒磯駅までの経路を探索するソースコードを書きます。
一応言っておきますが、このサンプルコードはXcode 5で書いて実行して下さいね!

早速ですが、プロジェクトを作成しましょう。Single View Applicationを選択してプロジェクトを作成しましょう。その他の設定は何でもいいです。

ios7-mapkit-mkdirections-1

プロジェクトを作成したら、今回は地図関連の機能を使いますので、MapKit.frameworkをリンクします。

ios7-mapkit-mkdirections-2

これで準備OK。あとは、ViewController.mを以下のように変更して実行してみましょう。

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

@interface ViewController () <MKMapViewDelegate>

@property (nonatomic) MKMapView *mapView;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.mapView = [[MKMapView alloc] initWithFrame:self.view.bounds];
    self.mapView.delegate = self;

    // マップの中心を設定する
    CLLocationCoordinate2D center = CLLocationCoordinate2DMake(35.84230806912384, 139.8394775390625);
    self.mapView.region = MKCoordinateRegionMakeWithDistance(center, 200000, 200000);

    [self.view addSubview:self.mapView];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    // 出発地の経度・緯度からマップアイテムを生成する(秋葉原駅)
    CLLocationCoordinate2D sourceCoordinate = CLLocationCoordinate2DMake(35.698683, 139.774219);
    MKPlacemark *sourcePlacemark = [[MKPlacemark alloc] initWithCoordinate:sourceCoordinate
                                                         addressDictionary:nil];
    MKMapItem *source = [[MKMapItem alloc] initWithPlacemark:sourcePlacemark];

    // 目的地の経度・緯度からマップアイテムを生成する(黒磯駅)
    CLLocationCoordinate2D destCoordinate = CLLocationCoordinate2DMake(36.969995, 140.059509);
    MKPlacemark *destPlacemark = [[MKPlacemark alloc] initWithCoordinate:destCoordinate
                                                         addressDictionary:nil];
    MKMapItem *dest = [[MKMapItem alloc] initWithPlacemark:destPlacemark];

    // 生成したマップアイテムを出発地・目的地にセットしてリクエストを生成
    MKDirectionsRequest *request = [[MKDirectionsRequest alloc] init];
    request.source = source;
    request.destination = dest;
    request.requestsAlternateRoutes = YES;

    // リクエストをセットしてルート探索開始!
    MKDirections *directions = [[MKDirections alloc] initWithRequest:request];
    [directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response, NSError *error) {
        if (error) {
            NSLog(@"error : %@", error);
            return;
        }

        // 見つかったルートを全てオーバーレイとして追加する
        for (MKRoute *route in response.routes) {
            [self.mapView addOverlay:route.polyline];
        }
    }];
}

- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay
{
    MKPolylineRenderer *renderer = [[MKPolylineRenderer alloc] initWithOverlay:overlay];
    renderer.lineWidth = 4.0;

    // インデックスに応じて5色に振り分ける
    NSInteger index = [mapView.overlays indexOfObject:overlay];

    if (index % 5 == 1) {
        renderer.strokeColor = [UIColor redColor];
    } else if (index % 5 == 2) {
        renderer.strokeColor = [UIColor yellowColor];
    } else if (index % 5 == 3) {
        renderer.strokeColor = [UIColor greenColor];
    } else if (index % 5 == 4) {
        renderer.strokeColor = [UIColor blueColor];
    } else {
        renderer.strokeColor = [UIColor purpleColor];
    }

    return renderer;
}

@end

ソースコードの解説

簡単に説明すると、まず画面に配置するMKMapViewインスタンスを保持するためにプロパティとしてmapViewを定義します(6行目)。
- viewDidLoadメソッドで画面いっぱいに表示されるようなMKMapViewインスタンスを、自身をデリゲートに設定して生成します(16,17行目)。自身をデリゲートに設定するのは、後ほど出てくる探索した経路を描画するために必要なためです。
また、マップの初期表示位置を決めなければならないので設定します。今回は秋葉原駅と黒磯駅のちょうど中間あたりを事前に調べたので、その地点を中心点とします(20,21行目)。
余談ですが、MKCoordinateRegionMakeWithDistance(CLLocationCoordinate2D centerCoordinate, CLLocationDistance latitudinalMeters, CLLocationDistance longitudinalMeters)関数を使用すれば、ある地点から指定した距離まで表示できるようなズームレベルを設定できます。

次に- viewDidAppearメソッドのコードを解説します。この辺が今回の本題である経路探索するためソースコードになります。
やり方は非常に簡単で、まず出発地と目的地のMKMapItemインスタンスを生成します(31〜34行目と37〜40行目)。
次に、これらのMapItemを用いてMKDirectionsRequestインスタンスを生成します。
これで、MKDirectionsを利用する準備は整いました!

あとは、作成したMKDirectionsRequestインスタンスを引数にセットしてMKDirectionsインスタンスを生成し、- calculateDirectionsWithCompletionHandler:メソッドを実行するだけです。 - calculateDirectionsWithCompletionHandler:で渡したブロックは読んで字のごとく経路探索が完了したときに実行されます。
探索が完了すると、ブロックの第一引数にMKDirectionsResponseインスタンスが渡されます。このクラスに定義されるNSArray型のプロパティroutesに探索した経路がセットされます。routesプロパティには、経路を表すMKRouteインスタンスの配列が保持されます。
配列ですので、もちろん経路が複数ある場合もあります。探索結果が複数ある場合は所要時間が短い順に並びます。
最後にMKRouteにはプロパティにpolylineを持っているので、mapViewにこのpolylineをオーバーレイとして追加すれば描画の設定は完了です。
この辺がiOS 7で追加された新機能を使ってるところになります(49〜60行目)。

残りは- mapView:rendererForOverlay:メソッドですが、この辺はiOS 6のときと同じです。

実行例

実行すると、以下のように探索した経路が地図上に描画されるはずです。

ios7-mapkit-mkdirections-3

2地点間の最短経路の所用時間だけ取得する

2地点間の最短経路の所用時間だけ取得するは、- calculateETAWithCompletionHandlerメソッドを使います。

MKDirections *directions = [[MKDirections alloc] initWithRequest:request];
[directions calculateETAWithCompletionHandler:^(MKETAResponse *response, NSError *error) {
    if (error) {
        NSLog(@"error : %@", error);
        return;
    }

    // 最短経路の所用時間をコンソールに表示する
    NSLog(@"%lf", response.expectedTravelTime);
}];

まとめ

サンプルのソースコード通り、非常に簡単に実装できることがわかりますね。今回紹介したMKDirections- calculateDirectionsWithCompletionHandler:- calculateETAWithCompletionHandlerメソッドですが、内部でAppleのサーバに問い合わせを行っているため時間がかかることがあります。なので、実際に使用するときはローディング中である旨を表示したりしましょう!