From 72595ff37d97adbe9da94a12ad9102ee76514b39 Mon Sep 17 00:00:00 2001 From: Xerath Date: Wed, 13 Dec 2023 09:36:14 +0900 Subject: [PATCH 1/6] =?UTF-8?q?=E2=9C=A8=20=ED=8F=89=EA=B7=A0=EA=B0=92?= =?UTF-8?q?=EA=B3=BC=20=EB=81=9D=EA=B0=92=20drop=EC=9D=84=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=ED=95=9C=20Polyline=20=EB=93=9C=EB=A1=9C=EC=9E=89=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/MapViewController.swift | 31 ++++----- .../RecordJourneyViewModel.swift | 64 +++++++++++++++++++ 2 files changed, 76 insertions(+), 19 deletions(-) diff --git a/iOS/Features/Home/Sources/NavigateMap/Presentation/Common/MapViewController.swift b/iOS/Features/Home/Sources/NavigateMap/Presentation/Common/MapViewController.swift index edcf843..efa49e5 100644 --- a/iOS/Features/Home/Sources/NavigateMap/Presentation/Common/MapViewController.swift +++ b/iOS/Features/Home/Sources/NavigateMap/Presentation/Common/MapViewController.swift @@ -324,20 +324,13 @@ extension MapViewController: CLLocationManagerDelegate { return } - let previousCoordinate = (self.viewModel as? RecordJourneyViewModel)?.state.previousCoordinate.value - - if let previousCoordinate = previousCoordinate { - if !self.isDistanceOver5AndUnder50(coordinate1: previousCoordinate, - coordinate2: newCurrentLocation.coordinate) { - return - } - } - let coordinate2D = CLLocationCoordinate2D(latitude: newCurrentLocation.coordinate.latitude, longitude: newCurrentLocation.coordinate.longitude) - - recordJourneyViewModel.trigger(.locationDidUpdated(coordinate2D)) - recordJourneyViewModel.trigger(.locationsShouldRecorded([coordinate2D])) + recordJourneyViewModel.trigger(.fiveLocationsDidRecorded(coordinate2D)) + guard let filteredCoordinate2D = recordJourneyViewModel.state.filteredCoordinate.value else { return } + dump(filteredCoordinate2D) + recordJourneyViewModel.trigger(.locationDidUpdated(filteredCoordinate2D)) + recordJourneyViewModel.trigger(.locationsShouldRecorded([filteredCoordinate2D])) } private func handleAuthorizationChange(_ manager: CLLocationManager) { @@ -378,13 +371,13 @@ extension MapViewController: CLLocationManagerDelegate { } } - private func isDistanceOver5AndUnder50(coordinate1: CLLocationCoordinate2D, - coordinate2: CLLocationCoordinate2D) -> Bool { - let location1 = CLLocation(latitude: coordinate1.latitude, longitude: coordinate1.longitude) - let location2 = CLLocation(latitude: coordinate2.latitude, longitude: coordinate2.longitude) - MSLogger.make(category: .navigateMap).log("이동한 거리: \(location1.distance(from: location2))") - return 5 <= location1.distance(from: location2) && location1.distance(from: location2) <= 50 - } +// private func isDistanceOver5AndUnder50(coordinate1: CLLocationCoordinate2D, +// coordinate2: CLLocationCoordinate2D) -> Bool { +// let location1 = CLLocation(latitude: coordinate1.latitude, longitude: coordinate1.longitude) +// let location2 = CLLocation(latitude: coordinate2.latitude, longitude: coordinate2.longitude) +// MSLogger.make(category: .navigateMap).log("이동한 거리: \(location1.distance(from: location2))") +// return 5 <= location1.distance(from: location2) && location1.distance(from: location2) <= 50 +// } } diff --git a/iOS/Features/Home/Sources/NavigateMap/Presentation/RecordJourney/RecordJourneyViewModel.swift b/iOS/Features/Home/Sources/NavigateMap/Presentation/RecordJourney/RecordJourneyViewModel.swift index 1d4e0f7..aebecce 100644 --- a/iOS/Features/Home/Sources/NavigateMap/Presentation/RecordJourney/RecordJourneyViewModel.swift +++ b/iOS/Features/Home/Sources/NavigateMap/Presentation/RecordJourney/RecordJourneyViewModel.swift @@ -20,6 +20,7 @@ public final class RecordJourneyViewModel: MapViewModel { case locationDidUpdated(CLLocationCoordinate2D) case locationsShouldRecorded([CLLocationCoordinate2D]) case recordingDidCancelled + case fiveLocationsDidRecorded(CLLocationCoordinate2D) } public struct State { @@ -27,6 +28,8 @@ public final class RecordJourneyViewModel: MapViewModel { public var previousCoordinate = CurrentValueSubject(nil) public var currentCoordinate = CurrentValueSubject(nil) public var recordingJourney: CurrentValueSubject + public var recordedCoordinates = CurrentValueSubject<[CLLocationCoordinate2D], Never>([]) + public var filteredCoordinate = CurrentValueSubject(nil) } // MARK: - Properties @@ -67,6 +70,7 @@ public final class RecordJourneyViewModel: MapViewModel { switch result { case .success(let recordingJourney): self.state.recordingJourney.send(recordingJourney) + self.state.filteredCoordinate.send(nil) case .failure(let error): MSLogger.make(category: .home).error("\(error)") } @@ -86,7 +90,67 @@ public final class RecordJourneyViewModel: MapViewModel { MSLogger.make(category: .home).error("\(error)") } } + case .fiveLocationsDidRecorded(let coordinate): + self.filterLongestCoordinate(coordinate: coordinate) } } } + +private extension RecordJourneyViewModel { + + func calculateDistance(from coordinate1: CLLocationCoordinate2D, + to coordinate2: CLLocationCoordinate2D) -> CLLocationDistance { + let location1 = CLLocation(latitude: coordinate1.latitude, + longitude: coordinate1.longitude) + let location2 = CLLocation(latitude: coordinate2.latitude, + longitude: coordinate2.longitude) + return location1.distance(from: location2) + } + + func filterLongestCoordinate(coordinate: CLLocationCoordinate2D) { + + var recordedCoords = self.state.recordedCoordinates.value + recordedCoords.append(coordinate) + self.state.recordedCoordinates.send(recordedCoords) + + if self.state.recordedCoordinates.value.count >= 5 { + let initialCoordinate = recordedCoords.reduce(CLLocationCoordinate2D(latitude: 0, + longitude: 0)) { result, coordinate in + return CLLocationCoordinate2D(latitude: result.latitude + coordinate.latitude, + longitude: result.longitude + coordinate.longitude) + } + let initialLat = initialCoordinate.latitude + let initialLong = initialCoordinate.longitude + let averageCoordinate = CLLocationCoordinate2D(latitude: initialLat / Double(recordedCoords.count), + longitude: initialLong / Double(recordedCoords.count)) + + // 가장 먼 거리에 있는 Coordinate의 index값을 찾음. + guard let indexToRemove = recordedCoords.enumerated().max(by: { + let distance1 = calculateDistance(from: $0.element, + to: averageCoordinate) + let distance2 = calculateDistance(from: $1.element, + to: averageCoordinate) + return distance1 < distance2 + })?.offset else { + return + } + + var fourCoordinates = recordedCoords + fourCoordinates.remove(at: indexToRemove) + + let finalCoordinate = fourCoordinates.reduce(CLLocationCoordinate2D(latitude: 0, + longitude: 0)) { result, coordinate in + return CLLocationCoordinate2D(latitude: result.latitude + coordinate.latitude, + longitude: result.longitude + coordinate.longitude) + } + let finalLat = finalCoordinate.latitude + let finalLong = finalCoordinate.longitude + let finalAverage = CLLocationCoordinate2D(latitude: finalLat / Double(fourCoordinates.count), + longitude: finalLong / Double(fourCoordinates.count)) + + self.state.filteredCoordinate.send(finalAverage) + self.state.recordedCoordinates.send([]) + } + } +} From 4a7bbbaf4be5b624fa98935611378615a72bdc59 Mon Sep 17 00:00:00 2001 From: Xerath Date: Wed, 13 Dec 2023 09:37:06 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=90=9B=20=EC=83=88=EB=A1=9C=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20=EC=8B=9C=20=EA=B8=B0=EC=A1=B4=EC=9D=98=20?= =?UTF-8?q?=EC=97=AC=EC=A0=95=EC=9D=B4=20=EC=A7=80=EB=8F=84=EC=97=90=20?= =?UTF-8?q?=EB=82=A8=EC=95=84=EC=9E=88=EB=8A=94=20=EC=9D=B4=EC=8A=88=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/Sources/Home/Presentation/HomeViewController.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/iOS/Features/Home/Sources/Home/Presentation/HomeViewController.swift b/iOS/Features/Home/Sources/Home/Presentation/HomeViewController.swift index 853e4e7..b727b90 100644 --- a/iOS/Features/Home/Sources/Home/Presentation/HomeViewController.swift +++ b/iOS/Features/Home/Sources/Home/Presentation/HomeViewController.swift @@ -223,7 +223,8 @@ extension HomeViewController: RecordJourneyButtonViewDelegate { let refreshButtonAction = UIAction { [weak self] _ in guard let coordinates = self?.contentViewController.visibleCoordinates else { return } - + self?.contentViewController.clearAnnotations() + self?.contentViewController.clearOverlays() self?.viewModel.trigger(.refreshButtonDidTap(visibleCoordinates: coordinates)) } self.refreshButton.addAction(refreshButtonAction, for: .touchUpInside) From 71d4b9d4caa5cd2628f08a1839ae636b70317dde Mon Sep 17 00:00:00 2001 From: Xerath Date: Wed, 13 Dec 2023 09:37:23 +0900 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=8E=A8=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EB=9D=BC=EC=9D=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/MusicSpot/MusicSpot/SceneDelegate.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/iOS/MusicSpot/MusicSpot/SceneDelegate.swift b/iOS/MusicSpot/MusicSpot/SceneDelegate.swift index 4956c04..e94fd71 100644 --- a/iOS/MusicSpot/MusicSpot/SceneDelegate.swift +++ b/iOS/MusicSpot/MusicSpot/SceneDelegate.swift @@ -84,7 +84,6 @@ private extension SceneDelegate { // self.isFirstLaunch = true // self.recordingJourneyID = nil // self.isRecording = false -// // do { // try self.keychain.deleteAll() // } catch { From ab477a084f0320d4173cfdc32dd56b84507759e1 Mon Sep 17 00:00:00 2001 From: Xerath Date: Wed, 13 Dec 2023 11:48:09 +0900 Subject: [PATCH 4/6] =?UTF-8?q?=E2=9C=A8=20=EC=8B=A4=EC=8B=9C=EA=B0=84=20P?= =?UTF-8?q?olyline=20=ED=95=84=ED=84=B0=EB=A7=81=20=EC=8B=9C=205m=20?= =?UTF-8?q?=EC=9D=B4=ED=95=98=EC=9D=98=20=EA=B0=92=20=ED=95=84=ED=84=B0?= =?UTF-8?q?=EB=A7=81=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Common/MapViewController.swift | 3 ++- .../RecordJourney/RecordJourneyViewModel.swift | 12 +++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/iOS/Features/Home/Sources/NavigateMap/Presentation/Common/MapViewController.swift b/iOS/Features/Home/Sources/NavigateMap/Presentation/Common/MapViewController.swift index 89a4ce3..a79026a 100644 --- a/iOS/Features/Home/Sources/NavigateMap/Presentation/Common/MapViewController.swift +++ b/iOS/Features/Home/Sources/NavigateMap/Presentation/Common/MapViewController.swift @@ -323,8 +323,9 @@ extension MapViewController: CLLocationManagerDelegate { let coordinate2D = CLLocationCoordinate2D(latitude: newCurrentLocation.coordinate.latitude, longitude: newCurrentLocation.coordinate.longitude) recordJourneyViewModel.trigger(.fiveLocationsDidRecorded(coordinate2D)) + + // filtering된 위치 정보가 있을 경우(5개의 위치 데이터가 들어와 필터링이 완료되었을 경우)에만 아래 두 trigger 실행 가능 guard let filteredCoordinate2D = recordJourneyViewModel.state.filteredCoordinate.value else { return } - dump(filteredCoordinate2D) recordJourneyViewModel.trigger(.locationDidUpdated(filteredCoordinate2D)) recordJourneyViewModel.trigger(.locationsShouldRecorded([filteredCoordinate2D])) } diff --git a/iOS/Features/Home/Sources/NavigateMap/Presentation/RecordJourney/RecordJourneyViewModel.swift b/iOS/Features/Home/Sources/NavigateMap/Presentation/RecordJourney/RecordJourneyViewModel.swift index aebecce..fb52e19 100644 --- a/iOS/Features/Home/Sources/NavigateMap/Presentation/RecordJourney/RecordJourneyViewModel.swift +++ b/iOS/Features/Home/Sources/NavigateMap/Presentation/RecordJourney/RecordJourneyViewModel.swift @@ -57,10 +57,14 @@ public final class RecordJourneyViewModel: MapViewModel { #if DEBUG MSLogger.make(category: .home).debug("View Did load.") #endif + + /// 이전 currentCoordinate를 previousCoordinate에, 저장할 현재 위치를 currentCoordinate에 update case .locationDidUpdated(let coordinate): let previousCoordinate = self.state.currentCoordinate.value self.state.previousCoordinate.send(previousCoordinate) self.state.currentCoordinate.send(coordinate) + + /// 저장하고 자 하는 위치 데이터를 서버에 전송 case .locationsShouldRecorded(let coordinates): Task { let recordingJourney = self.state.recordingJourney.value @@ -75,6 +79,7 @@ public final class RecordJourneyViewModel: MapViewModel { MSLogger.make(category: .home).error("\(error)") } } + /// 기록 중에 여정 기록을 취소 case .recordingDidCancelled: Task { guard let userID = self.userRepository.fetchUUID() else { return } @@ -90,6 +95,7 @@ public final class RecordJourneyViewModel: MapViewModel { MSLogger.make(category: .home).error("\(error)") } } + /// 업데이트되는 위치 정보를 받아와 5개가 될 경우 평균값 필터링 후 저장하는 로직 case .fiveLocationsDidRecorded(let coordinate): self.filterLongestCoordinate(coordinate: coordinate) } @@ -109,7 +115,11 @@ private extension RecordJourneyViewModel { } func filterLongestCoordinate(coordinate: CLLocationCoordinate2D) { - + if let previousCoordinate = self.state.previousCoordinate.value { + if calculateDistance(from: previousCoordinate, to: coordinate) <= 5 { + return + } + } var recordedCoords = self.state.recordedCoordinates.value recordedCoords.append(coordinate) self.state.recordedCoordinates.send(recordedCoords) From 331f0631097097027babc5314deecc14a38153c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Wed, 13 Dec 2023 19:12:34 +0900 Subject: [PATCH 5/6] =?UTF-8?q?:bug:=20Home=20=EC=9E=90=EB=8F=99=20Refresh?= =?UTF-8?q?=EA=B0=80=20=EC=97=AC=EC=A0=95=20=EA=B8=B0=EB=A1=9D=EC=A4=91?= =?UTF-8?q?=EC=9D=BC=EB=95=8C=EB=A7=8C=20=EB=8F=99=EC=9E=91=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/Presentation/HomeViewController.swift | 4 +++- .../Sources/MSData/Repository/UserRepository.swift | 11 +++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/iOS/Features/Home/Sources/Home/Presentation/HomeViewController.swift b/iOS/Features/Home/Sources/Home/Presentation/HomeViewController.swift index b727b90..b355783 100644 --- a/iOS/Features/Home/Sources/Home/Presentation/HomeViewController.swift +++ b/iOS/Features/Home/Sources/Home/Presentation/HomeViewController.swift @@ -124,7 +124,9 @@ public final class HomeViewController: HomeBottomSheetViewController { super.viewDidAppear(animated) // 화면 시작 시 새로고침 버튼 기능 한번 실행 - self.refreshButton.sendActions(for: .touchUpInside) + if !self.viewModel.state.isRecording.value { + self.refreshButton.sendActions(for: .touchUpInside) + } } // MARK: - Combine Binding diff --git a/iOS/MSData/Sources/MSData/Repository/UserRepository.swift b/iOS/MSData/Sources/MSData/Repository/UserRepository.swift index 24954a7..308efb1 100644 --- a/iOS/MSData/Sources/MSData/Repository/UserRepository.swift +++ b/iOS/MSData/Sources/MSData/Repository/UserRepository.swift @@ -57,11 +57,15 @@ public struct UserRepositoryImplementation: UserRepository { let result = await self.networking.request(UserResponseDTO.self, router: router) switch result { case .success(let userResponse): + #if DEBUG + MSLogger.make(category: .network).debug("서버에 새로운 유저를 생성했습니다.") + #endif if let storedUserID = try? self.storeUUID(userID) { return .success(storedUserID) + } else { + MSLogger.make(category: .keychain).warning("Keychain에 새로운 유저 정보를 저장하지 못했습니다.") + return .success(userResponse.userID) } - MSLogger.make(category: .keychain).warning("서버에 새로운 유저를 생성했지만, Keychain에 저장하지 못했습니다.") - return .success(userResponse.userID) case .failure(let error): return .failure(error) } @@ -73,6 +77,9 @@ public struct UserRepositoryImplementation: UserRepository { do { try self.keychain.set(value: userID, account: account) + #if DEBUG + MSLogger.make(category: .keychain).debug("Keychain에 서버 정보를 저장했습니다.") + #endif return userID } catch { throw MSKeychainStorage.KeychainError.creationError From fabaf9a7206b6575dd8cafa3213e74308843d61e Mon Sep 17 00:00:00 2001 From: Xerath Date: Tue, 9 Jan 2024 05:25:38 +0900 Subject: [PATCH 6/6] =?UTF-8?q?=E2=9C=A8=20=EC=A4=91=EC=9E=A5=EA=B0=92?= =?UTF-8?q?=EC=9D=84=20=ED=99=9C=EC=9A=A9=ED=95=9C=20=EC=9C=84=EC=B9=98=20?= =?UTF-8?q?=EB=B3=B4=EC=A0=95=20=ED=95=84=ED=84=B0=EB=A7=81=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/MapViewController.swift | 16 +- .../RecordJourneyViewModel.swift | 156 +++++++++++++----- 2 files changed, 123 insertions(+), 49 deletions(-) diff --git a/iOS/Features/Home/Sources/NavigateMap/Presentation/Common/MapViewController.swift b/iOS/Features/Home/Sources/NavigateMap/Presentation/Common/MapViewController.swift index a79026a..33a74da 100644 --- a/iOS/Features/Home/Sources/NavigateMap/Presentation/Common/MapViewController.swift +++ b/iOS/Features/Home/Sources/NavigateMap/Presentation/Common/MapViewController.swift @@ -174,6 +174,15 @@ public final class MapViewController: UIViewController { self?.drawPolylineToMap(using: points) } .store(in: &self.cancellables) + + viewModel.state.filteredCoordinate + .receive(on: DispatchQueue.main) + .sink { coordinate in + guard let filteredCoordinate2D = coordinate else { return } + viewModel.trigger(.locationDidUpdated(filteredCoordinate2D)) + viewModel.trigger(.locationsShouldRecorded([filteredCoordinate2D])) + } + .store(in: &self.cancellables) } // MARK: - Functions: Annotation @@ -322,12 +331,7 @@ extension MapViewController: CLLocationManagerDelegate { let coordinate2D = CLLocationCoordinate2D(latitude: newCurrentLocation.coordinate.latitude, longitude: newCurrentLocation.coordinate.longitude) - recordJourneyViewModel.trigger(.fiveLocationsDidRecorded(coordinate2D)) - - // filtering된 위치 정보가 있을 경우(5개의 위치 데이터가 들어와 필터링이 완료되었을 경우)에만 아래 두 trigger 실행 가능 - guard let filteredCoordinate2D = recordJourneyViewModel.state.filteredCoordinate.value else { return } - recordJourneyViewModel.trigger(.locationDidUpdated(filteredCoordinate2D)) - recordJourneyViewModel.trigger(.locationsShouldRecorded([filteredCoordinate2D])) + recordJourneyViewModel.trigger(.tenLocationsDidRecorded(coordinate2D)) } private func handleAuthorizationChange(_ manager: CLLocationManager) { diff --git a/iOS/Features/Home/Sources/NavigateMap/Presentation/RecordJourney/RecordJourneyViewModel.swift b/iOS/Features/Home/Sources/NavigateMap/Presentation/RecordJourney/RecordJourneyViewModel.swift index fb52e19..4d75ee0 100644 --- a/iOS/Features/Home/Sources/NavigateMap/Presentation/RecordJourney/RecordJourneyViewModel.swift +++ b/iOS/Features/Home/Sources/NavigateMap/Presentation/RecordJourney/RecordJourneyViewModel.swift @@ -20,7 +20,7 @@ public final class RecordJourneyViewModel: MapViewModel { case locationDidUpdated(CLLocationCoordinate2D) case locationsShouldRecorded([CLLocationCoordinate2D]) case recordingDidCancelled - case fiveLocationsDidRecorded(CLLocationCoordinate2D) + case tenLocationsDidRecorded(CLLocationCoordinate2D) } public struct State { @@ -29,7 +29,7 @@ public final class RecordJourneyViewModel: MapViewModel { public var currentCoordinate = CurrentValueSubject(nil) public var recordingJourney: CurrentValueSubject public var recordedCoordinates = CurrentValueSubject<[CLLocationCoordinate2D], Never>([]) - public var filteredCoordinate = CurrentValueSubject(nil) + public var filteredCoordinate = PassthroughSubject() } // MARK: - Properties @@ -64,11 +64,12 @@ public final class RecordJourneyViewModel: MapViewModel { self.state.previousCoordinate.send(previousCoordinate) self.state.currentCoordinate.send(coordinate) - /// 저장하고 자 하는 위치 데이터를 서버에 전송 + /// 저장하고자 하는 위치 데이터를 서버에 전송 case .locationsShouldRecorded(let coordinates): Task { let recordingJourney = self.state.recordingJourney.value - let coordinates = coordinates.map { Coordinate(latitude: $0.latitude, longitude: $0.longitude) } + let coordinates = coordinates.map { Coordinate(latitude: $0.latitude, + longitude: $0.longitude) } let result = await self.journeyRepository.recordJourney(journeyID: recordingJourney.id, at: coordinates) switch result { @@ -85,7 +86,8 @@ public final class RecordJourneyViewModel: MapViewModel { guard let userID = self.userRepository.fetchUUID() else { return } let recordingJourney = self.state.recordingJourney.value - let result = await self.journeyRepository.deleteJourney(recordingJourney, userID: userID) + let result = await self.journeyRepository.deleteJourney(recordingJourney, + userID: userID) switch result { case .success(let cancelledJourney): #if DEBUG @@ -95,9 +97,9 @@ public final class RecordJourneyViewModel: MapViewModel { MSLogger.make(category: .home).error("\(error)") } } - /// 업데이트되는 위치 정보를 받아와 5개가 될 경우 평균값 필터링 후 저장하는 로직 - case .fiveLocationsDidRecorded(let coordinate): - self.filterLongestCoordinate(coordinate: coordinate) + /// 업데이트되는 위치 정보를 받아와 10개가 될 경우 필터링 후 저장하는 로직 + case .tenLocationsDidRecorded(let coordinate): + self.filterCoordinate(coordinate: coordinate) } } @@ -114,50 +116,118 @@ private extension RecordJourneyViewModel { return location1.distance(from: location2) } - func filterLongestCoordinate(coordinate: CLLocationCoordinate2D) { - if let previousCoordinate = self.state.previousCoordinate.value { - if calculateDistance(from: previousCoordinate, to: coordinate) <= 5 { - return + /// 배열의 중앙값을 도출 + func findMedianFrom(array: [CLLocationDegrees]) -> CLLocationDegrees { + let sortedArray = array.sorted() + let count = sortedArray.count + + if count % 2 == 0 { + // Array has even number of elements + let middle1 = sortedArray[count / 2 - 1] + let middle2 = sortedArray[count / 2] + return (middle1 + middle2) / 2.0 + } else { + // Array has odd number of elements + return sortedArray[count / 2] + } + } + + /// 위도 배열, 경도 배열로부터 중앙값을 도출 + func medianCoordinate(recordedCoordinates: [CLLocationCoordinate2D]) -> CLLocationCoordinate2D { + + // 10개의 위도 값 배열 생성 + let latitudes = recordedCoordinates.map { coord in + coord.latitude + }.sorted() + + // 10개의 경도 값 배열 생성 + let longitudes = recordedCoordinates.map { coord in + coord.longitude + }.sorted() + + return CLLocationCoordinate2D(latitude: findMedianFrom(array: latitudes), + longitude: findMedianFrom(array: longitudes)) + } + + /// 위도 배열, 경도 배열로부터 평균값을 도출 + func averageCoordinate(recordedCoordinates: [CLLocationCoordinate2D]) -> CLLocationCoordinate2D { + let recordedLength = recordedCoordinates.count + let initialCoordinate = recordedCoordinates.reduce(CLLocationCoordinate2D(latitude: 0, + longitude: 0)) { result, coordinate in + return CLLocationCoordinate2D(latitude: result.latitude + coordinate.latitude, + longitude: result.longitude + coordinate.longitude) + } + let initialLat = initialCoordinate.latitude + let initialLong = initialCoordinate.longitude + return CLLocationCoordinate2D(latitude: initialLat / Double(recordedLength), + longitude: initialLong / Double(recordedLength)) + } + + /// 두 위치의 거리를 구하기 + func distanceWith(_ coordinate1: CLLocationCoordinate2D, + _ coordinate2: CLLocationCoordinate2D) -> Double { + let location1 = CLLocation(latitude: coordinate1.latitude, + longitude: coordinate1.longitude) + let location2 = CLLocation(latitude: coordinate2.latitude, + longitude: coordinate2.longitude) + + return location1.distance(from: location2) + } + + /// 현재 location들 중 평균으로부터 가장 먼 지점을 제거 + func deleteFarLocation(recordedCoordinates: [CLLocationCoordinate2D], + average: CLLocationCoordinate2D) -> [CLLocationCoordinate2D] { + var coordinates = recordedCoordinates + var maxDistance = 0.0 + var maxDistanceIndex = -1 + for (index, coordinate) in recordedCoordinates.enumerated() { + let location1 = CLLocation(latitude: coordinate.latitude, + longitude: coordinate.longitude) + let location2 = CLLocation(latitude: average.latitude, + longitude: average.longitude) + let distance = location1.distance(from: location2) + if distance > maxDistance { + maxDistance = distance + maxDistanceIndex = index } } + coordinates.remove(at: maxDistanceIndex) + return coordinates + + } + + func filterCoordinate(coordinate: CLLocationCoordinate2D) { + if let previousCoordinate = self.state.previousCoordinate.value, + calculateDistance(from: previousCoordinate, + to: coordinate) <= 5 { + return + } var recordedCoords = self.state.recordedCoordinates.value recordedCoords.append(coordinate) self.state.recordedCoordinates.send(recordedCoords) - if self.state.recordedCoordinates.value.count >= 5 { - let initialCoordinate = recordedCoords.reduce(CLLocationCoordinate2D(latitude: 0, - longitude: 0)) { result, coordinate in - return CLLocationCoordinate2D(latitude: result.latitude + coordinate.latitude, - longitude: result.longitude + coordinate.longitude) - } - let initialLat = initialCoordinate.latitude - let initialLong = initialCoordinate.longitude - let averageCoordinate = CLLocationCoordinate2D(latitude: initialLat / Double(recordedCoords.count), - longitude: initialLong / Double(recordedCoords.count)) - - // 가장 먼 거리에 있는 Coordinate의 index값을 찾음. - guard let indexToRemove = recordedCoords.enumerated().max(by: { - let distance1 = calculateDistance(from: $0.element, - to: averageCoordinate) - let distance2 = calculateDistance(from: $1.element, - to: averageCoordinate) - return distance1 < distance2 - })?.offset else { - return - } + if self.state.recordedCoordinates.value.count >= 10 { - var fourCoordinates = recordedCoords - fourCoordinates.remove(at: indexToRemove) + var finalAverage = CLLocationCoordinate2D() - let finalCoordinate = fourCoordinates.reduce(CLLocationCoordinate2D(latitude: 0, - longitude: 0)) { result, coordinate in - return CLLocationCoordinate2D(latitude: result.latitude + coordinate.latitude, - longitude: result.longitude + coordinate.longitude) + while true { + let average = averageCoordinate(recordedCoordinates: recordedCoords) + let median = medianCoordinate(recordedCoordinates: recordedCoords) + + if distanceWith(average, + median) <= 10 { + finalAverage = average + break + } else { + recordedCoords = deleteFarLocation(recordedCoordinates: recordedCoords, + average: average) + } + + if recordedCoords.count == 1 { + finalAverage = recordedCoords[0] + break + } } - let finalLat = finalCoordinate.latitude - let finalLong = finalCoordinate.longitude - let finalAverage = CLLocationCoordinate2D(latitude: finalLat / Double(fourCoordinates.count), - longitude: finalLong / Double(fourCoordinates.count)) self.state.filteredCoordinate.send(finalAverage) self.state.recordedCoordinates.send([])