iOSでインドアマップ(屋内の地図)を表示する
iOSで、次のようにインドアマップ(屋内の地図)を地図上にオーバーレイ表示する機能があります。
地図上にインドアマップの「画像」をオーバーレイすること自体は昔から可能だった[1]のですが、iOS 13以降から IMDF (後述)というフォーマットで記述された GeoJSON をMKGeoJSONDecoder
というAPIでデコードし、MKMultiPolygon
, MKMultiPolyline
, MKMultiPolygonRenderer
, MKMultiPolylineRenderer
といったAPIを使用してマップ上にオーバーレイできるようになりました。データフォーマットが規格化され、さらに描画APIが整備されたことにより、汎用的かつ統一感のあるインドアマップ表示が可能になったわけです。
Indoor Mapping Data Format(IMDF)
IMDFは、インドアマップを表現するためのデータフォーマットです。GeoJSON (RFC-7946)をベースとする2Dフォーマットで、ひとつのIMDFデータは複数の.geojson
ファイルから構成されます。
IDMFにはFootprint(建物の物理的範囲)、Level(各階/フロア)、Unit(部屋等)といったFeature Typesが定義されており、建物や屋外の施設を統一的フォーマットで規定できるようになっています。[2]
IMDFのデコード
MKGeoJSONDecoder
iOS 13で追加されたMKGeoJSONDecoder
クラスを利用して、IMDFをデコードできます。
let data = try Data(contentsOf: fileURL)
let geoJSONDecoder = MKGeoJSONDecoder()
let geoJSONFeatures = try geoJSONDecoder.decode(data)
let features = geoJSONFeatures as! [MKGeoJSONFeature]
デコードが成功するとMKGeoJSONFeature
オブジェクトの配列が得られます。
MKGeoJSONFeature
MKGeoJSONFeature
クラスには次の3つのプロパティが定義されています。
var identifier: String? { get }
var properties: Data? { get }
var geometry: [MKShape & MKGeoJSONObject] { get }
properties
プロパティにはシリアライズされたJSONデータが入っており、次のようにJSONDecoder
を用いてデコードできます。
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
properties = try decoder.decode(Properties.self, from: feature.properties!)
またgeometry
プロパティにはMKGeoJSONObject
プロトコルに準拠するMKShape
型のジオメトリ情報が入っています。これは次の「描画」で使用します。
インドアマップの描画
IMDFをMKGeoJSONDecoder
でデコードし、取得したMKGeoJSONFeature
オブジェクトのgeometry
プロパティにはMKShape
を継承するオブジェクトが配列で入っていますが、試しにAppleのサンプルコード"Dinoseum"に入っているIMDFをデコードしてgeometryプロパティの中身を次のように出力してみると、
feature.geometry.forEach { shape in
if let overlay = shape as? MKOverlay {
print("\(overlay)")
}
}
実はMKPolygon
やMKPolyline
、MKMultiPolygon
といったMKOverlay
プロトコルに準拠したクラスのオブジェクトが入っていることがわかります。
これらのMKOverlay
に準拠するオブジェクト群を配列に入れて、MKMapView
のaddOverlays
メソッドに渡すと、
mapView.addOverlays(overlays)
MKMapViewDelegate
のmapView(_:rendererFor:)
が呼ばれるようになるので、MKOverlay
プロトコルに準拠するそれぞれのクラスに応じたレンダラ(MK◯◯Renderer
)を初期化して返します。
func mapView(_ mapView: MKMapView,
rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer: MKOverlayPathRenderer
switch overlay {
case is MKMultiPolygon:
renderer = MKMultiPolygonRenderer(overlay: overlay)
case is MKPolygon:
renderer = MKPolygonRenderer(overlay: overlay)
case is MKMultiPolyline:
renderer = MKMultiPolylineRenderer(overlay: overlay)
case is MKPolyline:
renderer = MKPolylineRenderer(overlay: overlay)
default:
return MKOverlayRenderer(overlay: overlay)
}
// レンダラの設定(strokeColor, lineWidth, fillColor, etc.)
return renderer
}
なお、MKMultiPolygon
, MKMultiPolygonRenderer
と、MKMultiPolyline
, MKMultiPolylineRenderer
はiOS 13で追加された新クラスです。それぞれ従来からあるMKPolygon
やMKPolyline
の「マルチ」版です。IMDFのサポートに合わせ、複雑な描画をサポートするために導入されたものと考えられます。
-
PDF形式のベクター画像を地図上にオーバーレイするサンプルがiOS 9の頃から公開されていた ↩︎
Discussion