Share
Embed
[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
Transcript
38 slides
-
.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ʹΑΔϕΫτϧλΠϧͷߴϨϯμϦϯάΛ࣮ݱ͍ͯ͠Δ -
ϑΥʔΫݩ.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 -
εϚϗΞϓϦͷਤදࣔͷൺֱ (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 ແྉͰར༻͢Δ͜ͱ͕Ͱ͖Δ -
ਤදࣔϥΠϒϥϦͷൺֱʢओ؍ʣ GoogleMaps MapKit (Apple) MapLibre Native ಋೖ͢͠͞ ˚ ˕ ̋
ػೳੑ ˕ ̋ ̋ 4XJGU6*ͱͷ૬ੑ ˚ ̋ ˚ υΩϡϝϯτͷॆ࣮͞ ˕ ̋ ˚ J04ͷΈ J04"OESPJE J04"OESPJE -
4XJGU6*Ͱ.BQ7JFXΛ࡞ 4XJGU6*ͷίϯϙʔωϯτແ͍ͷͰUIViewRepresentableͰ࣮ 4XJGU6*Ͱ6*,JUΛ͑ΔΑ͏ʹ͢Δ1SPUPDPM MapView.swift import Mapbox import MapKit import SwiftUI
struct MapView: UIViewRepresentable { // ここに⾊々な関数を書いていきます } -
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ͰӅ͍ͨͨ͠Ί -
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は内部クラス) -
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 -
(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 } ҾͰϑΝΠϧ໊Λड͚औΔΑ͏ʹͯ͠൚༻తͳؔʹ͠·͢ -
ઢܗΛඳը͢ΔؔΛ࡞ 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 -
ઢܗΛඳը͢ΔؔΛ࡞ // 表⽰ソースを定義 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Λར༻ -
࣮ࡍʹඳըؔΛݺͼग़͢ 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) } } } ه -
ӺͷͱӺ໊Λඳը͢ΔؔΛ࡞ 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Λར༻ -
࣮ࡍʹඳըؔΛݺͼग़͢ 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) } } } ه ه -
۠ࢢொଜϙϦΰϯΛඳը͢ΔؔΛ࡞ 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Λར༻ ݝʹΑͬͯ৭Λมߋ -
࣮ࡍʹඳըؔΛݺͼग़͢ 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) } } } ه ه ඳըॱʹҙ