5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

マンホールカードをマップ上にまとめて経路検索したい!

Posted at

きっかけ

現状にマンホールカードの配布場所へ経路案内を直接行うアプリケーションは無い!
配布場所へ行くにはGKP公式のホームページから住所をコピーするのが主流。
それでは工程が増えてしまい、非効率である。
なので、配布場所が一目で分かりつつ経路案内も行うマップAppを作成する。

マンホールカードとは

下水道広報プラットフォーム(GKP)が各地方自治体と協力して制作しているマンホールのコレクションカードである。1151種類のカードが制作されており、737自治体・3団体が協力している。(2025年8月時点)

開発

では実際に作ってみる!!!
・・・とは言ってみたもののswiftは勉強したこともない人間のため生成AIを利用しながら進めていきたいと思います。

  • 実験環境
    • MacBook Air 13in
    • M4 チップ
  • 対応バージョン
    • MacOS Sequoia version15.4
    • iOS 18.4
    • XcodeVersion 16.3

簡単な地図アプリ作成

この章は、「SwiftUI対応 たった2日でマスターできる iPhoneアプリ開発集中講座 Xcode16/iOS18/Swift6.0対応」を参考にしています。全てとは言いませんがほとんどはこちらから引用しています。

  1. Swfitでマップを表示するにはMapKitをインポートしなければならない。
MapView.swift
import  MapKit

2. マップを表示

MapView.swift
import MapKit

struct MapView: View {
    var body: some View{
        // Text("Hello, World!") 削除
        Map() // マップを表示
    }
}

実際に作成したアプリ

ContentView.swiftとMap.swiftの2つを作った。

基本構造

ContentView.swift
struct ContentView: View {
    @StateObject private var viewModel = LocationViewModel()
    @State private var position: MapCameraPosition = .automatic
    @State private var showLocationList = false
    
    var body: some View {
        // ビューの内容
    }
}

  • ContentViewはSwiftUIのViewプロトコルに準拠した構造体。アプリ画面を定義。
  • @StateObjectはデータを保持するviewModelオブジェクトを作成。画面が再構築されたとしてもデータが保持されるようになる。
  • @Stateは画面の状態を保持する変数を定義。値が変更されると自動的に画面が更新される。
  • positionは地図の表示位置や尺度を管理。
  • showLocationListは一覧表示画面の表示/非表示を切り替えるフラグ。

地図の表示

ContenView.swift
Map(position: $position) {
    // マンホールカードの位置をマーカーで表示
    ForEach(viewModel.locations) { location in
        if viewModel.selectedLocation?.id == location.id {
            Marker(location.title, coordinate: location.coordinate)
                .tint(.red) // 選択中は赤色
        } else {
            Marker(location.title, coordinate: location.coordinate)
                .tint(location.region.color)
        }
    }
    
    // 選択された場所の詳細表示
    // ...
    
    // 経路の表示
    // ...
    
    // ユーザーの現在地表示
    UserAnnotation()
}
Map.swift
struct Location: Identifiable {
    let id = UUID()
    let title: String
    let coordinate: CLLocationCoordinate2D
    let prefecture: String
    let region: Region
}
  • Mapは上で紹介しているように地図を表示するのに必要な構造体です。
  • ForEachは配列の各要素に対して繰り返し処理を行う。配列にはマンホールカードの場所が入っており、場所毎にマーカーを配置。
  • Markerは地図上にピンを立てる構造体。施設名と座標を指定している。
  • tintはマーカーの色を設定する。選択中は赤色、それ以外は地域毎の色に分けられる。
  • UserAnnotationはユーザの現在地を地図上に表示させる。
  • Locationはマンホールカードの設置場所を表す構造体。上から順に、
    • id:各場所を識別するためのID
    • title:場所の名前
    • coordinate:緯度と経度の座標
    • prefecture:都道府県
    • region:地域区分

地図のコントロール

ContentView.swift
.mapControls {
    MapUserLocationButton()
        .padding(.trailing)
        .buttonBorderShape(.circle)
    
    MapCompass()
        .padding(.trailing, 40)
    
    MapScaleView()
}
  • .mapControlsは地図上のコントロール(ボタンなど)を配置。
  • MapUserLocationButtonは現在地に移動するボタンを表示。
  • MapCompassは方位を示すコンパスを表示。
  • MapScaleViewは地図の縮尺を示すスケールを表示。

経路情報の表示

ContentView.swift
if let route = viewModel.route {
    VStack(alignment: .leading) {
        Text("\(viewModel.selectedLocation?.title ?? "目的地") までの経路")
            .font(.headline)
        
        Text("距離: \(formatDistance(route.distance))")
        Text("所要時間: \(formatTime(route.expectedTravelTime))")
        
        // 交通手段の表示と切り替え
        // ...
    }
    // ...
}
Map.swift
func calculateRoute() {
    // 前回の検索をキャンセル
    cancelAllDirectionsRequests()
    
    // 位置情報と目的地の確認
    guard let userLocation = userLocation, let selectedLocation = selectedLocation else {
        // ...
        return
    }
    
    // 経路計算を実行
    // ...
}

選択された場所までの経路情報(距離・所要時間)を表示する。また、if文でrouteに値が存在する場合に縦方向に経路情報をビューに配置する。
また、車・徒歩・公共交通機関で経路を検索するようにします。各交通手段の所要時間を比較して最速の経路を選択する。

交通手段の切り替え

ContentView.swift
HStack {
    Text("交通手段: \(viewModel.transportTypeName(viewModel.selectedTransportType))")
    
    Spacer()
    
    // 他の交通手段がある場合、切り替えボタンを表示
    if viewModel.availableRoutes.count > 1 {
        Menu {
            // 交通手段のメニュー項目
            // ...
        } label: {
            Image(systemName: "arrow.triangle.swap")
                .foregroundColor(.blue)
        }
    }
}

現在選択されている交通手段を表示し、他の交通手段に切り替えるためのボタンを配置。Menuがドロップダウンメニューを扱う際の構造体。

マンホールカードの一覧表示

ContentView.swift
struct LocationListView: View {
    @ObservedObject var viewModel: LocationViewModel
    @Binding var isPresented: Bool
    @State private var searchText = ""
    
    var filteredLocations: [Location] {
        if searchText.isEmpty {
            return viewModel.locations
        } else {
            return viewModel.locations.filter { $0.title.contains(searchText) }
        }
    }
    
    var body: some View {
        NavigationView {
            List(filteredLocations) { location in
                // 各マンホールカードの情報を表示するボタン
                // ...
            }
            .navigationTitle("マンホールカード一覧")
            .searchable(text: $searchText, prompt: "検索")
            // ...
        }
    }
}
  • LocationListViewはマンホールカード一覧を表示す画面の構造体。
  • @ObservedObjectLocationViewModelから渡されたviewModelの変更を確認する。
  • filteredLocations:検索テキストに基づいてフィルタリングされた場所の配列を計算する。
  • NavigationViewはナビゲーションバーを含むビューを作成。
  • Listスクロール可能なリストを作成。
  • .searchableは検索機能を追加する。

完成系

ManholeCards_Screenshot.png

Lists_Screenshot.png

参考文献

この記事は以下の情報を参考にして執筆しました。

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?