# 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\"."}
    3313 views