Labo288

プログラミングのこと、GISのこと、パソコンのこと、趣味のこと

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

はじめに

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

www.mapbox.com

Mapbox for iOSの導入

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

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)
  }
}

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

サードパーティ製のスタイルを適用する

Mapboxのスタイルは、MGLMapViewの初期化時にstyleURLという引数で設定されます。 つまりURLを渡さなければなりませんが、どこかのサーバーにデータを置いておく、というのはナンセンスです。 という訳で、①好きなデータ等を設定したスタイルを.json形式で作成する、②①のローカルアドレスを取得しMGLMapViewに渡す、という方針になりました。

①:スタイルを.json形式で作成する

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

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

versionは8で固定です。sourcesはレイヤーデータの形式等の設定、layersはsourcesのデータをどのように表示するか(色など)の設定となっています。 つまりsourcesとlayersに適切な初期データを与えて、.jsonデータを作成すれば良いわけです。

    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ファイルの出力については、長くなるのでサンプルだけ貼ります。

    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()の返り値です。つまり以下のとおり書けます。

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スタイルにアクセスするためのものでした。 つまりアクセス数による課金だとか、そういった恐れななくなります。

サンプル

国土地理院データを表示してみたサンプル

f:id:kiguchi999:20191002224707j:plain