// // StartMotionVC.swift // Twear // // Created by yangbin on 2021/12/28. // import UIKit import SnapKit import SwiftDate import CoreLocation import MBProgressHUD import HCKalmanFilter class StartMotionVC: UIViewController { @IBOutlet weak var mapBackView: UIView! @IBOutlet weak var countImageView: UIImageView! @IBOutlet weak var detailView: UIView! @IBOutlet weak var typeImageView: UIImageView! @IBOutlet weak var stopButton: ProgressButton! @IBOutlet weak var mapButton: UIButton! @IBOutlet weak var startButton: UIButton! @IBOutlet weak var pauseButton: UIButton! @IBOutlet weak var calorieLabel: UILabel! @IBOutlet weak var timeLabel: UILabel! @IBOutlet weak var signalImageView: UIImageView! @IBOutlet weak var gpsLabel: UILabel! @IBOutlet weak var signalImageView1: UIImageView! @IBOutlet weak var gpsView: UIView! @IBOutlet weak var motionTypeLabel: UILabel! @IBOutlet weak var mapTimeLabel: UILabel! @IBOutlet weak var mapDistanceLabel: UILabel! @IBOutlet weak var distanceLabel: UILabel! @IBOutlet weak var speedLabel: UILabel! @IBOutlet weak var speedUnitLabel: UILabel! @IBOutlet weak var unitLabel: UILabel! @IBOutlet weak var unitLabel1: UILabel! private lazy var traceManager: MATraceManager! = MATraceManager() let user = UserInfo private lazy var mapView: MAMapView = MAMapView() private var count: Int = 3 private var speedArray: [Float] = [] var isRecord: Bool = false var motionType: TrainType = .running var time: Int = 0 var distance: Float = 0 var speed: Float = 0 var calorie: Float = 0 var startDate = Date() var coordinateArray: [CLLocationCoordinate2D] = [] var locationArray: [CLLocation] = [] var stepCadenceArray: [Int] = [] var altitudeArray: [Double] = [] var resetKalmanFilter: Bool = false var hcKalmanFilter: HCKalmanAlgorithm? private lazy var locationManager: AMapLocationManager = AMapLocationManager() override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) GCDTimer.shared.scheduledDispatchTimer(WithTimerName: "count_down", timeInterval: 1, queue: .main, repeats: true) {[weak self] in self?.countDown() } locationManager.startUpdatingLocation() locationManager.startUpdatingHeading() } override func viewDidLoad() { super.viewDidLoad() stopButton.progressClosure = { [weak self] in self?.stopMotion() // self?.dismiss(animated: true) } var motionTypeText = "户外跑步" var typeImageName = "motion_run" switch motionType { case .run_indoor: motionTypeText = "室内跑步" case .walking: motionTypeText = "步行" typeImageName = "motion_walking" case .bicycle: motionTypeText = "骑行" typeImageName = "motion_bicycle" case .mountaineering: motionTypeText = "爬山" typeImageName = "motion_mountaineering" default: break } typeImageView.image = UIImage(named: typeImageName) motionTypeLabel.text = LocString(motionTypeText) if user.distanceUnit == 0 { speedUnitLabel.text = LocString("公里/分钟") unitLabel.text = LocString("公里") unitLabel1.text = LocString("公里") } else { speedUnitLabel.text = LocString("英里/分钟") unitLabel.text = LocString("英里") unitLabel1.text = LocString("英里") } initMapView() } func initMapView() { MAMapView.updatePrivacyShow(.didShow, privacyInfo: .didContain) MAMapView.updatePrivacyAgree(.didAgree) AMapServices.shared().enableHTTPS = true mapView.showsUserLocation = false let compassX = mapView.compassOrigin.x mapView.compassOrigin = CGPoint(x: compassX-5, y: 120) mapView.userTrackingMode = .followWithHeading mapView.zoomLevel = 17.5 mapView.delegate = self mapView.mapLanguage = (AppSettings.shared.language == .Chinese) ? 0 : 1 // mapView.distanceFilter = 10 // mapView.desiredAccuracy = kCLLocationAccuracyBestForNavigation mapView.frame = mapBackView.frame mapBackView.addSubview(mapView) mapBackView.sendSubviewToBack(mapView) mapBackView.isHidden = true locationManager.delegate = self locationManager.pausesLocationUpdatesAutomatically = false // locationManager.allowsBackgroundLocationUpdates = true if motionType == .run_indoor { locationManager.distanceFilter = 12 } else { locationManager.distanceFilter = 23 } locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation locationManager.allowsBackgroundLocationUpdates = true // locationManager.startUpdatingLocation() } func startMotion() { time += 1 timeLabel.toTimeType3(length: time) mapTimeLabel.toTimeType3(length: time) } func stopMotion() { isRecord = false if distance >= 500 { var altitude: Double = 0 if let minAltitude = locationArray.min(\.altitude)?.altitude, let maxAltitude = locationArray.max(\.altitude)?.altitude { altitude = maxAltitude - minAltitude } var coordinateStr = "" for coordinate in coordinateArray { coordinateStr += "\(coordinate.latitude),\(coordinate.longitude)|" } coordinateStr = coordinateStr.substring(toIndex: coordinateStr.count-1) var speedStr = "" for speed in speedArray { speedStr += "\(speed)|" } speedStr = speedStr.substring(toIndex: speedStr.count-1) var stepCadenceStr = "" for cadence in stepCadenceArray { stepCadenceStr += "\(cadence)|" } stepCadenceStr = stepCadenceStr.substring(toIndex: stepCadenceStr.count-1) var altitudeStr = "" for alt in altitudeArray { altitudeStr += "\(alt)|" } altitudeStr = altitudeStr.substring(toIndex: altitudeStr.count-1) let motion = MotionModel(type: motionType, date: DateInRegion().date - time.seconds, length: time, calorie: calorie, distance: Int(distance), steps: distanceToStep(distance), altitude: altitude, coordinateArray: coordinateStr, speedArray: speedStr, stepCadenceArray: stepCadenceStr, altitudeArray: altitudeStr) motion.add() MedalModel.update(motion: motion) // MBProgressHUD.showh("..test") let vc = UIStoryboard.loadViewControllerIdentifier(storyboardName: "Motion", identifier: "EndMotionVC") as! EndMotionVC vc.modalPresentationStyle = .fullScreen vc.coordinateArray = coordinateArray vc.calorie = calorie vc.distance = distance vc.length = time vc.date = startDate vc.speedArray = speedArray vc.motionType = motionType vc.altitudeArray = altitudeArray vc.stepCadenceArray = stepCadenceArray vc.altitude = altitude self.present(vc, animated: true, completion: nil) // self.dismiss(animated: true, completion: nil) } else { let view = BottomAlertView(title: LocString("本次运动距离太短,将不保存记录"), sureText: LocString("我知道了")) view.clickClosure = {[weak self] index in self?.dismiss(animated: true) } view.show() } } @IBAction func continueMotion(_ sender: Any) { isRecord = true showStopButton(false) GCDTimer.shared.scheduledDispatchTimerNotNow(WithTimerName: "StartMotion", timeInterval: 1, queue: .main, repeats: true) { [weak self] in self?.startMotion() } } @IBAction func pauseMotion(_ sender: Any) { isRecord = false showStopButton(true) if GCDTimer.shared.isExistTimer(WithTimerName: "StartMotion") { GCDTimer.shared.cancleTimer(WithTimerName: "StartMotion") } } func showStopButton(_ isShow: Bool) { pauseButton.isHidden = isShow self.startButton.isHidden = !isShow self.stopButton.isHidden = !isShow let centerX = isShow ? SCREEN_WIDTH/2*0.55 : SCREEN_WIDTH/2 UIView.animate(withDuration: 0.3) { self.stopButton.center.x = centerX self.startButton.center.x = SCREEN_WIDTH-centerX } completion: { finish in } } func countDown() { if count >= 0 { countImageView.image = UIImage(named: "count_down_\(count)") let animation = CABasicAnimation(keyPath: "transform.scale") animation.fromValue = 0.5 animation.toValue = 1 animation.duration = 0.5 animation.beginTime = 0 if count == 1 { hideMap(1) } self.countImageView.layer.add(animation, forKey: nil) count -= 1 } else { if GCDTimer.shared.isExistTimer(WithTimerName: "count_down") { GCDTimer.shared.cancleTimer(WithTimerName: "count_down") } self.countImageView.isHidden = true if motionType == .run_indoor { self.mapButton.isHidden = true self.signalImageView.isHidden = true self.gpsLabel.isHidden = true } else { self.mapButton.isHidden = false self.signalImageView.isHidden = false self.gpsLabel.isHidden = false } self.detailView.isHidden = false self.motionTypeLabel.isHidden = false isRecord = true startDate = DateInRegion().date GCDTimer.shared.scheduledDispatchTimerNotNow(WithTimerName: "StartMotion", timeInterval: 1, queue: .main, repeats: true) { [weak self] in self?.startMotion() } locationManager.startUpdatingLocation() // resetKalmanFilter = true } } @IBAction func showMap(_ sender: Any) { mapBackView.isHidden = false mapBackView.isUserInteractionEnabled = false UIView.animate(withDuration: 0.3) { self.mapBackView.center = self.view.center self.mapBackView.transform = .identity // self.view.layoutIfNeeded() } completion: { finish in self.mapBackView.isUserInteractionEnabled = true } } @IBAction func hideMap(_ sender: Any) { UIView.animate(withDuration: 0.3) { self.mapBackView.transform = CGAffineTransform.init(scaleX: 0.02, y: 0.02) self.mapBackView.center = self.mapButton.center // self.view.layoutIfNeeded() } completion: { finish in self.mapBackView.isHidden = true } } deinit { print("deinit\(NSStringFromClass(type(of: self)))!!!!!!!") } private func distanceToCalorie(_ distance: Float) -> Float { let user = UserInfo var weight = user.weight if user.weight < 15 { weight = 65 } if user.gender == 1 { return Float((weight - 10)) * distance } else { return Float((weight - 10)) * distance * 1.3 } } func updateSignal(_ accuracy: CLLocationAccuracy) { var signal: Int = 3 if accuracy < 0 { signal = 0 } else if accuracy > 143 { signal = 1 } else if accuracy > 48 { signal = 2 } else { signal = 3 } signalImageView.image = UIImage(named: "signal_\(signal)") signalImageView1.image = UIImage(named: "signal_map_\(signal)") } private func distanceToStepCadence(_ distance: Float, time: Int) -> Int { let user = UserInfo if user.gender == 1 { return Int (distance * 100 / 0.4 / Float(user.height)) * 60 / time } else { return Int (distance * 100 / 0.35 / Float(user.height)) * 60 / time } } private func distanceToStep(_ distance: Float) -> Int { let user = UserInfo if user.gender == 1 { return Int (distance * 100 / 0.4 / Float(user.height)) } else { return Int (distance * 100 / 0.35 / Float(user.height)) } } } extension StartMotionVC: AMapLocationManagerDelegate { func amapLocationManager(_ manager: AMapLocationManager!, didUpdate location: CLLocation!) { // mapView.showsUserLocation if !isRecord { return } // if !updatingLocation { // return // } // guard let location = userLocation.location else { // return // } print("信号强度:\(location.horizontalAccuracy)") updateSignal(location.horizontalAccuracy) if coordinateArray.count == 0 { // coordinateArray.append(location.coordinate) // locationArray.append(location) } else { let lastCoordinate = coordinateArray.last! let point1 = MAMapPointForCoordinate(lastCoordinate) let point2 = MAMapPointForCoordinate(location.coordinate) let dis = MAMetersBetweenMapPoints(point1, point2) // print(Float(dis)) distance += Float(dis) // print(distance) if distance == 0 { return } if user.distanceUnit == 0 { distanceLabel.text = String(format:"%.2f", distance/1000) mapDistanceLabel.text = String(format:"%.2f", distance/1000) speed = Float(time)/(distance/1000) if speed > 0 { speedArray.append(speed) } speedLabel.text = "\(String(format:"%02d", Int(speed)/60))′\(String(format:"%02d", Int(speed)%60))″" } else { distanceLabel.text = (distance/1000).footString() mapDistanceLabel.text = (distance/1000).footString() speed = Float(time)/(distance/1000) if speed > 0 { speedArray.append(speed) } speedLabel.text = "\(String(format:"%02d", Int(speed/0.6213)/60))′\(String(format:"%02d", Int(speed/0.6213)%60))″" } calorie = distanceToCalorie(distance/1000) calorieLabel.text = String(format:"%.2f", calorie) if time > 0 { stepCadenceArray.append(distanceToStepCadence(distance, time: time)) } altitudeArray.append(Double(location.altitude)) } print("添加") if hcKalmanFilter == nil { self.hcKalmanFilter = HCKalmanAlgorithm(initialLocation: location) hcKalmanFilter?.rValue = 40 hcKalmanFilter?.resetKalman(newStartLocation: location) self.coordinateArray.append(location.coordinate) self.locationArray.append(location) } else { let kalmanLocation = hcKalmanFilter?.processState(currentLocation: location) // print("\(kalmanLocation?.coordinate) --- \(location.coordinate)") self.coordinateArray.append(kalmanLocation!.coordinate) self.locationArray.append(kalmanLocation!) } updatePath() // let kalmanLocation = hcKalmanFilter?.processState(currentLocation: location) // print(kalmanLocation!.coordinate) // print(location!.getDistanceFrom(userLocation.location)) } } extension StartMotionVC: MAMapViewDelegate { // func mapView(_ mapView: MAMapView!, viewFor annotation: MAAnnotation!) -> MAAnnotationView! { // if annotation is MAUserLocation { // var userView = mapView.dequeueReusableAnnotationView(withIdentifier: "userAnnotationIdentifer") // if userView == nil { // userView = MAAnnotationView(annotation: annotation, reuseIdentifier: "userAnnotationIdentifer") // } // userView!.image = UIImage(named: "firmware_update") // return userView! // } // return nil // } func mapView(_ mapView: MAMapView!, didUpdate userLocation: MAUserLocation!, updatingLocation: Bool) { // if updatingLocation { // let userView = mapView.view(for: userLocation) // // //取方向信息 // if let userHeading = userLocation.heading { // //根据方向旋转箭头 // userView?.rotateWithHeading(heading: userHeading) // } // } /* if !isRecord { return } if !updatingLocation { return } guard let location = userLocation.location else { return } if coordinateArray.count == 0 { coordinateArray.append(location.coordinate) } else { let lastCoordinate = coordinateArray.last! let point1 = MAMapPointForCoordinate(lastCoordinate) let point2 = MAMapPointForCoordinate(location.coordinate) let dis = MAMetersBetweenMapPoints(point1, point2) // print(Float(dis)) distance += Float(dis) // print(distance) if distance == 0 { return } distanceLabel.text = String(format:"%.2f", distance/1000) mapDistanceLabel.text = String(format:"%.2f", distance/1000) speed = Float(time)/(distance/1000) speedLabel.text = "\(String(format:"%02d", Int(speed)/60))′\(String(format:"%02d", Int(speed)%60))″" calorieLabel.text = String(format:"%.2f", distanceToCalorie(distance/1000)/1000) } print("添加") // print(location!.getDistanceFrom(userLocation.location)) self.coordinateArray.append(location.coordinate) updatePath() */ // if isRecording { // // filter the result // if userLocation.location.horizontalAccuracy < 100.0 { // // addLocation(location: userLocation.location) // } // } // // var speed = location!.speed // if speed < 0.0 { // speed = 0.0 // } // // let infoArray: [(String, String)] = [ // ("coordinate", NSString(format: "<%.4f, %.4f>", location!.coordinate.latitude, location!.coordinate.longitude) as String), // ("speed", NSString(format: "%.2fm/s(%.2fkm/h)", speed, speed * 3.6) as String), // ("accuracy", "\(location!.horizontalAccuracy)m"), // ("altitude", NSString(format: "%.2fm", location!.altitude) as String)] // // statusView!.showStatusInfo(info: infoArray) } func updatePath () { // 每次获取到新的定位点重新绘制路径 /* var mArr = [MATraceLocation]() for loc: CLLocation in self.locationArray { let tLoc = MATraceLocation() tLoc.loc = loc.coordinate tLoc.speed = loc.speed * 3.6 //m/s 转 km/h tLoc.time = loc.timestamp.timeIntervalSince1970 * 1000 tLoc.angle = loc.course mArr.append(tLoc) } weak var weakSelf = self _ = traceManager.queryProcessedTrace(with: mArr, type: .aMap, processingCallback: { (index:Int32, arr:[MATracePoint]?) in }, finishCallback: { (arr:[MATracePoint]?, distance:Double) in // if saving { // weakSelf?.totalTraceLength = 0.0 // weakSelf?.currentRecord?.updateTracedLocations(arr) // weakSelf?.saveRoute() // weakSelf?.isSaving = false // weakSelf?.showTip(tip: "recording save done") // } // weakSelf?.updateUserlocationTitle(withDistance: distance) weakSelf?.addFullTrace(arr) }, failedCallback: { (errCode:Int32, errDesc:String?) in print(errDesc ?? "error") // weakSelf?.isSaving = false }) */ // 移除掉除之前的overlay let overlays = self.mapView.overlays self.mapView.removeOverlays(overlays) let polyline = MAPolyline(coordinates: &self.coordinateArray, count: UInt(self.coordinateArray.count)) self.mapView.add(polyline) // 将最新的点定位到界面正中间显示 let lastCoord = self.coordinateArray[self.coordinateArray.count - 1] self.mapView.setCenter(lastCoord, animated: true) } func addFullTrace(_ tracePoints: [MATracePoint]?) { let polyline: MAPolyline? = self.makePolyline(with: tracePoints) if polyline == nil { return } // self.tracedPolylines.append(polyline!) self.mapView.add(polyline!) if let lastCoord = self.coordinateArray.last { self.mapView.setCenter(lastCoord, animated: true) } } func makePolyline(with tracePoints: [MATracePoint]?) -> MAPolyline? { if tracePoints == nil || tracePoints!.count < 2 { return nil } var pCoords = [CLLocationCoordinate2D]() for i in 0.. MAOverlayRenderer! { if let poly = overlay as? MAPolyline { let polylineView = MAPolylineRenderer(polyline: poly) polylineView!.lineWidth = 6 polylineView!.strokeColor = UIColor(red: 4 / 255.0, green: 181 / 255.0, blue: 108 / 255.0, alpha: 1.0) polylineView!.lineJoinType = kMALineJoinRound polylineView!.lineCapType = kMALineCapRound return polylineView } else { return nil } } // // func mapView(mapView: MAMapView!, viewForOverlay overlay: MAOverlay!) -> MAOverlayView! { // if overlay.isKindOfClass(MAPolyline) { // let polylineView = MAPolylineView(overlay: overlay) // polylineView.lineWidth = 6 // polylineView.strokeColor = UIColor(red: 4 / 255.0, green: 181 / 255.0, blue: 108 / 255.0, alpha: 1.0) // // return polylineView // } // // return nil // } func mapView(_ mapView: MAMapView, didChange mode: MAUserTrackingMode, animated: Bool) { // if mode == MAUserTrackingMode.none { // locationButton?.setImage(imageNotLocate, for: UIControlState.normal) // } // else { // locationButton?.setImage(imageLocated, for: UIControlState.normal) // } } } extension MAAnnotationView { /// 根据heading信息旋转大头针视图 /// /// - Parameter heading: 方向信息 func rotateWithHeading(heading: CLHeading) { //将设备的方向角度换算成弧度 let headings = Double.pi * heading.magneticHeading / 180.0 //创建不断旋转CALayer的transform属性的动画 let rotateAnimation = CABasicAnimation(keyPath: "transform") //动画起始值 let formValue = self.layer.transform rotateAnimation.fromValue = NSValue(caTransform3D: formValue) //绕Z轴旋转heading弧度的变换矩阵 let toValue = CATransform3DMakeRotation(CGFloat(headings), 0, 0, 1) //设置动画结束值 rotateAnimation.toValue = NSValue(caTransform3D: toValue) rotateAnimation.duration = 0.35 rotateAnimation.isRemovedOnCompletion = true //设置动画结束后layer的变换矩阵 self.layer.transform = toValue //添加动画 self.layer.add(rotateAnimation, forKey: nil) } }