AppleWatch GPSブイで潮流チェック♪

GPSブイの説明動画を作って見ました。 本来なら海で使うことを想定していますが、アップルさん本社付近でのテストになります

課題

  • 海の中で目印となる、GPSブイが欲しい

ニーズ

  • GPSを活用して、位置情報を記録できる機能が欲しい
  • 釣っている最中は釣り具以外何も持ちたくない

背景

釣行計画はスマートフォン

  • 釣りスポットの履歴をGPSで簡単に確認できることで、以前に釣った場所を特定しやすくなる。
  • 釣りスポットの情報を見ながら、新しい釣りスポットを開拓する楽しみが増す。
  • 潮の流れや季節の変化など環境情報をより正確に把握し、釣果向上につなげることができる。

現場まで航行は

GPS釣りスポット記録アプリを利用し目的地に行ける

船にGPS付き魚群探知機で目的地付近に行く

実釣

目印としてのGPSブイやマーキング装置を設定し、潮や風でどれだけ流れたか知りたい

ほしい機能

  • GPSブイの投入
    • ブイID、緯度経度記録
    • ○ブイIは3個
    • Xブイは3個
  • GPSブイからの距離計測
    • ○ブイから50m離れたら振動でお知ら
    • Xブイから50m以内に入ったら振動でお知らせ
  • ブイ使用履歴を作成
    • 記録は5個

最初の一歩 マップ表示  

import MapKit
struct ContentView: View {
      var body: some View {
         Map()
      }
}

   

場所指定

  • 緯度経度と表示エリアを設定
import SwiftUI
import MapKit
struct ContentView: View {
  var body: some View {
    //三重県南伊勢町五ケ所を指定
    @State  var region = MKCoordinateRegion(
      center : CLLocationCoordinate2D (
        latitude: 34.349// 緯度
        longitude: 136.697 // 経度
      ),
      latitudinalMeters: 500.0, // 南北の表示エリア(単位:メートル)
      longitudinalMeters: 500.0 // 東西の表示エリア(単位:メートル)
    )
    
    Map(coordinateRegion: $region)
  }
}

現在位置表示

  • 緯度経度取得 LocationManagerクラス作成
    • ググって、情報をまとめました。みなさんありがとうございます
import WatchKit
import MapKit

class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
    // CLLocationManagerをインスタンス化
    let manager = CLLocationManager()
    
    // 更新のたびに変化するので@Publishedを付与して観測
    @Published  var region =  MKCoordinateRegion()
    
    override init() {
        super.init()     // スーパクラスイニシャライズ
        manager.delegate = self     //自身をデリゲートプロパティに
        manager.requestWhenInUseAuthorization()  // 位置情報を利用許可を要求
        manager.desiredAccuracy = kCLLocationAccuracyBest   // 最高精度の位置情報を要求
        manager.distanceFilter = 3.0     // 更新距離(m)
        manager.startUpdatingLocation()  //現在位置アップデート生成開始
    }
    
    // 領域の更新をするデリゲートメソッド
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        
        // 配列の最後に最新のロケーションが格納される
        // map関数を使って全要素にアクセス map{ $0←要素に参照 }
        locations.last.map {
            let center = CLLocationCoordinate2D(
                latitude: $0.coordinate.latitude,
                longitude: $0.coordinate.longitude)
            
            // 地図を表示するための領域を再構築
            region = MKCoordinateRegion(
                center: center,
                latitudinalMeters: 100.0,
                longitudinalMeters: 100.0
            )
        }
    }
}
  • View改良
    • ググって、情報をまとめました。みなさんありがとうございます
import SwiftUI
import MapKit

struct ContentView: View {
    @ObservedObject  var manager = LocationManager()
    //ユーザートラッキングモードを追従モード変数 .follow ユーザーを追跡  .none ユーザーの追跡を停止
    @State  var trackingMode = MapUserTrackingMode.follow
    
 
 struct ContentView: View {
    @ObservedObject  var manager = LocationManager()
    @State  var trackingMode = MapUserTrackingMode.follow
    
    var body: some View {
        ZStack{
            Map(coordinateRegion: $manager.region,  //状態変数をバインディング指定
                showsUserLocation: true, // マップ上にユーザーの場所を表示するオプションをBool値で指定
                userTrackingMode: $trackingMode
            )
            .edgesIgnoringSafeArea(.bottom)
            .edgesIgnoringSafeArea(.top)
        }
    }
}

GPSブイ設置

  • 緯度経度取得 LocationManagerクラスに、記録機能追加
    func reloadRegion (bouyNo: Int){
        // オプショナルバインディング
        if let location = manager.location {
            
            let center = CLLocationCoordinate2D(
                latitude: location.coordinate.latitude,
                longitude: location.coordinate.longitude
            )

            if bouyNo == 0 || bouyNo == 1 || bouyNo == 2 {
                //GPSブイ投下を記録
                pointList[bouyNo] = Point(
                    name: "No.\(bouyNo + 1)",
                    latitude: location.coordinate.latitude ,
                    longitude: location.coordinate.longitude
                )
                region = MKCoordinateRegion(
                    center: center,
                    latitudinalMeters: 50.0,
                    longitudinalMeters: 50.0
                )
            } else {
                //ロケーションボタン
                region = MKCoordinateRegion(
                    center: center,
                    latitudinalMeters: 200.0,
                    longitudinalMeters: 200.0
                )
            }
        }
    }
  • View改良し
    • ブイ投下ボタン追加
    • ブイ番号表示
struct Point: Identifiable {
    let id = UUID()         //ユニークID
    let name: String
    let latitude: Double    // 緯度
    let longitude: Double   // 経度
    // 座標
    var coordinate: CLLocationCoordinate2D {
        CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
    }
}

var pointList = [
    Point(name: "1", latitude: 35.709152712026265, longitude: 139.80771829999996),
    Point(name: "2", latitude: 35.711554715026265, longitude: 139.81371829999996),
    Point(name: "3", latitude: 35.712527719026265, longitude: 139.81071829999996)
]

struct ContentView: View {
    @ObservedObject  var manager = LocationManager()
    
    //ユーザートラッキングモードを追従モード変数 .follow ユーザーを追跡  .none ユーザーの追跡を停止
    @State  var trackingMode = MapUserTrackingMode.follow
    
    var body: some View {
        ZStack{
            Map(coordinateRegion: $manager.region,  //状態変数をバインディング指定
                showsUserLocation: true, //ユーザーの場所を表示するオプションをBool値で指定
                userTrackingMode: $trackingMode,
                annotationItems: pointList,
                annotationContent: { (pointList) in
                    MapAnnotation(coordinate: pointList.coordinate) {
                        VStack {
                            Image(systemName: "mappin")
                                .foregroundColor(.orange)
                                .font(.system(size: 20))
                            Text(pointList.name)
                                .foregroundColor(.orange)
                                .font(.system(size: 12))
                        }
                    }
                }
            )
            .edgesIgnoringSafeArea(.all)
            .edgesIgnoringSafeArea(.bottom)
            .edgesIgnoringSafeArea(.top)
            VStack{
                Spacer()
                HStack{
                     Button(action: {
                        manager.reloadRegion(bouyNo: 0)
                    }) {
                        Image(systemName: "mappin.circle.fill")
                            .foregroundColor(.white)
                            .font(.system(size: 12))
                        Text("1")
                            .foregroundColor(.white)
                            .font(.system(size: 12))
                    }
                    .frame(width: 41, height: 25)
                    .cornerRadius(30.0)
                    
                    Button(action: {
                        manager.reloadRegion(bouyNo: 1)
                    }) {
                        Image(systemName: "mappin.circle.fill")
                            .foregroundColor(.white)
                            .font(.system(size: 12))
                        Text("2")
                            .foregroundColor(.white)
                            .font(.system(size: 12))
                    }
                    .frame(width: 41, height: 25)
                    .cornerRadius(30.0)
                    
                    Button(action: {
                        manager.reloadRegion(bouyNo: 2)
                    }) {
                        Image(systemName: "mappin.circle.fill")
                            .foregroundColor(.white)
                            .font(.system(size: 12))
                        Text("3")
                            .foregroundColor(.white)
                            .font(.system(size: 12))
                    }
                    .frame(width: 41, height: 25)
                    .cornerRadius(30.0)
                    
                    
                    Button(action: {
                        manager.reloadRegion(bouyNo: 3)
                    }) {
                        Image(systemName: "location.fill")
                            .foregroundColor(.white)
                            .font(.system(size: 16))
                    }
                    .frame(width: 32, height: 25)
                    .cornerRadius(30.0)
                    
                }
                .background(Color(red: 0.4, green: 0.5, blue: 0.2))   //背景色
                .cornerRadius(30.0)
            }
        }
    }
}
  • 出来たもの
    • ボタン群、左から
      • GPSブイNo.1 投下ボタン
      • GPSブイNo.2 投下ボタン
      • GPSブイNo.3 投下ボタン
      • 現在位置にマップを移動
    • ブイ投下すると、ブイマークとナンバーが表示

      

GPSブイとの距離

  • 距離の計算方法は、Hubenyの公式を採用します
    • 主に日本の計算サイトなどで用いられている公式で,これもざっくり下図を用いて説明すると,まず,2地点A,Bの中点Rにおける回転楕円体の子午線方向と卯酉線方向のそれぞれの曲がり具合を円で近似します.次に2地点A,Bの経度差をΔx,緯度差をΔyとし,回転楕円体の近似円の子午線方向の曲率半径をM,卯酉線方向の曲率半径をNとします.点Aと経度が等しく,点Bと緯度が等しい点Hをつくって直角三角形をつくります.角AOH=Δyと書けるのでAH≒MΔyと近似でき,Rの緯度をμとすると線分BHもBH≒NcosμΔxと近似できます.この線分AHと線分BHを利用して求める距離である線分ABを3平方の定理で導きます
    • 下記C言語プログラムをSwift言語に書き換え、GPSブイNo.1との距離を表示します。単位はメートルとします
func deg2rad(_ deg:CGFloat ) -> CGFloat {
    return CGFloat.pi / 180.0 * deg
}

func cal_distance(x1: Double, y1: Double, x2: Double, y2: Double)->(Double) {
    let dx = x2 - x1
    let dy = y2 - y1
    let mu = (y1 + y2) / 2.0 // μ
    let RX = 6378.137; // 回転楕円体の長半径(赤道半径)[km]
    let RY = 6356.752; // 回転楕円体の短半径(極半径) [km]
    let E = sqrt(1 - pow(RY / RX, 2.0)) // 離心率
    let W = sqrt(1 - pow(E * sin(mu), 2.0))
    let M = RX * (1 - pow(E, 2.0)) / pow(W, 3.0) // 子午線曲率半径
    let N = RX / W // 卯酉線曲率半径
    return sqrt(pow(M * dy, 2.0) + pow(N * dx * cos(mu), 2.0)) // 距離[km]
}

    func locationDistance() -> Int {
        if let location = manager.location {
            let a = deg2rad( pointList[0].latitude )
            let b = deg2rad( pointList[0].longitude )
            let c = deg2rad( location.coordinate.latitude )
            let d = deg2rad( location.coordinate.longitude )
            let e = Int( cal_distance(x1: a, y1: b, x2: c, y2: d) * 1000.0 ) % 10000
            return( e )

        }else{
            return( 0 )
        }
    }
 
  • 表示
    • 4桁のメートルとしています
    • 「Off」にすると、拡大、縮小、移動が自由に出来ます。「On」場合は縮率が600mでGPSで測定した位置が画面中心になります

    • 下ボタン群、左から

      • GPSブイNo.1 投下ボタン
      • GPSブイNo.2 投下ボタン
      • GPSブイNo.3 投下ボタン
      • 現在位置にマップを移動 ー> 拡大鏡に変更

GPSの動きを手動で止める

  • AppleWatchの「設定」>「ブライバシーとセキュリティー」>「位置情報サービス」>「gpsBuoy」>「しない」とする
  • 止まって、地図が表示されなくなる

  • どなたか、プログラムを使った止めかたをご教授して頂けると幸いです

`