# Mapbox Maps SDKでスマートフォンアプリ開発
<!-- Put the link to this slide here so people can follow -->
松澤太郎 @ Georepublic
slide: https://hackmd.io/@smellman/foss4g-2019-kobe
---
## 自己紹介
- シニアエンジニア @ Georepublic
- コミュニティいろいろやってます
- OSGeo日本支部理事
- 日本UNIXユーザ会理事
- OpenStreetMap Foundation Japanメンバー
- ブレイクコアクラスタ
---
FOSS4G 2019 NiigataでYoutube配信担当をやりました。
---
## 配信風景
![Imgur](https://i.imgur.com/fagCBpFl.jpg)
---
## Youtube配信概要
- ビデオカメラ
- HDMI to USB
- Thinkpad X220
- ffmpeg (配信ソフト)
- htop (監視)
---
# 本題
---
Mapboxのスマートフォン向けのライブラリの紹介です。
注意: 今回は `mapbox gl js` の話ではありません。
注意: Slideに収めるためインデントが少し崩れているものがあります。
---
## Mapbox Maps SDK
- Mapboxが出しているネイティブ向けSDK
- Maps SDKと略している
- 公式サイトではiOS/Android/Unityに対応
- 今回はiOSとAndroidについてお話
- Github上にはXamarin/ReactNative/Node.jsなど
- Node.jsはサーバサイドレンダリングに利用
- コア部分はC++で作成
---
## Overview
| Platform | Development Languages |
| ------------- | --------------------- |
| iOS | Swift/Objective-C |
| Android | Kotlin/Java |
| Unity | C# |
| Xamarin | C# |
| React Native | JavaScript |
| Node.js | JavaScript |
---
## Mapbox GL Ecosystem
![Imgur](https://i.imgur.com/NubTHBG.png)
---
macOS向けにAppleScript対応とかやりすぎ
---
## 基本機能
- [Mapbox Style Specification](https://docs.mapbox.com/mapbox-gl-js/style-spec/)に対応
- たまに対応していない仕様もある
- プラットフォームごとに実装されている機能が違う
- Mapbox以外で配布しているStyleも使える
- Smartphone向けには事前ダウンロード機能がある
- iOSにはファイルの上限があったりする
- Mapboxのサービスを使う場合はAccessTokenが必要
---
今回はiOS/Android/~~React Native~~の実装を見ていく
React Nativeは間に合いませんでした(後述)
---
# Mapbox Maps SDK for iOS
- Swift/Objective-Cに対応したSDK
- StoryBoardなどのGUI開発にも対応
- インストール方法がいくつかある
- 手動
- CocoaPad
- Carthage
---
## 手動で構築
- Mapbox.frameworkをプロジェクトに追加
- Build phaseにshellを追加
- Info.plistに以下を追加
- MGLMapboxAccessToken
- ログインした状態でチュートリアルを見るとこれを使えって出てくる
- NSLocationWhenInUseUsageDescription
---
## 注意点
Mapbox公式のドキュメントは記述が古く、Mapbox.frameworkの追加の仕方が古いXCodeのやり方になっている
---
## Storyboardに追加
- Main.storyboardにViewを追加する
- クラスをUIViewからMGLMapViewに変更する
- styleのURLを記述
- あとは座標とか入れておくと自動的にその場所へ
---
![Imgur](https://i.imgur.com/FClMadf.png)
---
![Imgur](https://i.imgur.com/FHZIGOdl.png)
---
![Imgur](https://i.imgur.com/Jajo1ZZl.png)
---
## Marker(Annotation)を追加
- Annotationを反応できるようにDelegateを追加
- ViewController内でself.view.subviewsからMGLMapViewを探してあったらAnnotationを追加する
- storyboardを使うとExampleと違うものになりがちなので注意
- ユーティリティ関数を作ると良いかも
---
```swift
import UIKit
import Mapbox
class ViewController: UIViewController,
MGLMapViewDelegate {
...
}
```
---
```swift
override func viewDidLoad() {
super.viewDidLoad()
for v in self.view.subviews {
if let item = v as? MGLMapView {
item.delegate = self
let hello = MGLPointAnnotation()
hello.coordinate = CLLocationCoordinate2D(...)
hello.title = "..."
hello.subtitle = "..."
item.addAnnotation(hello)
}
}
}
```
---
```swift
// delegate
func mapView(_ mapView: MGLMapView,
viewFor annotation: MGLAnnotation)
-> MGLAnnotationView? {
return nil
}
func mapView(_ mapView: MGLMapView,
annotationCanShowCallout annotation: MGLAnnotation)
-> Bool {
return true
}
```
---
![imgur](https://i.imgur.com/ofnYHsSl.png)
---
# Mapbox Maps SDK for Android
- Java/Kotlinに対応
- 基本的にコードを書くスタイル
- インストールはGradleでやるので楽
---
## 環境構築
- Gradleの設定をapp/build.gradleに書き込む
- build.gradleが2つあるので注意
- permissionの設定をAndroidManifest.xmlに追加
- コードを書いて実装をする
---
![imgur](https://i.imgur.com/RA25E7ol.png)
---
![imgur](https://i.imgur.com/YOX9fdQl.png)
---
![imgur](https://i.imgur.com/9dzwDx9l.png)
---
## SymbolLayerを追加
- 8.xからMapView.addMarkerがdepricatedに
- SymbolLayerで代用する
- MapboxMap.OnMapClickListenerでクリックイベントをハンドリングする
- 以前のmarkerとかなり違う考え方
---
## 補足
- Layerを使うやり方自体はベースマップと処理を共通化するという考え方と思われる
- Mapbox的には正しいやり方
- iOSはAndroidと同じ実装ができるがこっちはまだmarkerの処理はdepricatedになっていない
- いずれなると思われる
- この仕様を調べるためにReact Nativeの実装調べきれませんでした
---
```java=
public void onMapReady(@NonNull final
MapboxMap mapboxMap) {
this.mapboxMap = mapboxMap;
this.features = new ArrayList<>();
Feature f = Feature.fromGeometry(
Point.fromLngLat(135.19890, 34.68505));
f.addStringProperty(TITLE_PROP, "KITTO");
f.addStringProperty(DESCRIPTION_PROP,
"Welcome to FOSS4G 2019 Kansai/Kobe");
this.features.add(f);
...
```
---
```java=
mapboxMap.setStyle(
new Style.Builder().fromUri("...")
.withImage(ICON_ID,
BitmapFactory.decodeResource(
MainActivity.this.getResources(),
R.drawable.red_marker))
.withSource(new GeoJsonSource(SOURCE_ID,
FeatureCollection.fromFeatures(this.features)))
.withLayer(new SymbolLayer(LAYER_ID, SOURCE_ID)
.withProperties(PropertyFactory.iconImage(ICON_ID),
PropertyFactory.iconAllowOverlap(true),
PropertyFactory.iconOffset(new Float[]{0f, -9f}))
), new Style.OnStyleLoaded() {...});
mapboxMap.addOnMapClickListener(this);
```
---
- 考え方としてはMapbox StudioでSymbolLayerを登録する流れに近い
1. アイコンを登録
2. ソースを登録
3. SymbolLayerを登録して、その属性にiconImageをつける
- 元のアイコン画像が無いので自分で用意する必要がある
---
```java=
@Override
public boolean onMapClick(@NonNull LatLng point) {
PointF screenPoint = mapboxMap.getProjection()
.toScreenLocation(point);
List<Feature> features = mapboxMap
.queryRenderedFeatures(screenPoint, LAYER_ID);
if (!features.isEmpty()) {
// ここに具体的な処理を書く
return true;
}
return false;
}
```
---
処理の中身の例
```java=
Feature f = features.get(0);
AlertDialog.Builder builder = new
AlertDialog.Builder(MainActivity.this);
builder
.setMessage(f.getStringProperty(DESCRIPTION_PROP))
.setTitle(f.getStringProperty(TITLE_PROP));
AlertDialog dialog = builder.create();
dialog.show();
```
---
![imgur](https://i.imgur.com/9rLpC6Bl.png)
---
![imgur](https://i.imgur.com/Yp5wIXrl.png)
---
ではiOSではこの書き方どうするの?
---
```swift
func mapView(_ mapView: MGLMapView,
didFinishLoading style: MGLStyle) {
let markerImage =
#imageLiteral(resourceName: "marker")
mapView.style?.setImage(markerImage,
forName: "marker-icon")
let coordinate =
CLLocationCoordinate2D(latitude: 34.68505,
longitude: 135.19890)
```
---
```swift
let hello = MGLPointFeature()
hello.coordinate = coordinate
hello.attributes = [
"name": "KITTO",
"description":
"Welcome to FOSS4G 2019 Kansai/Kobe"
]
let source = MGLShapeSource(
identifier: "kobe_source",
features: [hello], options: nil)
mapView.style?.addSource(source)
```
---
```swift
let symbols = MGLSymbolStyleLayer(
identifier: "kobe_layer", source: source)
symbols.iconImageName = NSExpression(
forConstantValue: "marker-icon")
symbols.iconAllowsOverlap = NSExpression(
forConstantValue: true)
symbols.iconOffset = NSExpression(
forConstantValue: CGVector(dx: 0, dy: -9))
mapView.style?.addLayer(symbols)
}
```
---
```swift
if let item = v as? MGLMapView {
item.delegate = self
mapView = item
let singleTap = UITapGestureRecognizer(
target: self,
action: #selector(handleMapTap(sender:)))
for recognizer in mapView.gestureRecognizers!
where recognizer is UITapGestureRecognizer {
singleTap.require(toFail: recognizer)
}
mapView.addGestureRecognizer(singleTap)
}
```
---
```swift
@objc @IBAction func handleMapTap(
sender: UITapGestureRecognizer) {
if sender.state == .ended {
let point = sender.location(in: sender.view!)
let layerIdentifiers: Set = ["kobe_layer"]
```
---
```swift
for feature in mapView.visibleFeatures(
at: point,
styleLayerIdentifiers: layerIdentifiers)
where feature is MGLPointFeature {
guard let selectedFeature =
feature as? MGLPointFeature else {
fatalError("...")
}
showCallout(feature: selectedFeature)
return
}
}
}
```
---
```swift
func showCallout(feature: MGLPointFeature) {
let dialog: UIAlertController =
UIAlertController(
title: feature.attributes["name"] as? String,
message:
feature.attributes["description"] as? String,
preferredStyle: UIAlertController.Style.alert)
let action = UIAlertAction(title: "閉じる",
style: UIAlertAction.Style.default,
handler: nil)
dialog.addAction(action)
self.present(dialog, animated: true, completion: nil)
}
```
---
![imgur](https://i.imgur.com/1Vfa35Vl.png)
---
![imgur](https://i.imgur.com/jxOY4gpl.png)
---
# まとめ
- AndroidのMapbox Maps SDK 8.xからmarkerを追加するものが使えなくなった
- Layerを使おうという方向性
- iOSはまだ存在するが、Layerを使う方法もある
- 今後はLayerを意識したプログラミングが必要
- ReactNativeでも推奨されている
---
# おわり
- [GitHub](https://github.com/smellman)
- [Twitter](https://twitter.com/smellman)
{"metaMigratedAt":"2023-06-15T00:39:07.667Z","metaMigratedFrom":"YAML","title":"Mapbox Maps SDKでスマートフォンアプリ開発","breaks":true,"description":"View the slide with \"Slide Mode\".","contributors":"[{\"id\":\"370340a2-02b1-4368-b231-d79cd9de7109\",\"add\":13699,\"del\":4447}]"}