GIS入門 - 地理情報をiOSで活用する

22.4K Views

August 22, 24

スライド概要

iOSDC 2024での発表資料です。

プロポーザルはこちら:
https://fortee.jp/iosdc-japan-2024/proposal/71a271a5-faea-4083-b0f9-1fa1487e42d6

profile-image

フリーランスiOSエンジニア 「エンジニアと人生」コミュニティ主宰

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

(ダウンロード不可)

関連スライド

各ページのテキスト
1.

GIS入門 地理情報をiOSで活用する 堤 修一 (@shu223)

2.

自己紹介 • 堤 修一 • @shu223 (GitHub, Zenn, Qiita, note, Docswell, 𝕏, YouTube, Podcast, etc...) • 書籍(商業出版4冊、個人出版多数 @BOOTH):

3.

GISとは

4.

GIS(Geographic Information System: 地理情報システム) 地理空間情報(=位置情報+関連する情報) を扱うシステム 1 「地理空間情報:GISとは - 国土交通省」の記述:『様々な地理空間情報を重ね合わせて表示するためのシステム 』 1

5.

地図アプリもGISの一例 地図アプリ: 地理空間情報(=位置情報+関連する情報)を重ね 合わせて可視化し、いろんな機能(検索、ナ をつけたシステム ビゲーション等)

6.

今日のゴール 地図をしくみから理解し、iOSでさまざまな応用ができるよう になる 話さないこと(スコープ外) 特定のSDKやAPIの使い方解説 ソースコードも出てくるが、今回重要なのは概念やしくみの理解

7.

アジェンダ • GISの基礎 • 地図をゼロから実装する • 応用編

8.

アジェンダ • GISの基礎 • 地図をゼロから実装する • 応用編

9.

ラスターデータとベクトルデータ

10.

ラスターデータ • 要は画像データ • 位置情報はどう持つのか? • → タイルインデックスにより一意 に定まる(後述)

11.

ベクトルデータ 緯度経度の座標を使い、以下の3つの形状を表すデータ。 • 点(Point)・・・地点 • 線(LineString)・・・経路、路線 • 面(Polygon)・・・領域

12.

ベクトルデータと属性 位置に関する属性情報も付与可能 • 例 • 県境データ(面)に都道府県名や人口 • 道路データ(線)に路線名や制限速度

13.

地図タイル

14.

巨大な地理情報データを一度に配信できない → タイル状に分割 「地理院地図|地理院タイルについて」より

15.

タイルインデックス • タイルインデックス Z/X/Y • Z: ズームレベル • X: 横方向のインデックス • Y: 縦方向のインデックス → そのタイルの領域が一意に定まる 2 2 XYZ方式と呼ばれ、デファクトスタンダードとなっている。

16.

提供元が違うタイルでもインデックスが同じであれば同じ領域を表す 0/0/0 : 地球全体を表す1枚のタイル 地理院タイル 標準地図 OpenStreetMapタイル

17.

ズームレベルが1つ上がると1つのタイルが4つに分割 地理院タイル 標準地図

18.

ラスタータイルとベクトルタイル • ラスタータイル: ラスターデータのタイル • ファイルの実体は単なる画像(256x256 or 512x512が一 般的) • タイルインデックス自体が位置(領域)を表す • ベクトルタイル: ベクトルデータのタイル • 詳細は後述

19.

アジェンダ • GISの基礎 • 地図をゼロから実装する • 応用編

20.

MapKitや他のSDKを「使わずに」iOSで地図を 実装してみる “作れないものは、理解できない” ― リチャード・ファインマン 3 → 作りながら地図の仕組み/GISの基礎を理解していく 3 オライリー社の「ゼロから作るDeep Learning」シリーズより引用

21.

最小限の地図実装を目指す 1. ラスタータイルを使って地図を表示 2. 現在位置を表示 3. ズームレベルの変更

22.
[beta]
ステップ1: ラスタータイルの描画
国土地理院タイル「標準地図」を描画する

URLフォーマット:
https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png

let urlStr = String(format: "https://.../%d/%d/%d.png", z, x, y)
AsyncImage(url: URL(string: urlStr)!)

23.

完成 1. ラスタータイルを使って地図を表示

24.

ステップ2: 現在位置を表示する 現在位置(=緯度経度)をラスタータイル上にマッピングする Webメルカトル投影法 に基づき 4 経緯度 → ピクセル座標に変換 4 北緯および南緯85.051129度以上の描画を諦め、地球全体を正方形の地図として表現する投影法。地理院地図やGoogle Maps 等、多くの地図アプリケーションの土台となっている。

25.
[beta]
• 緯度経度 → ピクセル座標への変換実装

5

func latlonToXY(coord: CLLocationCoordinate2D, zoom: Double) -> (Int, Int)
{ ... }

• 現在位置取得 → ピクセル座標 → スクリーン座標
let (px, py) = latlonToXY(coord: location.coordinate, zoom: z) // ピクセル座標
let x = px / tileWidth
let y = py / tileWidth

print("タイルインデックス: \(z)/\(x)/\(y)")
// → タイル内ピクセル座標 → スクリーン座標

5

緯度経度をピクセル座標に変換するSwift実装

26.

完成 1. ラスタータイルを使って地図を表示 2. 現在位置を表示 (緯度経度→ピクセル座標→スクリ ーン座標)

27.

ステップ3: ズーム ズームレベルが1つ上がると4つのタイ ルに分割 • ズームイン: 1つのタイル領域に4つ のタイルを描画 • ズームアウト: 4つのタイル領域に1 つのタイルを描画

28.

• 4つのラスタータイルを描画 VStack(spacing: 0) { ForEach(0..<2, id: \.self) { row in HStack(spacing: 0) { ForEach(0..<2, id: \.self) { column in ... // タイル描画 } } } }

29.

完成 1. ラスタータイルを使って地図を表示 2. 現在位置を表示 3. ズームレベルの変更

30.

アジェンダ • GISの基礎 • 地図をゼロから実装する • 応用編

31.

もうちょっと「自前実装ならでは」感がほしい...

32.

ラスタータイルは予め地名などが埋め込まれているものが多い

33.

→ ベクトルタイルを利用する クライアント側で動的に描画するため、 見た目をカスタマイズ可能

34.

ベクトルタイルの描画方法 • ベクトルタイルを読み込む • Protocol Buffers形式のファイルをデコードする • デコードしたベクトルデータを使用して、iOSで地図として描 画する

35.

ベクトルタイルの中身を見てみよう • データ: OpenMapTiles の Kyoto のタイルセット 6 7 • Viewer: Mapbox Studio の Tilesets に読み込ませて表示 6 MapTiler社が提供している、OpenStreetMap や Natural Earth などの全世界の地図データをベクトルタイル化したもの 7 JapanやTokyoだと、データサイズやレイヤー数等で Mapbox Studioのアップロード制限 に引っかかる

38.

OpenMapTilesのスキーマ: openmaptiles.org/schema/ → 多くのレイヤーがあり、それぞれに多くのフィールドがあ り、さらにその値として多数のクラスが定義されている

39.

ベクトルタイルレンダラの自前実装は難しい • 地図のズームレベルや位置に応じて適切にスケーリングを行 う必要がある • 投影法を加味した複雑な計算が必要 • ほぼ全画面に大量のベクトルデータをレンダリングする必要 があり、かつ滑らかに動作する必要がある • OpenGLやMetalを使用し、GPUを活用して描画する

40.

→ ここからはゼロからではなくSDKを使います ただし • 「SDKの使い方」ではなく • あくまで地図のしくみを理解していろんな応用ができるよう に というのを主眼に置いて解説していきます

41.

こういうのはどう実現する? • 地名ラベルを非表示に • 道路を際立たせる • 山や川など歩けない場所は可視化 → ベクトルタイルの描画方法を変更したい → スタイルのカスタマイズ

42.

スタイル: 地図の視覚的な外観を定義するための設定 • ベクトルタイルのデータをどのように表示するかを定義する • 実態はJSON • 各社のスタイル仕様は互換性がない 8 9 8 Mapboxスタイルの仕様: https://docs.mapbox.com/style-spec/guides/ 9 スタイル仕様の互換性はないが、MapLibre Native SDKのように複数のスタイルをサポートしているSDKもある

43.

スタイルをサポートしているiOS SDK • Mapbox Maps Native SDK for iOS • MapLibre Native SDK for iOS • Mapboxから枝分かれしたOSS 10 どちらでも目的は果たせるが、今回はMapbox SDKを選択 10 Mapbox系マップSDK for iOSの系譜

44.

スタイルのカスタマイズ方法 • GUIエディタを利用(Mapbox Studio, MapTiler Cloud, Maputnik) • クライアントサイドでの動的なスタイル変更

45.

Mapbox Studioでスタイルをカスタマ イズする • 地名ラベルを非表示に • 道路を際立たせる • 山や川など歩けない場所は可視化 (詳細手順は別途記事にまとめます)

46.

スタイルを適用する スタイルのJSONをエクスポート 11 → アプリに組み込む let styleJSONURL = Bundle.main.url(..., withExtension: "json")! let styleJSON = try String(contentsOf: jsonURL) let options = MapInitOptions(styleJSON: styleJSON) 11 mapbox://styles/... のURIを直接指定する方法もある

47.

完成 スタイルをカスタマイズ(=ベクトルタ イルの描画方法を変更)した

48.

"スタイルをカスタマイズ(=ベクトルタイルの描画方法を変更)" 本当?

49.

ベクトルタイルどこいった? • コードに一切ベクトルタイルの気配がない let styleJSONURL = Bundle.main.url(..., withExtension: "json")! let styleJSON = try String(contentsOf: jsonURL) let options = MapInitOptions(styleJSON: styleJSON) • タイルインデックス(Z/X/Y)とかも1ミリも出てきていな い...

50.

style.json の中身をのぞいてみると...

51.

スタイルを定義するJSONファイルの中で、ベクトルタイルURL を指定している https://a.tiles.mapbox.com/v4/{タイルセットID}/{z}/{x}/{y}.vector.pbf (.pbfはProtocol Buffersでエンコードされたベクトルタイルの拡張子) → スタイルは明確にベクトルタイルを扱って見た目をカスタマ イズする仕組みであることがわかる

52.

3D表示

53.

スタイルをカスタマイズしたサンプル ポケモンGO

54.

地図を描画できていれば、それを3D的に描画すればOK 12 図は実際にiOSでSceneKitを使って地図を3D描画して作成したもの 12

55.

地形を立体的に表示するには? Appleマップの3D表示

56.

地形を立体的に表示するしくみ 標高のラスタータイルをハイトマップとして使う

57.

標高タイルの利用方法 標高値がラスタータイルのRGB値にエンコーディングされてい る MapTiler社のTerrain RGB形式 13 の場合: elevation = -10000 + ((R * 256 * 256 + G * 256 + B) * 0.1) → 標高値をハイトマップとして3D化に使える 13 国土地理院の標高タイルはまた違う形式でエンコーディングされている

58.

標高タイルをハイトマップ化 14 14 標高値 0m 〜 4000m を 0.0 - 1.0 に正規化したグレースケール画像として生成

59.

地図のラスタータイルをハイトマップで3D化 15 15 ハイトマップを使って2D画像を3D化する実装は、デプスマップを使った3D化実装(参考: iOS-Depth-Sampler)と同様

60.

富士山周辺の地図タイルを標高タイルで3D表示 16 16 地図タイルは「国土地理院 全国ランドサットモザイク画像」、標高タイルは「MapTiler Terrain RGB」を利用

61.

iOSでの3D表示 MapboxでもMapKitでもカメラでどの角度から見るか設定する だけ let cameraOptions = CameraOptions( center: ..., zoom: zoomLevel, pitch: 60) // 0度だと真上からみた2Dビューとなる let options = MapInitOptions(cameraOptions: cameraOptions, styleJSON: styleJSON) mapView = MapView(frame: view.bounds, mapInitOptions: options)

62.

3D地形表示のコード 17 // 標高のラスタータイルをデータソースとして追加している var demSource = RasterDemSource(id: "...") demSource.url = "mapbox://mapbox.mapbox-terrain-dem-v1" ... try! mapView.mapboxMap.addSource(demSource) var terrain = Terrain(sourceId: demSource.id) ... try! mapView.mapboxMap.setTerrain(terrain) 17 詳細なコードとその解説は別途Zenn記事にまとめます

63.

自キャラとモンスターの3Dモデルを設置 • 緯度経度からシーン内のXY座標へ変換し設置する • → Webメルカトル投影法(前述)を使う • SDKを使う場合は意識する必要なし 18 18 MapKitでもMapbox SDKでも、緯度経度だけで地図上の然るべき位置に設置できる(2Dと同様)

64.

デモ

65.

• マップの3D表示 • 地形も標高タイルで3D化 • 現在位置に自キャラ、公園のある位 置にモンスターの3Dモデルを設置

66.

まとめ • ラスターデータとベクトルデータ • 地図タイル/タイルインデックス • ベクトルタイルとスタイル • 3D表現 → 「地図のしくみ」を理解

67.

(おまけ)ビルの3D表示のしくみ ビルの3Dモデルを持っている? 19 「建物の領域を表すポリゴン」を「建物 の高さ」情報(ベクトルタイル内に持っ ている)を用いて3D的に表示している 20 19 3Dモデルとして描画されるケースもある 20 この機能を司るクラスはMapbox SDKでは FillExtrusionLayer と命名さ れており、"extrusion" は「押出成形」の意味

68.

kyotoのタイルセット: building レイヤーに(建物の領域を 表すポリゴンに加えて) • render_height • renderminheight というフィールドがあり、高さの情報を 持っている

69.

ご清聴ありがとうございました!