MKTileOverlayを用いてMKMapViewにタイルを表示する

# はじめに
※この記事は[LIFULL Advent Calendar 2021](https://qiita.com/advent-calendar/2021/lifull)の15日目の記事となります。
※2日目に投稿したけど、まだ空いていたのでまた投稿しちゃいます。

MapKitでピンを立てたりルートを表示したりすることはあったけど、`MKTileOverlay`を使ってタイルを表示させることはなかったので、大きく二種類の方法があったので今回やってみたという記事です。

# サーバ上にあるタイル情報を用いる場合
## 使用する情報
今回はよく検索にもヒットする**[国土地理院](https://maps.gsi.go.jp/development/ichiran.html)**のデータを利用します。

注意点としては以下があります。
>地理院タイルをウェブサイトやソフトウェア、アプリケーション上でリアルタイムに読み込んで利用する場合、地理院タイルは出典の明示のみで申請不要でご利用いただけます。

アプリ開発で使う場合は出典を明示しておきましょう。

## 実装
StoryBoard上に`MKMapView`が既に配置されている前提とします。

```swift
// 国土地理院のデータから、今回は「淡色地図」タイルを利用する
let overlay = MKTileOverlay.init(urlTemplate:"https://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png")

// MKMapView にオーバーレイを追加する    
mapView.addOverlay(overlay)
```

これだけだとマップに変化はありません。
MKMapViewDelegateのrendererForメソッドを実装する必要があります。

```swift
extension HogeViewController: MKMapViewDelegate {
    func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
        return MKTileOverlayRenderer.init(overlay: overlay)
    }
}
```

##結果
オーバーレイを追加したことで右画像のように淡色地図が表示されます。
他にも国土地理院のデータでは航空写真や活断層図等、さまざまなデータが用意されています。

|標準地図|淡色地図|
|---|---|
|<img width="230" alt="標準地図.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550560/2a58c3b7-79bf-9d5a-3321-69478c78d8df.png">|<img width="230" alt="単色地図.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550560/aa913bf3-f0b7-de86-db67-dca1f2e275e3.png">|

# カスタムのタイル情報を用いる場合
タイルを表示する方法は先ほど実現した、国土地理院や他のサイトで**公開されているデータを用いる**か、自作のAPIから`タイル情報を受けとってその情報をもとにカスタムな表示をする`のどちらかに分かれるかと思います。
後者では`MKTileOverlay`の`loadTile()`メソッドを用いて実現します。

今回は、タイルの座標によって赤、青、緑のいずれかのタイルを重ねるようにしてみます。

## 実装
まずは`MKTileOverlay`を継承したカスタムクラスを定義します。
このクラスで`loadTile()`メソッドをオーバーライドします。

```swift:CustomTileOverlay.swift
import UIKit
import MapKit

class CustomTileOverlay: MKTileOverlay {

    override func loadTile(at path:MKTileOverlayPath, result:@escaping (Data?, Error?) -> Void) {
        UIGraphicsBeginImageContext(CGSize(width: 256,height: 256))

        guard let context = UIGraphicsGetCurrentContext() else {
            return
        }

        let r: CGFloat = ((path.x + path.y) % 3) == 0 ? 1 : 0
        let g: CGFloat = ((path.x + path.y + 1) % 3) == 0 ? 1 : 0
        let b: CGFloat = ((path.x + path.y + 2) % 3) == 0 ? 1 : 0
        let alpha: CGFloat = 0.5;
        let fill = CGColor(red: r, green: g, blue: b, alpha: alpha)

        context.setFillColor(fill)
        context.fill(CGRect(x: 0, y: 0, width: 256,height: 256))

        let tileImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        result(tileImage?.pngData(), nil)
    }
}
```

国土地理院のデータを扱っていた箇所を今回のオーバーレイを使うように書き換えてみましょう。
一行変更するだけで問題ありません。

```swift
// 国土地理院のデータを扱う場合
// let overlay = MKTileOverlay.init(urlTemplate:"https://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png")

// 今回作成したOverlayを呼び出す
let overlay = CustomTileOverlay()

// そのまま
mapView.addOverlay(overlay)
```

##結果
見事チェック模様にタイルが表示されるようになりましたね。
<img width="230" alt="チェックタイル.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550560/459d1ac3-8fa9-61ae-9f9e-d9efee5c336e.png">

# おまけ：特定の位置だけタイルを表示してみる
上記の例から、ある特定の場所だけにタイルを表示するような処理に書き換えてみます。
`loadTile`メソッドの引数にある`path: MKTileOverlay`は`x、y、z`というプロパティを持っています。
xとyはタイルを表示するためのx座標とy座標を表し、zは地図のズームレベルを表します。

今回は直撃ちで、`x=116397、y=51624、z=17`を指定してみます。

```swift:CustomTileOverlay.swift
override func loadTile(at path:MKTileOverlayPath, result:@escaping (Data?, Error?) -> Void) {
        UIGraphicsBeginImageContext(CGSize(width: 256,height: 256))

        guard
            let context = UIGraphicsGetCurrentContext(),
            path.x == 116397,
            path.y == 51624,
            path.z == 17 else {
                return
            }

        // 以降は同じ
    }
```

これで実行すると、渋谷付近にタイルが表示されるのを確認できました。
ちなみにズームレベルが17で指定しているので、このズームレベルよりも大きくても小さくても表示されません。
<img width="230" alt="特定位置地図.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550560/6c46d25d-a954-ac6e-32be-392a8ba6fcc5.png">

# 終わりに
普段触らないタイル表示は数行で実現はできるものでした。
後者のカスタムな表示方法は、独自のAPIを用意できると、より個人開発で活用できそうだなと思いました。

API開発のスキルは現在磨き中なので、自前でできるようにして色々遊べるようになりたい。。

# 参照

1. [国土地理院](https://maps.gsi.go.jp/development/ichiran.html)
1. [MKMapView の地図タイルをオーバーレイする](https://daisuke-t-jp.hatenablog.com/entry/2019/08/27/MKMapView-Tile-Overlay)
1. [SwiftUIでカスタム地図を表示する（その2） - Blogger](https://soratomichi.blogspot.com/2021/08/swift.html)
1. [loadTile(at:result:) | Apple Developer Documentation](https://developer.apple.com/documentation/mapkit/mktileoverlay/1452445-loadtile)