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Λ࢖ͬͯΈΔ
    FOSS4G Japan 2023
    )BSVLJ*OPVF

    View Slide

  2. ࣗݾ঺հ
    Ҫ্੖ك)BSVLJ*OPVF
    5XJUUFS!LZPUPOBHPZB
    ৬ۀJ04ΞϓϦΤϯδχΞ
    Ґஔ৘ใΛ࢖ͬͨ#UP#ΞϓϦΛ࡞ͬͯ·͢
    ग़਎஍Ѫ஌ݝ๛ڮࢢ
    ډॅ஍ઍ༿ݝྲྀࢁࢢ
    झຯిंɾόεʹ৐Δ஍ਤΛݟΔ
    εΩϧJ044XJGU3FBDU1ZUIPO

    View Slide

  3. ΞδΣϯμ
    w.BQ-JCSF/BUJWFͱ͸ʁ
    w.BQ-JCSF/BUJWFGPSJ04Ͱ(FP+40/Λදࣔ͢Δ
    w-JOF4USJOH
    w1PJOU
    w1PMZHPO
    ࣌ؒͷ౎߹ͰɺͰ͖Δͱ͜Ζ·Ͱ঺հ͠·͢ɻ
    ࢒ͬͨ෦෼͸ޙ೔εϥΠυެ։ʹͯɻ

    View Slide

  4. ͸͡Ίʹ
    ಥવͰ͕͢
    .BQ-JCSF/BUJWF͸͝ଘ஌Ͱ͔͢ʁ

    View Slide

  5. .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ʹΑΔϕΫτϧλΠϧͷߴ଎ϨϯμϦϯάΛ࣮ݱ͍ͯ͠Δ

    View Slide

  6. .BQ-JCSFͷαϯϓϧը૾
    iOS Android
    .BQ-JCSF/BUJWF(JU)VCͷ3&"%.&NEΑΓҾ༻
    IUUQTHJUIVCDPNNBQMJCSFNBQMJCSFOBUJWFCMPCNBJO3&"%.&NE

    View Slide

  7. ϑΥʔΫݩ͸.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

    View Slide

  8. εϚϗΞϓϦͷ஍ਤදࣔͷൺֱ
    (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 ແྉͰར༻͢Δ͜ͱ͕Ͱ͖Δ

    View Slide

  9. ஍ਤදࣔϥΠϒϥϦͷൺֱʢओ؍ʣ
    GoogleMaps MapKit (Apple) MapLibre Native
    ಋೖ͠΍͢͞ ˚ ˕ ̋
    ػೳੑ ˕ ̋ ̋
    4XJGU6*ͱͷ૬ੑ ˚ ̋ ˚
    υΩϡϝϯτͷॆ࣮͞ ˕ ̋ ˚
    J04ͷΈ
    J04"OESPJE J04"OESPJE

    View Slide

  10. .BQ-JCSF/BUJWFͷಋೖํ๏
    .BQ5JMFS͞Μ͕ಋೖํ๏ͷυΩϡϝϯτΛ·ͱΊ͍ͯ·͢
    J04 "OESPJE
    J04ΞϓϦʹ͓͚Δಋೖํ๏ʹ͍ͭͯ͸2JJUBهࣄΛॻ͍͍ͯ·͢

    View Slide

  11. ΞδΣϯμ
    w.BQ-JCSF/BUJWFͱ͸ʁ
    w.BQ-JCSF/BUJWFGPSJ04Ͱ஍ਤΛදࣔ͢Δ
    w.BQ-JCSF/BUJWFGPSJ04Ͱ(FP+40/Λදࣔ͢Δ
    w-JOF4USJOH
    w1PJOU
    w1PMZHPO

    View Slide

  12. αϯϓϧΞϓϦͷσΟϨΫτϦߏ੒
    .BQ-JCSFͷ7JFXΛఆٛ
    4XJGU6*ͷ7JFX
    .BQ5JMFSͷ"1*,FZ
    ඳըʹར༻͢Δ(FP+40/ϑΝΠϧ
    ݱঢ়.PEFMʹ͸Կ΋ೖͬͯ·ͤΜʜ
    "QQϑΝΠϧ
    .BJO7JFXΛ࠷ॳʹදࣔ͢ΔΑ͏ʹఆٛ

    View Slide

  13. .BQ-JCSF/BUJWFGPSJ04ͷಋೖํ๏
    4XJGU1BDLBHF.BOBHFS͔ΒύοέʔδΛμ΢ϯϩʔυ
    ʮNBQMJCSFHMOBUJWFʯͱೖྗ
    ʮ"EE1BDLBHFʯΛΫϦοΫ

    View Slide

  14. .BQ-JCSF/BUJWFGPSJ04ͷಋೖํ๏
    ʮ"EE1BDLBHFʯΛΫϦοΫ
    νΣοΫϘοΫεΛೖΕͯ
    4XJGU1BDLBHF.BOBHFS͔ΒύοέʔδΛμ΢ϯϩʔυ

    View Slide

  15. 4XJGU6*Ͱ.BQ7JFXΛ࡞੒
    4XJGU6*ͷίϯϙʔωϯτ͸ແ͍ͷͰUIViewRepresentableͰ࣮૷
    4XJGU6*Ͱ6*,JUΛ࢖͑ΔΑ͏ʹ͢Δ1SPUPDPM

    MapView.swift
    import Mapbox


    import MapKit


    import SwiftUI


    struct MapView: UIViewRepresentable {


    // ここに⾊々な関数を書いていきます


    }


    View Slide

  16. 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ͰӅ͍ͨͨ͠Ί

    View Slide

  17. 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は内部クラス)

    View Slide

  18. 4XJGU6*Ͱ.BQ7JFXΛ࡞੒
    UIViewRepresentableͷ౎߹ͰۭͷupdateUIViewΛఆٛ
    MapView.swift
    func updateUIView(_ uiView: UIViewType, context: Context) {




    }


    View Slide

  19. 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

    View Slide

  20. ࣮ߦ
    ΤϛϡϨʔλ΍࣮ػͰ
    Ϗϧυɾ࣮ߦΛ͢Δͱ
    ஍ਤ͕දࣔ͞ΕΔ
    ˜.*&36/&c˜.BQ5JMFS˜0QFO4USFFU.BQDPOUSJCVUPST

    View Slide

  21. ΞδΣϯμ
    w.BQ-JCSF/BUJWFͱ͸ʁ
    w.BQ-JCSF/BUJWFGPSJ04Ͱ(FP+40/Λදࣔ͢Δ
    w-JOF4USJOH
    w1PJOU
    w1PMZHPO

    View Slide

  22. (FP+40/Λ༻ҙ
    ͭ͘͹ΤΫεϓϨεͷઢܗσʔλΛ༻ҙ
    ࠃ౔਺஋৘ใͷ
    మಓ ࣌ܥྻσʔλ
    Λ
    Ճ޻ͨ͠(FP+40/
    9DPEFʹ
    (FP+40/ϑΟϧΛ
    ಡΈࠐΈ

    View Slide

  23. (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


    }


    Ҿ਺ͰϑΝΠϧ໊Λड͚औΔΑ͏ʹͯ͠൚༻తͳؔ਺ʹ͠·͢

    View Slide

  24. ઢܗΛඳը͢Δؔ਺Λ࡞੒
    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

    View Slide

  25. ઢܗΛඳը͢Δؔ਺Λ࡞੒
    // 表⽰ソースを定義


    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Λར༻

    View Slide

  26. ࣮ࡍʹඳըؔ਺Λݺͼग़͢
    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)


    }


    }


    }


    ௥ه

    View Slide

  27. ࣮ߦ
    ΤϛϡϨʔλ΍࣮ػͰ
    Ϗϧυɾ࣮ߦΛ͢Δͱ
    ΦϨϯδ৭ͷઢܗ͕දࣔ͞ΕΔ
    ˜.*&36/&c˜.BQ5JMFS˜0QFO4USFFU.BQDPOUSJCVUPST

    View Slide

  28. ΞδΣϯμ
    w.BQ-JCSF/BUJWFͱ͸ʁ
    w.BQ-JCSF/BUJWFGPSJ04Ͱ(FP+40/Λදࣔ͢Δ
    w-JOF4USJOH
    w1PJOU
    w1PMZHPO

    View Slide

  29. (FP+40/Λ༻ҙ
    ͭ͘͹ΤΫεϓϨεͷӺͷϙΠϯτσʔλΛ༻ҙ
    ࠃ౔਺஋৘ใͷ
    మಓ ࣌ܥྻσʔλ
    Λ
    Ճ޻ͨ͠(FP+40/

    View Slide

  30. Ӻͷ఺ͱӺ໊Λඳը͢Δؔ਺Λ࡞੒
    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Λར༻

    View Slide

  31. ࣮ࡍʹඳըؔ਺Λݺͼग़͢
    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)


    }




    }


    }


    ௥ه
    ௥ه

    View Slide

  32. ࣮ߦ
    ΤϛϡϨʔλ΍࣮ػͰ
    Ϗϧυɾ࣮ߦΛ͢Δͱ
    ӺͷԁͱӺ໊͕දࣔ͞ΕΔ
    ˜.*&36/&c˜.BQ5JMFS˜0QFO4USFFU.BQDPOUSJCVUPST

    View Slide

  33. ΞδΣϯμ
    w.BQ-JCSF/BUJWFͱ͸ʁ
    w.BQ-JCSF/BUJWFGPSJ04Ͱ(FP+40/Λදࣔ͢Δ
    w-JOF4USJOH
    w1PJOU
    w1PMZHPO

    View Slide

  34. (FP+40/Λ༻ҙ
    ͭ͘͹ΤΫεϓϨε͕௨͍ͬͯΔ۠ࢢொଜͷϙϦΰϯσʔλΛ༻ҙ
    ࠃ౔਺஋৘ใͷ
    ߦ੓۠ըΛ
    Ճ޻ͨ͠(FP+40/

    View Slide

  35. ۠ࢢொଜϙϦΰϯΛඳը͢Δؔ਺Λ࡞੒
    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Λར༻
    ݝʹΑͬͯ৭Λมߋ

    View Slide

  36. ࣮ࡍʹඳըؔ਺Λݺͼग़͢
    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)


    }




    }


    }


    ௥ه
    ௥ه ඳըॱʹ஫ҙ

    View Slide

  37. ࣮ߦ
    ΤϛϡϨʔλ΍࣮ػͰ
    Ϗϧυɾ࣮ߦΛ͢Δͱ
    ۠ࢢொଜͷϙϦΰϯ͕
    ݝผͰ৭෼͚͞Εͯදࣔ͞ΕΔ
    ˜.*&36/&c˜.BQ5JMFS˜0QFO4USFFU.BQDPOUSJCVUPST

    View Slide

  38. ͓ΘΓʹ
    ࠓճͷ(FP+40/දࣔαϯϓϧ͸(JU)VCʹͯެ։͍ͯ͠·͢
    IUUQTHJUIVCDPNLLL59.BQ4BNQMF
    ·ͨɺ͜ͷදࣔαϯϓϧΛجʹͯ͠ޙ೔2JJUBهࣄΛॻ͘༧ఆͰ͢
    هࣄͷެ։·Ͱ͠͹Β͓͘଴ͪԼ͍͞

    View Slide