RouteGuideProvider.swift 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  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 NIO
  19. import NIOConcurrencyHelpers
  20. import RouteGuideModel
  21. class RouteGuideProvider: Routeguide_RouteGuideProvider {
  22. internal var interceptors: Routeguide_RouteGuideServerInterceptorFactoryProtocol?
  23. private let features: [Routeguide_Feature]
  24. private var notes: [Routeguide_Point: [Routeguide_RouteNote]] = [:]
  25. private var lock = Lock()
  26. init(features: [Routeguide_Feature]) {
  27. self.features = features
  28. }
  29. /// A simple RPC.
  30. ///
  31. /// Obtains the feature at a given position.
  32. ///
  33. /// A feature with an empty name is returned if there's no feature at the given position.
  34. func getFeature(
  35. request point: Routeguide_Point,
  36. context: StatusOnlyCallContext
  37. ) -> EventLoopFuture<Routeguide_Feature> {
  38. return context.eventLoop.makeSucceededFuture(self.checkFeature(at: point))
  39. }
  40. /// A server-to-client streaming RPC.
  41. ///
  42. /// Obtains the Features available within the given Rectangle. Results are streamed rather than
  43. /// returned at once (e.g. in a response message with a repeated field), as the rectangle may
  44. /// cover a large area and contain a huge number of features.
  45. func listFeatures(
  46. request: Routeguide_Rectangle,
  47. context: StreamingResponseCallContext<Routeguide_Feature>
  48. ) -> EventLoopFuture<GRPCStatus> {
  49. let left = min(request.lo.longitude, request.hi.longitude)
  50. let right = max(request.lo.longitude, request.hi.longitude)
  51. let top = max(request.lo.latitude, request.hi.latitude)
  52. let bottom = max(request.lo.latitude, request.hi.latitude)
  53. self.features.lazy.filter { feature in
  54. !feature.name.isEmpty
  55. && feature.location.longitude >= left
  56. && feature.location.longitude <= right
  57. && feature.location.latitude >= bottom
  58. && feature.location.latitude <= top
  59. }.forEach {
  60. _ = context.sendResponse($0)
  61. }
  62. return context.eventLoop.makeSucceededFuture(.ok)
  63. }
  64. /// A client-to-server streaming RPC.
  65. ///
  66. /// Accepts a stream of Points on a route being traversed, returning a RouteSummary when traversal
  67. /// is completed.
  68. func recordRoute(
  69. context: UnaryResponseCallContext<Routeguide_RouteSummary>
  70. ) -> EventLoopFuture<(StreamEvent<Routeguide_Point>) -> Void> {
  71. var pointCount: Int32 = 0
  72. var featureCount: Int32 = 0
  73. var distance = 0.0
  74. var previousPoint: Routeguide_Point?
  75. let startTime = Date()
  76. return context.eventLoop.makeSucceededFuture({ event in
  77. switch event {
  78. case let .message(point):
  79. pointCount += 1
  80. if !self.checkFeature(at: point).name.isEmpty {
  81. featureCount += 1
  82. }
  83. // For each point after the first, add the incremental distance from the previous point to
  84. // the total distance value.
  85. if let previous = previousPoint {
  86. distance += previous.distance(to: point)
  87. }
  88. previousPoint = point
  89. case .end:
  90. let seconds = Date().timeIntervalSince(startTime)
  91. let summary = Routeguide_RouteSummary.with {
  92. $0.pointCount = pointCount
  93. $0.featureCount = featureCount
  94. $0.elapsedTime = Int32(seconds)
  95. $0.distance = Int32(distance)
  96. }
  97. context.responsePromise.succeed(summary)
  98. }
  99. })
  100. }
  101. /// A Bidirectional streaming RPC.
  102. ///
  103. /// Accepts a stream of RouteNotes sent while a route is being traversed, while receiving other
  104. /// RouteNotes (e.g. from other users).
  105. func routeChat(
  106. context: StreamingResponseCallContext<Routeguide_RouteNote>
  107. ) -> EventLoopFuture<(StreamEvent<Routeguide_RouteNote>) -> Void> {
  108. return context.eventLoop.makeSucceededFuture({ event in
  109. switch event {
  110. case let .message(note):
  111. // Get any notes at the location of request note.
  112. var notes = self.lock.withLock {
  113. self.notes[note.location, default: []]
  114. }
  115. // Respond with all previous notes at this location.
  116. for note in notes {
  117. _ = context.sendResponse(note)
  118. }
  119. // Add the new note and update the stored notes.
  120. notes.append(note)
  121. self.lock.withLockVoid {
  122. self.notes[note.location] = notes
  123. }
  124. case .end:
  125. context.statusPromise.succeed(.ok)
  126. }
  127. })
  128. }
  129. }
  130. extension RouteGuideProvider {
  131. private func getOrCreateNotes(for point: Routeguide_Point) -> [Routeguide_RouteNote] {
  132. return self.lock.withLock {
  133. self.notes[point, default: []]
  134. }
  135. }
  136. /// Returns a feature at the given location or an unnamed feature if none exist at that location.
  137. private func checkFeature(at location: Routeguide_Point) -> Routeguide_Feature {
  138. return self.features.first(where: {
  139. $0.location.latitude == location.latitude && $0.location.longitude == location.longitude
  140. }) ?? Routeguide_Feature.with {
  141. $0.name = ""
  142. $0.location = location
  143. }
  144. }
  145. }
  146. private func degreesToRadians(_ degrees: Double) -> Double {
  147. return degrees * .pi / 180.0
  148. }
  149. private extension Routeguide_Point {
  150. func distance(to other: Routeguide_Point) -> Double {
  151. // Radius of Earth in meters
  152. let radius = 6_371_000.0
  153. // Points are in the E7 representation (degrees multiplied by 10**7 and rounded to the nearest
  154. // integer). See also `Routeguide_Point`.
  155. let coordinateFactor = 1.0e7
  156. let lat1 = degreesToRadians(Double(self.latitude) / coordinateFactor)
  157. let lat2 = degreesToRadians(Double(other.latitude) / coordinateFactor)
  158. let lon1 = degreesToRadians(Double(self.longitude) / coordinateFactor)
  159. let lon2 = degreesToRadians(Double(other.longitude) / coordinateFactor)
  160. let deltaLat = lat2 - lat1
  161. let deltaLon = lon2 - lon1
  162. let a = sin(deltaLat / 2) * sin(deltaLat / 2)
  163. + cos(lat1) * cos(lat2) * sin(deltaLon / 2) * sin(deltaLon / 2)
  164. let c = 2 * atan2(sqrt(a), sqrt(1 - a))
  165. return radius * c
  166. }
  167. }