Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[FOSS4G 2023 Japan] スマホアプリの地図表示における第三の選択肢 MapLibre Native を使ってみる

Haruki Inoue
September 18, 2023

[FOSS4G 2023 Japan] スマホアプリの地図表示における第三の選択肢 MapLibre Native を使ってみる

MapLibreにおいても、スマホアプリのSDKが提供されています。
この発表では、オープンソースのスマホアプリ用地図ライブラリ MapLibre Native で、GeoJSON のジオメトリを表示するサンプルを紹介します。

サンプルアプリのソースコード
https://github.com/k15015kk/TXMapSample

FOSS4G 2023 Japan コアデイ
https://www.osgeo.jp/events/foss4g-2023/foss4g-2023-japan-at-fukui/foss4g-coreday

Haruki Inoue

September 18, 2023
Tweet

More Decks by Haruki Inoue

Other Decks in Programming

Transcript

  1. .BQ-JCSF/BUJWFͱ͸ʁ Open-source SDK for Android and iOS allowing displaying maps

    inside of your mobile applications, desktop application, or embedded devices. "OESPJEͱJ04ͷΦʔϓϯιʔεͷ4%,Ͱ͋Γɺ ϞόΠϧΞϓϦɺσεΫτοϓΞϓϦɺ૊ΈࠐΈσόΠεͰ ஍ਤ͕දࣔͰ͖ΔΑ͏ʹͳΔ .BQ-JCSF/BUJWFެࣜ8FCαΠτΑΓҾ༻IUUQTNBQMJCSFPSHQSPKFDUTNBQMJCSFOBUJWF (16ʹΑΔϕΫτϧλΠϧͷߴ଎ϨϯμϦϯάΛ࣮ݱ͍ͯ͠Δ
  2. ϑΥʔΫݩ͸.BQCPY(-/BUJWF 044ͩͬͨ.BQCPY(-/BUJWFΛϑΥʔΫ͍ͯ͠Δ ɹMapbox GL Native (OSS) ɹMapbox GL Native (non-OSS)

    2020/12 ɹMapbox GL Native (OSS) Fork! ࢀߟࢿྉ.BQ-JCSF/BUJWFͷ(JU)VC'03,NE IUUQTHJUIVCDPNNBQMJCSFNBQMJCSFOBUJWFCMPCNBJO'03,NE
  3. εϚϗΞϓϦͷ஍ਤදࣔͷൺֱ (PPHMF.BQT4%, w "OESPJEͷඪ४Ͱ͋ΓJ04Ͱ΋࢖༻Մೳ w ஍ਤΛදࣔ͢Δ͚ͩͳΒ"1*,FZΛઃఆ͢Δ͚ͩͰ0, w ͍ΖΜͳػೳΛ࢖͓͏ͱ͢Δͱ͓͕͔͔ۚΔʢैྔ՝ۚੑʣ .BQ,JU 

    "QQMF.BQT w J04ͷΈར༻Մೳ w 7JFXΛઃఆ͢Δ͚ͩͰ஍ਤ͕දࣔ͞ΕΔ w ແྉͰར༻͢Δ͜ͱ͕Ͱ͖Δ .BQ-JCSF/BUJWF w J04"OESPJEͰར༻Մೳ w ػೳ͸গ͠গͳΊʢJ04͸.BQ,JUͱซ༻Ͱར༻͢Δͱ͍͍͔΋ʣ w ແྉͰར༻͢Δ͜ͱ͕Ͱ͖Δ
  4. ஍ਤදࣔϥΠϒϥϦͷൺֱʢओ؍ʣ GoogleMaps MapKit (Apple) MapLibre Native ಋೖ͠΍͢͞ ˚ ˕ ̋

    ػೳੑ ˕ ̋ ̋ 4XJGU6*ͱͷ૬ੑ ˚ ̋ ˚ υΩϡϝϯτͷॆ࣮͞ ˕ ̋ ˚ J04ͷΈ J04"OESPJE J04"OESPJE
  5. 4XJGU6*Ͱ.BQ7JFXΛ࡞੒ makeUIViewʹ஍ਤදࣔͷॳظઃఆΛͭͭ͠MGLMapViewΛฦ͢ MapView.swift func makeUIView(context: Context) -> some UIView {

    // 地図のスタイルを定義 let styleURL = URL(string: "https://api.maptiler.com/maps/jp-mierune-gray/style.json?key=\(mapTilerAPIKey)") // MGLMapViewを定義 let mapView = MGLMapView(frame: .zero, styleURL: styleURL) // AutoLayoutの設定を定義 mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight] // Mapboxのロゴを消す mapView.logoView.isHidden = true // TXの路線の重⼼を地図の中⼼に設定 mapView.setCenter( CLLocationCoordinate2D( latitude: 35.894930906699322, longitude: 139.937432307518321 ), zoomLevel: 9.2, animated: false ) // デリゲート(処理の委任先)定義 mapView.delegate = context.coordinator return mapView } .BQ5JMFSͷ஍ਤΛදࣔ "1*,FZ͸ผϑΝΠϧͰఆٛ HJUJHOPSFͰӅ͍ͨͨ͠Ί
  6. 4XJGU6*Ͱ.BQ7JFXΛ࡞੒ CoordinatorΛఆٛͯ͠஍ਤૢ࡞ʹؔ͢ΔॲཧΛهࡌ MapView.swift func makeCoordinator() -> MainMapView.Coordinator { Coordinator(self) }

    class Coordinator: NSObject, MGLMapViewDelegate { var control: MapView init(_ control: MapView) { self.control = control } // マップが表⽰されたあとの処理 func mapViewDidFinishLoadingMap(_ mapView: MGLMapView) { } } (class Coordinatorは内部クラス)
  7. 4XJGU6*Ͱ.BQ7JFXΛ࡞੒ 4XJGU6*ͷ෦෼Λ࣮૷ɻ࠷ॳʹදࣔ͞ΕΔ7JFXΛ.BJO7JFXʹઃఆɻ MainVie import SwiftUI struct MainView: View { var

    body: some View { ZStack(alignment: .topLeading) { MapView() } .edgesIgnoringSafeArea(.all) } } struct MainView_Previews: PreviewProvider { static var previews: some View { MainView() } } MainView.swift
  8. (FP+40/ΛಡΈࠐΉؔ਺Λ࡞੒ MapView.swift func loadGeoJSONData(resourceName: String) async -> Data { guard

    let jsonURL = Bundle.main.url( forResource: resourceName, withExtension: "geojson" ) else { preconditionFailure("GeoJSONファイルの読み込みに失敗しました") } guard let jsonData = try? Data(contentsOf: jsonURL) else { preconditionFailure("GeoJSONファイルのパースに失敗しました") } return jsonData } Ҿ਺ͰϑΝΠϧ໊Λड͚औΔΑ͏ʹͯ͠൚༻తͳؔ਺ʹ͠·͢
  9. ઢܗΛඳը͢Δؔ਺Λ࡞੒ MapView.swift func drawRailway(_ mapView: MGLMapView, geoJson: Data) { //

    ここに描画に必要な定義をします } guard let style = mapView.style else { return } // GeoJSONデータからShapeを⽣成 guard let shapeFromGeoJson = try? MGLShape(data: geoJson, encoding: String.Encoding.utf8.rawValue) else { fatalError("MGLShapeの⽣成ができませんでした") } func drawRailway .BQ-JCSFͷ7JFX (FP+40/ͷ%BUB
  10. ઢܗΛඳը͢Δؔ਺Λ࡞੒ // 表⽰ソースを定義 let shapeSource = MGLShapeSource(identifier: "railway-source", shape: shapeFromGeoJson,

    options: nil) style.addSource(shapeSource) // レイヤーを定義 let lineLayer = MGLLineStyleLayer(identifier: "railway-line-style", source: shapeSource) // 始点・終点の形 lineLayer.lineJoin = NSExpression(forConstantValue: "round") lineLayer.lineCap = NSExpression(forConstantValue: "round") // 線の⾊ lineLayer.lineColor = NSExpression(forConstantValue: UIColor.orange) // 線の幅 // ズームレベルに応じて幅を変えたい場合 mgl_interpolate:withCurveType:parameters:stops: を使って定義 lineLayer.lineWidth = NSExpression( format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", [10: 2.0, 18: 8.0] ) style.addLayer(lineLayer) func drawRailway /4&YQSFTTJPOͰఆٛ͢Δ͜ͱ͕ଟ͍ ഑ྻ΍ϩʔΧϧ%#ͰܭࢉࣜΛฦؔ͢਺ ೖྗͱग़ྗͷؒΛิؒ͢ΔϑΥʔϚοτ ζʔϜϨϕϧ͕dͷͱ͖ɺ෯ΛdͷؒͰิؒ͢Δ ઢܗͷඳը͸.(--JOF4UZMF-BZFSΛར༻
  11. ࣮ࡍʹඳըؔ਺Λݺͼग़͢ MapView.swift class Coordinator: NSObject, MGLMapViewDelegate { // 省略 func

    mapViewDidFinishLoadingMap(_ mapView: MGLMapView) { self.control.drawRailwayAndStation(mapView) } } func drawRailwayAndStation(_ mapView: MGLMapView) { Task { let railwayData = await loadGeoJSONData(resourceName: "TX_Railway") await MainActor.run { drawRailway(mapView, geoJson: railwayData) } } } ௥ه
  12. Ӻͷ఺ͱӺ໊Λඳը͢Δؔ਺Λ࡞੒ func drawStation(_ mapView: MGLMapView, geoJson: Data) { // GeoJSONをshapeデータに変換(省略)

    // MGLShapeSourceを定義してstyleにSourceを追加(省略) // 駅の場所をCircleStyleに表⽰ let circleLayer = MGLCircleStyleLayer(identifier: "station-circle-style", source: shapeSoruce) circleLayer.circleColor = NSExpression(forConstantValue: UIColor.orange) circleLayer.circleRadius = NSExpression( format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", [10: 4.0, 18: 16.0] ) style.addLayer(circleLayer) // 駅名をSymbolStyleLayerで表⽰する let shapeLayer = MGLSymbolStyleLayer(identifier: "station-symbol-style", source: shapeSoruce) shapeLayer.text = NSExpression(forKeyPath: "N05_011") shapeLayer.textColor = NSExpression(forConstantValue: UIColor.white) shapeLayer.textTranslation = NSExpression(forConstantValue: NSValue(cgVector: CGVector(dx: -4, dy: -4))) shapeLayer.textFontNames = NSExpression(forConstantValue: ["HiraginoSans-W6"]) shapeLayer.textFontSize = NSExpression(forConstantValue: 12.0) shapeLayer.textIgnoresPlacement = NSExpression(forConstantValue: true) shapeLayer.textJustification = NSExpression(forConstantValue: "right") shapeLayer.textAnchor = NSExpression(forConstantValue: "bottom-right") shapeLayer.textHaloColor = NSExpression(forConstantValue: UIColor.black) shapeLayer.textHaloWidth = NSExpression(forConstantValue: 1.0) style.addLayer(shapeLayer) } MapView.swift ςΩετʹԑऔΓΛ͚ͭΔ ԁͷඳը͸.(-$JSDMF4UZMF-BZFSΛར༻ ςΩετͷඳը͸.(-4ZNCPM4UZMF-BZFSΛར༻
  13. ࣮ࡍʹඳըؔ਺Λݺͼग़͢ MapView.swift class Coordinator: NSObject, MGLMapViewDelegate { // 省略 func

    mapViewDidFinishLoadingMap(_ mapView: MGLMapView) { self.control.drawRailwayAndStation(mapView) } } func drawRailwayAndStation(_ mapView: MGLMapView) { Task { let railwayData = await loadGeoJSONData(resourceName: "TX_Railway") let stationData = await loadGeoJSONData(resourceName: "TX_Station") await MainActor.run { drawRailway(mapView, geoJson: railwayData) drawStation(mapView, geoJson: stationData) } } } ௥ه ௥ه
  14. ۠ࢢொଜϙϦΰϯΛඳը͢Δؔ਺Λ࡞੒ func drawMunicipality(_ mapView: MGLMapView, geoJson: Data) { // GeoJSONをshapeデータに変換(省略)

    // MGLShapeSourceを定義してstyleにSourceを追加(省略) // 市町村ポリゴンのスタイルを定義 let fillStyleLayer = MGLFillStyleLayer(identifier: "municipality-fill-style", source: shapeSource) fillStyleLayer.fillColor = NSExpression( format: "MGL_MATCH(N03_001, '東京都', %@, '埼⽟県', %@, '千葉県', %@, '茨城県', %@, %@)", UIColor.red, UIColor.yellow, UIColor.green, UIColor.blue, UIColor.black ) fillStyleLayer.fillOpacity = NSExpression(forConstantValue: 0.2) style.addLayer(fillStyleLayer) // ポリゴンの輪郭線スタイルを定義 let lineLayer = MGLLineStyleLayer(identifier: "municipality-line-style", source: shapeSource) lineLayer.lineWidth = NSExpression(forConstantValue: 1.0) lineLayer.lineColor = NSExpression(forConstantValue: UIColor.black) style.addLayer(lineLayer) } MapView.swift (FP+40/QSPQFSUJFTͷ஋ʹΑͬͯ৭Λ෼͚͍ͨ৔߹͸ .(-@."5$)Λར༻ͯ͠෼͚Δ Ұ൪࠷ޙͷ!͸σϑΥϧτ஋Λఆٛ͢Δ ԁͷඳը͸.(-'JMM4UZMF-BZFSΛར༻ ϙϦΰϯͷྠֲ͸.(--JOF4UZMF-BZFSΛར༻ ݝʹΑͬͯ৭Λมߋ
  15. ࣮ࡍʹඳըؔ਺Λݺͼग़͢ MapView.swift class Coordinator: NSObject, MGLMapViewDelegate { // 省略 func

    mapViewDidFinishLoadingMap(_ mapView: MGLMapView) { self.control.drawRailwayAndStation(mapView) } } func drawRailwayAndStation(_ mapView: MGLMapView) { Task { let municipalityData = await loadGeoJSONData(resourceName: "TX_Municipality") let railwayData = await loadGeoJSONData(resourceName: "TX_Railway") let stationData = await loadGeoJSONData(resourceName: "TX_Station") await MainActor.run { drawMunicipality(mapView, geoJson: municipalityData) drawRailway(mapView, geoJson: railwayData) drawStation(mapView, geoJson: stationData) } } } ௥ه ௥ه ඳըॱʹ஫ҙ