RouteGuideProvider.swift 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. /*
  2. * Copyright 2019, gRPC Authors All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import Foundation
  17. import GRPC
  18. import NIOConcurrencyHelpers
  19. import NIOCore
  20. import RouteGuideModel
  21. #if compiler(>=5.6)
  22. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  23. internal final class RouteGuideProvider: Routeguide_RouteGuideAsyncProvider {
  24. private let features: [Routeguide_Feature]
  25. private let notes: Notes
  26. internal init(features: [Routeguide_Feature]) {
  27. self.features = features
  28. self.notes = Notes()
  29. }
  30. internal func getFeature(
  31. request point: Routeguide_Point,
  32. context: GRPCAsyncServerCallContext
  33. ) async throws -> Routeguide_Feature {
  34. return self.lookupFeature(at: point) ?? .unnamedFeature(at: point)
  35. }
  36. internal func listFeatures(
  37. request: Routeguide_Rectangle,
  38. responseStream: GRPCAsyncResponseStreamWriter<Routeguide_Feature>,
  39. context: GRPCAsyncServerCallContext
  40. ) async throws {
  41. let longitudeRange = request.lo.longitude ... request.hi.longitude
  42. let latitudeRange = request.lo.latitude ... request.hi.latitude
  43. for feature in self.features where !feature.name.isEmpty {
  44. if feature.location.isWithin(latitude: latitudeRange, longitude: longitudeRange) {
  45. try await responseStream.send(feature)
  46. }
  47. }
  48. }
  49. internal func recordRoute(
  50. requestStream points: GRPCAsyncRequestStream<Routeguide_Point>,
  51. context: GRPCAsyncServerCallContext
  52. ) async throws -> Routeguide_RouteSummary {
  53. var pointCount: Int32 = 0
  54. var featureCount: Int32 = 0
  55. var distance = 0.0
  56. var previousPoint: Routeguide_Point?
  57. let startTimeNanos = DispatchTime.now().uptimeNanoseconds
  58. for try await point in points {
  59. pointCount += 1
  60. if let feature = self.lookupFeature(at: point), !feature.name.isEmpty {
  61. featureCount += 1
  62. }
  63. if let previous = previousPoint {
  64. distance += previous.distance(to: point)
  65. }
  66. previousPoint = point
  67. }
  68. let durationInNanos = DispatchTime.now().uptimeNanoseconds - startTimeNanos
  69. let durationInSeconds = Double(durationInNanos) / 1e9
  70. return .with {
  71. $0.pointCount = pointCount
  72. $0.featureCount = featureCount
  73. $0.elapsedTime = Int32(durationInSeconds)
  74. $0.distance = Int32(distance)
  75. }
  76. }
  77. internal func routeChat(
  78. requestStream: GRPCAsyncRequestStream<Routeguide_RouteNote>,
  79. responseStream: GRPCAsyncResponseStreamWriter<Routeguide_RouteNote>,
  80. context: GRPCAsyncServerCallContext
  81. ) async throws {
  82. for try await note in requestStream {
  83. let existingNotes = await self.notes.addNote(note, to: note.location)
  84. // Respond with all existing notes.
  85. for existingNote in existingNotes {
  86. try await responseStream.send(existingNote)
  87. }
  88. }
  89. }
  90. /// Returns a feature at the given location or an unnamed feature if none exist at that location.
  91. private func lookupFeature(at location: Routeguide_Point) -> Routeguide_Feature? {
  92. return self.features.first(where: {
  93. $0.location.latitude == location.latitude && $0.location.longitude == location.longitude
  94. })
  95. }
  96. }
  97. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  98. internal final actor Notes {
  99. private var recordedNotes: [Routeguide_Point: [Routeguide_RouteNote]]
  100. internal init() {
  101. self.recordedNotes = [:]
  102. }
  103. /// Record a note at the given location and return the all notes which were previously recorded
  104. /// at the location.
  105. internal func addNote(
  106. _ note: Routeguide_RouteNote,
  107. to location: Routeguide_Point
  108. ) -> ArraySlice<Routeguide_RouteNote> {
  109. self.recordedNotes[location, default: []].append(note)
  110. return self.recordedNotes[location]!.dropLast(1)
  111. }
  112. }
  113. #endif // compiler(>=5.6)
  114. private func degreesToRadians(_ degrees: Double) -> Double {
  115. return degrees * .pi / 180.0
  116. }
  117. extension Routeguide_Point {
  118. fileprivate func distance(to other: Routeguide_Point) -> Double {
  119. // Radius of Earth in meters
  120. let radius = 6_371_000.0
  121. // Points are in the E7 representation (degrees multiplied by 10**7 and rounded to the nearest
  122. // integer). See also `Routeguide_Point`.
  123. let coordinateFactor = 1.0e7
  124. let lat1 = degreesToRadians(Double(self.latitude) / coordinateFactor)
  125. let lat2 = degreesToRadians(Double(other.latitude) / coordinateFactor)
  126. let lon1 = degreesToRadians(Double(self.longitude) / coordinateFactor)
  127. let lon2 = degreesToRadians(Double(other.longitude) / coordinateFactor)
  128. let deltaLat = lat2 - lat1
  129. let deltaLon = lon2 - lon1
  130. let a = sin(deltaLat / 2) * sin(deltaLat / 2)
  131. + cos(lat1) * cos(lat2) * sin(deltaLon / 2) * sin(deltaLon / 2)
  132. let c = 2 * atan2(sqrt(a), sqrt(1 - a))
  133. return radius * c
  134. }
  135. func isWithin<Range: RangeExpression>(
  136. latitude: Range,
  137. longitude: Range
  138. ) -> Bool where Range.Bound == Int32 {
  139. return latitude.contains(self.latitude) && longitude.contains(self.longitude)
  140. }
  141. }
  142. extension Routeguide_Feature {
  143. static func unnamedFeature(at location: Routeguide_Point) -> Routeguide_Feature {
  144. return .with {
  145. $0.name = ""
  146. $0.location = location
  147. }
  148. }
  149. }