[iOS] MKDirections を使った地図上への経路表示

# MKMapView 上への経路表示
MKMapView 上に経路を表示する際の覚え書き。MKMapView のコードは省略。経路表示に必要な部分だけのソースコード。

## [基本] 二点間の経路の描画
自分のプログラムに以下のコードを実装すれば、簡単に経路表示ができる。描画に必要な引数 coordinate は MKAnnotation の property で提供されている。つまり、地図上に複数の MKPointAnnotation をドロップして、ピンをタップした場合に、現在地からそのピンで示される場所までの経路を描画できる。

```objectivec
// 現在地と coordinate で示される座標間の経路を描画する
-(void)drawRouteWithCoordinate:(CLLocationCoordinate2D)coordinate
{
  __weak typeof(self)   weakSelf = self;
    
  MKPlacemark * placemark;
  placemark = [[MKPlacemark alloc]
               initWithCoordinate:coordinate addressDictionary:nil];
    
  MKMapItem * mapItem;
  mapItem = [[MKMapItem alloc] initWithPlacemark:placemark];
      
  MKDirectionsHandler   completionHandler = ^(MKDirectionsResponse * response, NSError * error) {
    if (!error) {
      if (response.routes.count > 0) { // 有効な経路が見つかった
        MKRoute * route = response.routes[0];
        [weakSelf.mapView addOverlay:route.polyline level:MKOverlayLevelAboveRoads];
      }
    }
  };

  MKDirectionsRequest * request = [MKDirectionsRequest new];
  request.transportType = MKDirectionsTransportTypeAutomobile; // 徒歩は Walking
  request.source        = [MKMapItem mapItemForCurrentLocation];
  request.destination   = mapItem;

  MKDirections * directions;
  directions = [[MKDirections alloc] initWithRequest:request];
  [directions calculateDirectionsWithCompletionHandler:completionHandler];
}

// XXX: 地図上に描画する経路の色などを指定（これを実装しないと何も表示されない）
#pragma mark - MKMapViewDelegate
-(MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id <MKOverlay>)overlay
{
  if ([overlay isKindOfClass:[MKPolyline class]]) {
    [self.overlays addObject:overlay];
    MKPolyline * polyline = overlay;
    MKPolylineRenderer * renderer;
    renderer = [[MKPolylineRenderer alloc] initWithPolyline:polyline];
    renderer.lineWidth   = 5.0f; // 描画する線の太さはココで指定
    renderer.strokeColor = [UIColor redColor]; // 経路の色指定はココ
    return renderer;
  }
  returni nil;
}
```

実際に利用する場合は、次のようなコードを実装する。地図上のピンをタップした時に、現在地からピンで表示される位置までの経路を描画する。

```objectivec
// ピンのタップで経路を描画する
#pragma mark - MKMapViewDelegate
-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)annotationView
{
  if ([annotationView.annotation isKindOfClass:[MKPointAnnotation class]]) {
    id <MKAnnotation>   annotation = annotationView.annotation;
    [self drawRouteWithCoordinate:annotation.coordinate];
  }
}

// overlay で描画した経路を除去する
#pragma mark - MKMapViewDelegate
-(void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)annotationView
{
  if (mapView.overlays.count > 0) {
    [mapView removeOverlays:mapView.overlays];
  }
}
```

## [応用] 複数の地点を経由した場合の描画

A点,B点,C点,D点,E点のような複数の地点を順に経由していく場合の経路描画を考える。各地点の情報は MKMapItem オブジェクトとして準備しておく。それを経由順に NSArray 配列に格納して渡す。実際のコードは次の通り。コード内に依存している部分がないので、そのまま自分のプログラムに取り込めると思う。

```objectivec
-(void)showRouteWithMapItems:(NSArray *)mapItems
{
  __weak typeof(self)   weakSelf = self;

  // MKMapView 上での拡大縮小およびスクロール操作などを禁止
  self.mapView.userInteractionEnabled = NO;

  // 通常、経路計算と描画は非同期並列処理で実施される。
  // しかし、A点からE点まで順に経路を表示したい場合は、逐次的な処理が必要となる。
  // そのため、ここでは semaphore を使った排他制御を利用する。
  dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

  // 処理はバックグラウンドキューで行う
  dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
  dispatch_block_t routingBlock = ^{
    MKDirectionsHandler completionHandler = ^(MKDirectionsResponse * response, NSError * error) {
      if (!error) {
        if (response.routes.count > 0) { // 有効な経路が見つかった
          dispatch_block_t mainBlock = ^{ // 経路描画は main スレッドで
            MKRoute * route = response.routes[0];
            [weakSelf.mapView addOverlay:route.polyline];
          };
          dispatch_async(dispatch_get_main_queue(), mainBlock);
        }
      }
      // 処理が完了したのでシグナル送信。次の処理を許可する。
      dispatch_semaphore_signal(semaphore);
    };
    NSUInteger n = mapItems.count - 1;
    for (NSUInteger i = 0; i < n; i++) {
      MKMapItem * srcItem = mapItems[i];
      MKMapItem * dstItem = mapItems[i+1];

      MKDirectionsRequest * request = [MKDirectionsRequest new];
      request.transportType     = MKDirectionsTransportTypeAutomobile;
      request.source            = srcItem;
      request.destination       = dstItem;
      request.requestsAlternateRoutes = NO; // YES で複数候補を返す。今回は不要。

      MKDirections * directions;
      directions = [[MKDirections alloc] initWithRequest:request];
      [directions calculateDirectionsWithCompletionHandler:completionHandler];
      // 排他制御を行って逐次処理を実現
      dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    }
    dispatch_block_t finishBlock = ^{
      // 全経路の描画が完了したら MKMapView 上でのユーザ操作を許可する。
      // BACKGROUND スレッド内なので main スレッド上で実行させるのを忘れずに。
      weakSelf.mapView.userInteractionEnabled = YES;
    };
    dispatch_async(dispatch_get_main_queue(), finishBlock);
  };
  dispatch_async(globalQueue, routingBlock);
}
```

たとえば、以下のように利用する。こちらのソースコードも NSArray で管理する annotations を準備すれば、そのまま自作のプログラムに採用できるでしょう。

```objectivec
-(void)drawRoute
{
  NSMutableArray * mapItems = [NSMutableArray new];

  // 最初の地点として現在地を設定
  [mapItems addObject:[MKMapItem mapItemForCurrentLocation]];

  // 管理している annotations 配列から順に MKMapItem を作成して mapItems に追加する
  for (MKPinAnnotation * annotation in self.annotations) {
    MKPlacemark * placemark;
    placemark = [[MKPlacemark alloc]
                 initWithCoordinate:annotation.coordinate
                 addressDictionary:nil];

    MKMapItem * mapItem;
    mapItem = [[MKMapItem alloc] initWithPlacemark:placemark];

    [mapItems addObject:mapItem];
  }
  [self showRouteWithMapItems:mapItems]; // 経路描画
}
```


なお、上記ソースコード内の MapKit の各 API の詳細は、クラスリファレンスを参照してね。