MKMapSnapshotter を使った地図画像の生成

iOS 7 以降と OS X 10.9 以降では MKMapSnapshotter を利用して, 地図の画像 (UIImage) を非同期で生成することができます. これを利用すると MKMapView と同じ見た目の地図の画像をアプリケーション内で簡単に生成することができ, ユーザーによる操作を受けつける必要がない MKMapView を UIImageView などで置換して負荷を減らしたり, 地図画像をファイルに保存したりすることができます.

シンプルな例

もっとも単純に地図の画像を作成する例は以下のようになります.

let center = CLLocationCoordinate2D(latitude: 35.0, longitude: 135.0)
let span = MKCoordinateSpan(latitudeDelta: 1.0, longitudeDelta: 1.0)
let options = MKMapSnapshotOptions()
options.region = MKCoordinateRegion(center: center, span: span)
options.scale = UIScreen.mainScreen().scale
options.size = CGSize(width: 320.0, height: 320.0)

let snapshotter = MKMapSnapshotter(options: options)

snapshotter.startWithCompletionHandler({(snapshot, error) in
    let image = snapshot.image
})

まず, MKMapSnapshotOptions で region や画像のサイズなどを設定します. region は MKMapView と同じように設定します.

次に, MKMapSnapshotter を先ほど作成した options を使って作成し, startWithCompletionHandler で非同期で撮影処理を行います. 撮影が終了すると completionHandler が呼び出されます. startWithCompletionHandler を利用する場合, Swift だと Closure, Objective-C では Block を利用することになります.

snapshot.image に地図の画像が UIImage として入っているので, これを UIImageView を使って表示したり, ファイルに保存したりすることができます.

MKPinAnnotationView の描画

地図画像の上に MKPinAnnotationView を用いてピンを表示する場合は, MKMapSnapshotter を使って撮影した UIImage の上にさらにピンの画像を描画することによって実現します. MKPinAnnotationView を描画する例は以下のようになります.

let center = CLLocationCoordinate2D(latitude: 35.0, longitude: 135.0)
let span = MKCoordinateSpan(latitudeDelta: 1.0, longitudeDelta: 1.0)
let options = MKMapSnapshotOptions()
options.region = MKCoordinateRegion(center: center, span: span)
options.scale = UIScreen.mainScreen().scale
options.size = CGSize(width: 320.0, height: 320.0)

let snapshotter = MKMapSnapshotter(options: options)
snapshotter.startWithQueue(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), completionHandler: {(snapshot, error) in
    if error != nil { return }
    
    let image = snapshot.image
    
    let annotationView = MKPinAnnotationView(annotation: nil, reuseIdentifier: "Pin")
    annotationView.pinColor = .Green
    let pinImage = annotationView.image
    
    UIGraphicsBeginImageContextWithOptions(image.size, true, image.scale)
    
    image.drawAtPoint(CGPoint(x: 0.0, y: 0.0))
    
    var point = snapshot.pointForCoordinate(center)
    let pinCenterOffset = annotationView.centerOffset
    point.x -= annotationView.bounds.size.width / 2.0
    point.y -= annotationView.bounds.size.height / 2.0
    point.x += pinCenterOffset.x
    point.y += pinCenterOffset.y
    pinImage.drawAtPoint(point)
    
    let finalImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
})

options の設定は前のものと同じです. startWithQueue を使って completionHandler を実行する dispatch queue を明示的に指定しています. startWithCompletionHandler を用いた場合はメインスレッドで completionHandler が実行されます.

completionHandler 内部で MKPinAnnotationView を生成して, アノテーションビューの UIImage を地図の UIImage に重ねて描画し, 最終的な結果を finalImage に代入しています. この方法は他の AnnotationView でも利用できるはずです.

マルチスレッドのプログラミングを行うことになるので, completionHandler から UI を操作する場合などは注意が必要です. 必要に応じて dispatch_async(dispatch_get_main_queue(), {}) などを利用してください.

参考