Mapbox for iOSでサードパーティ製のスタイルを設定する

##はじめに
地図系のフレームワークで有名で、最近ではゼンリンとの提携がニュースになったりしたMapboxはiOS向けにもフレームワークを提供しています。

##Mapbox for iOSの導入
ただ地図を表示するだけなら、チュートリアルのとおりとてもシンプルに書けます。

```swift
import Mapbox
 
class ViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
 
    let url = URL(string: "mapbox://styles/mapbox/streets-v11") //Mapbox公式スタイル
    let mapView = MGLMapView(frame: view.bounds, styleURL: url)
    mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    mapView.setCenter(CLLocationCoordinate2D(latitude: 59.31, longitude: 18.06), zoomLevel: 9, animated: false)
    view.addSubview(mapView)
  }
}
```

しかし上記コードのコメントにも書いたとおり、基本的には公式スタイルを設定するように作られています。
スタイルは、<b>.json形式のデータにも関わらず</b>、です。
公式スタイルを参照するのが最もラクですが、そのためにはAPIトークンが必要で、アクセス数に応じて料金が発生します。
けれどもMapbox公式スタイルでなくとも、OpenStreetMapなどオープンデータを活用する事も出来るはず…という事で色々試しました。

##サードパーティ製のスタイルを適用する
Mapboxのスタイルは、MGLMapViewの初期化時にstyleURLという引数で設定します。
つまりURLを渡さなければなりませんが、どこかのサーバーにJSONファイルを置いておく、というのはナンセンスです。ローカルで完結させたい。

という訳で、①:好きなデータ等を設定したスタイルを.json形式で作成する、②:①のローカルアドレスを取得しMGLMapViewに渡す、という方針になりました。

###①：スタイルを.json形式で作成する
Swiftでの.jsonの取り扱い等は本記事では割愛します。
スタイルの.jsonファイル、言い換えると辞書形式のデータ構造は以下のとおりです。

```swift
    private var style:[String:Any] = [
        "version":8,
        "sources":[],
        "layers":[],
    ]
```

versionは8で固定です。sourcesはレイヤーデータの形式等の設定、layersはsourcesのデータをどのように表示するか（透過度など）の設定となっています。
ここではsourcesとlayersに適切な初期データを与えて、.jsonデータを作成します。

```swift
    mutating func setBasemap(name:String, tileUrlStr:String, attributionUrl:String="", tileSize:Int=256) {
        let sources = [
            name:[
                "type":"raster",
                "tiles":[tileUrlStr],
                "attribution":"<a href='" + attributionUrl + "'>" + name + "</a>",
                "tileSize":tileSize
            ]
        ]
        
        let layers = [
            [
                "id":name,
                "type":"raster",
                "source":name,
                "minzoom":0,
                "maxzoom":18
            ]
        ]
        
        self.style["sources"] = sources
        self.style["layers"] = layers
    }
```

上記のコードは、とあるラスターレイヤーをスタイルに設定するサンプルです。
レイヤーのnameとタイルのtileUrlStrを与えてやればスタイルに追記します。
.jsonファイルの出力については、長くなるのでサンプルだけ貼ります。

```swift
    func writeJson(outputDir:String, filename:String) -> URL? {
        let nsHomeDir = NSHomeDirectory()
        let outputPath = nsHomeDir + outputDir + "/" + filename + ".json"
        
        do {
            let jsonData = try JSONSerialization.data(withJSONObject: self.style, options: .prettyPrinted)
            try jsonData.write(to: URL(fileURLWithPath: outputPath))
            return URL(fileURLWithPath: outputPath)
        } catch {
            print("error")
            return nil
        }
    }
```

### ②：①のローカルアドレスを取得しMGLMapViewに渡す
①のローカルアドレスは、writeJson()の返り値です。つまり以下のとおり書けます。

```swift
let tmpStyleUrl = msManager.writeJson(outputDir: "/tmp", filename: "style")
mapView = MGLMapView(frame: rect, styleURL: tmpStyleUrl!)
```

ここで、msManagerとはスタイル全般を取り扱うクラスです。
アプリを起動する度に、writeJson()でiOS内の/tmpにstyle.jsonとして出力しています。
/tmpは、アプリを終了した後のデータの保存は保証されない、一次保存用フォルダです。
今回のような用途にもってこいですね。

さて、これで純正スタイルを設定する必要がなくなりました。
API tokenを削除してみましたが、問題なく動作します。API tokenはMapboxスタイルにアクセスするためのものでした。