アプリケーションに Amazon Location の追跡を追加する - Amazon Location Service

アプリケーションに Amazon Location の追跡を追加する

アプリケーションの最後のステップは、追跡機能を追加することです。この場合、追跡の開始、停止、トラッカーポイントの取得と表示をアプリケーションに追加します。

  1. プロジェクトに TrackingBottomView.swift ファイルを追加します。このファイルには、ユーザーの位置の追跡を開始および停止し、マップ上に追跡ポイントを表示するボタンがあります。

    import SwiftUI struct TrackingBottomView: View { @ObservedObject var trackingViewModel: TrackingViewModel var body: some View { Button(action: { Task { if(trackingViewModel.trackingButtonText == NSLocalizedString("StartTrackingLabel", comment: "")) { trackingViewModel.startTracking() } else { trackingViewModel.stopTracking() } } }) { HStack { Spacer() Text("Tracking") .foregroundColor(trackingViewModel.trackingButtonColor) .background(.white) .cornerRadius(15.0) Image(systemName: trackingViewModel.trackingButtonIcon) .resizable() .frame(width: 24, height: 24) .padding(5) .background(.white) .foregroundColor(trackingViewModel.trackingButtonColor) } } .accessibility(identifier: "TrackingButton") .background(.white) .clipShape(RoundedRectangle(cornerRadius: 8)) .padding(.trailing, 10) .padding(.bottom, 40) .frame(width: 130, alignment: .trailing) .shadow(color: Color.black.opacity(0.3), radius: 3, x: 0, y: 2) } }
  2. TrackingView.swift ファイルを以下のコードで更新します。

    import SwiftUI struct TrackingView: View { @ObservedObject var trackingViewModel: TrackingViewModel var body: some View { ZStack(alignment: .bottom) { if trackingViewModel.mapSigningIntialised { MapView(trackingViewModel: trackingViewModel) VStack { UserLocationView(trackingViewModel: trackingViewModel) CenterAddressView(trackingViewModel: trackingViewModel) TrackingBottomView(trackingViewModel: trackingViewModel) } } else { Text("Loading...") } } .onAppear() { if !trackingViewModel.identityPoolId.isEmpty { Task { do { try await trackingViewModel.authWithCognito(identityPoolId: trackingViewModel.identityPoolId) } catch { print(error) } } } } } }
  3. TrackingViewModel.swift ファイルに以下のコードを追加します。これらの関数は追跡の開始と停止を処理します。また、ユーザーの位置情報に対するアクセス許可が拒否された場合は、エラーアラートを表示します。

  4. フォアグラウンド追跡を実装するために、以下のコード例をコピーして貼り付けます。

    func showLocationDeniedRationale() { alertTitle = NSLocalizedString("locationManagerAlertTitle", comment: "") alertMessage = NSLocalizedString("locationManagerAlertText", comment: "") showAlert = true } // Required in info.plist: Privacy - Location When In Use Usage Description func startTracking() { do { print("Tracking Started...") if(client == nil) { initializeClient() } try client.startTracking() DispatchQueue.main.async { [self] in self.trackingButtonText = NSLocalizedString("StopTrackingLabel", comment: "") self.trackingButtonColor = .red self.trackingButtonIcon = "pause.circle" trackingActive = true } } catch TrackingLocationError.permissionDenied { showLocationDeniedRationale() } catch { print("error in tracking") } } func stopTracking() { print("Tracking Stopped...") client.stopTracking() trackingButtonText = NSLocalizedString("StartTrackingLabel", comment: "") trackingButtonColor = .blue trackingButtonIcon = "play.circle" trackingActive = false }
    注記

    startTracking は、ユーザーの位置情報に対するアクセス許可をリクエストします。アプリケーションは [使用時] または [1 回のみ] のアクセス許可を使用する必要があります。それ以外の場合、アプリケーションはアクセス許可の拒否エラーをスローします。

追跡位置情報を取得して表示するには、以下の手順に従います。

  1. ユーザーのデバイスから位置情報を取得するには、開始と終了の日時を指定する必要があります。1 回の呼び出しで最大 100 件の追跡位置情報が返されますが、追跡位置情報が 100 件を超える場合は「nextToken」値が返されます。指定した開始時刻と終了時刻の間の追跡ポイントをさらに読み込むには、「nextToken」を使用して後続の「getTrackerDeviceLocation」を呼び出す必要があります。

    func getTrackingPoints(nextToken: String? = nil) async throws { guard trackingActive else { return } // Initialize startTime to 24 hours ago from the current date and time. let startTime: Date = Date().addingTimeInterval(-86400) var endTime: Date = Date() if lastGetTrackingTime != nil { endTime = lastGetTrackingTime! } let result = try await client?.getTrackerDeviceLocation(nextToken: nextToken, startTime: startTime, endTime: endTime) if let trackingData = result { lastGetTrackingTime = Date() let devicePositions = trackingData.devicePositions let positions = devicePositions!.sorted { (pos1: LocationClientTypes.DevicePosition, pos2: LocationClientTypes.DevicePosition) -> Bool in guard let date1 = pos1.sampleTime, let date2 = pos2.sampleTime else { return false } return date1 < date2 } let trackingPoints = positions.compactMap { position -> CLLocationCoordinate2D? in guard let latitude = position.position!.last, let longitude = position.position!.first else { return nil } return CLLocationCoordinate2D(latitude: latitude, longitude: longitude) } DispatchQueue.main.async { self.mapViewDelegate!.drawTrackingPoints( trackingPoints: trackingPoints) } if let nextToken = trackingData.nextToken { try await getTrackingPoints(nextToken: nextToken) } } }
  2. 次は、MapView.swift ファイル内のコードを以下のコードに置き換えます。

    import SwiftUI import MapLibre struct MapView: UIViewRepresentable { var onMapViewAvailable: ((MLNMapView) -> Void)? var mlnMapView: MLNMapView? var trackingViewModel: TrackingViewModel func makeCoordinator() -> MapView.Coordinator { return Coordinator(self, trackingViewModel: trackingViewModel) } func makeUIView(context: Context) -> MLNMapView { let styleURL = URL(string: "https://maps.geo.\(trackingViewModel.region).amazonaws.com/maps/v0/maps/\(trackingViewModel.mapName)/style-descriptor") let mapView = MLNMapView(frame: .zero, styleURL: styleURL) mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight] mapView.setZoomLevel(15, animated: true) mapView.showsUserLocation = true mapView.userTrackingMode = .follow context.coordinator.mlnMapView = mapView mapView.delegate = context.coordinator mapView.logoView.isHidden = true context.coordinator.addCenterMarker() onMapViewAvailable?(mapView) trackingViewModel.mlnMapView = mapView return mapView } func updateUIView(_ uiView: MLNMapView, context: Context) { } class Coordinator: NSObject, MLNMapViewDelegate, MapViewDelegate { var control: MapView var mlnMapView: MLNMapView? var trackingViewModel: TrackingViewModel var centerMarker: MLNPointAnnotation? public init(_ control: MapView, trackingViewModel: TrackingViewModel) { self.control = control self.trackingViewModel = trackingViewModel super.init() self.trackingViewModel.mapViewDelegate = self } func mapViewDidFinishRenderingMap(_ mapView: MLNMapView, fullyRendered: Bool) { if(fullyRendered) { mapView.accessibilityIdentifier = "MapView" mapView.isAccessibilityElement = false } } func addCenterMarker() { guard let mlnMapView = mlnMapView else { return } let centerCoordinate = mlnMapView.centerCoordinate let marker = MLNPointAnnotation() marker.coordinate = centerCoordinate marker.accessibilityLabel = "CenterMarker" mlnMapView.addAnnotation(marker) centerMarker = marker trackingViewModel.reverseGeocodeCenter(centerCoordinate: centerCoordinate, marker: marker) } func mapView(_ mapView: MLNMapView, regionDidChangeAnimated animated: Bool) { if let marker = centerMarker { DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { mapView.deselectAnnotation(marker, animated: false) marker.coordinate = mapView.centerCoordinate let centerCoordinate = mapView.centerCoordinate self.trackingViewModel.reverseGeocodeCenter(centerCoordinate: centerCoordinate, marker: marker) } } } func mapView(_ mapView: MLNMapView, viewFor annotation: MLNAnnotation) -> MLNAnnotationView? { guard let pointAnnotation = annotation as? MLNPointAnnotation else { return nil } let reuseIdentifier: String var color: UIColor = .black if pointAnnotation.accessibilityLabel == "Tracking" { reuseIdentifier = "TrackingAnnotation" color = UIColor(red: 0.00784313725, green: 0.50588235294, blue: 0.58039215686, alpha: 1) } else if pointAnnotation.accessibilityLabel == "LocationChange" { reuseIdentifier = "LocationChange" color = .gray } else { reuseIdentifier = "DefaultAnnotationView" } var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier) if annotationView == nil { if reuseIdentifier != "DefaultAnnotationView" { annotationView = MLNAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier) //If point annotation is an uploaded Tracking point the radius is 20 and color is blue, otherwise radius is 10 and color is gray let radius = pointAnnotation.accessibilityLabel == "Tracking" ? 20:10 annotationView?.frame = CGRect(x: 0, y: 0, width: radius, height: radius) annotationView?.backgroundColor = color annotationView?.layer.cornerRadius = 10 if pointAnnotation.accessibilityLabel == "Tracking" { annotationView?.layer.borderColor = UIColor.white.cgColor annotationView?.layer.borderWidth = 2.0 annotationView?.layer.shadowColor = UIColor.black.cgColor annotationView?.layer.shadowOffset = CGSize(width: 0, height: 2) annotationView?.layer.shadowRadius = 3 annotationView?.layer.shadowOpacity = 0.2 annotationView?.clipsToBounds = false } } else { return nil } } return annotationView } func mapView(_ mapView: MLNMapView, didUpdate userLocation: MLNUserLocation?) { if (userLocation?.location) != nil { if trackingViewModel.trackingActive { let point = MLNPointAnnotation() point.coordinate = (userLocation?.location!.coordinate)! point.accessibilityLabel = "LocationChange" mapView.addAnnotation(point) Task { do { try await trackingViewModel.getTrackingPoints() } catch { print(error) } } } } } func checkIfTrackingAnnotationExists(on mapView: MLNMapView, at coordinates: CLLocationCoordinate2D) -> Bool { let existingAnnotation = mapView.annotations?.first(where: { annotation in guard let annotation = annotation as? MLNPointAnnotation else { return false } return annotation.coordinate.latitude == coordinates.latitude && annotation.coordinate.longitude == coordinates.longitude && annotation.accessibilityLabel == "Tracking" }) return existingAnnotation != nil } public func drawTrackingPoints(trackingPoints: [CLLocationCoordinate2D]?) { guard let mapView = mlnMapView, let newTrackingPoints = trackingPoints, !newTrackingPoints.isEmpty else { return } let uniqueCoordinates = newTrackingPoints.filter { coordinate in !checkIfTrackingAnnotationExists(on: mapView, at: coordinate) } let points = uniqueCoordinates.map { coordinate -> MLNPointAnnotation in let point = MLNPointAnnotation() point.coordinate = coordinate point.accessibilityLabel = "Tracking" return point } mapView.addAnnotations(points) } } } protocol MapViewDelegate: AnyObject { func drawTrackingPoints(trackingPoints: [CLLocationCoordinate2D]?) }

文字列値をローカライズするには、以下の手順に従います。

  1. Localizable.xcstrings という名前の新しいファイルを作成し、追加します。

  2. Localizable.xcstrings ファイルを右クリックし、ソースコードとして開きます。

  3. その内容を以下のように置き換えます。

    { "sourceLanguage" : "en", "strings" : { "Cancel" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", "value" : "Cancel" } } } }, "Error" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", "value" : "Error" } } } }, "Loading..." : { }, "locationManagerAlertText" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", "value" : "Allow \\\"Quick Start App\\\" to use your location" } } } }, "locationManagerAlertTitle" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", "value" : "We need your location to detect your location in map" } } } }, "NotAllFieldsAreConfigured" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", "value" : "Not all the fields are configured" } } } }, "OK" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", "value" : "OK" } } } }, "StartTrackingLabel" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", "value" : "Start Tracking" } } } }, "StopTrackingLabel" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", "value" : "Stop Tracking" } } } }, "Tracking" : { } }, "version" : "1.0" }
  4. ファイルを保存し、アプリケーションをビルドして実行して、機能をプレビューします。

  5. 位置情報に対するアクセス許可を付与し、[追跡] ボタンをタップします。アプリケーションはユーザーの位置情報の取得を開始します。取得した情報は Amazon Location Tracker にアップロードします。また、ユーザーの位置の変更、追跡ポイント、現在の住所をマップ上に表示します。

これでクイックスタートアプリケーションが完成しました。このチュートリアルでは、以下の機能を備えた iOS アプリケーションの作成方法について説明しました。

  • ユーザーが操作できるマップを作成します。

  • ユーザーがマップビューを変更する際に関連する複数のマップイベントを処理します。

  • Amazon Location Service API を呼び出します。例えば、Amazon Location の searchByPosition API を使用して、特定の位置でマップを検索します。

次のステップ

クイックスタートチュートリアルを完了したので、Amazon Location Service を使用してアプリケーションを構築する方法を理解できるでしょう。

このアプリケーションのソースコードは GitHub で入手できます。

Amazon Location をさらに活用するには、以下のリソースをご覧ください。