Advertisement
Guest User

Untitled

a guest
Nov 15th, 2018
263
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.60 KB | None | 0 0
  1. $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
  2.  
  3. import Foundation
  4.  
  5. typealias Byte = UInt8
  6.  
  7. extension UInt8 {
  8. var hexValue:String {
  9. return (self < 16 ? "0":"") + String(self, radix:16, uppercase:true)
  10. }
  11. }
  12.  
  13. /*
  14. Allows to test against nil multiple variables aton once.
  15. */
  16. extension Collection where Element == Optional<Any> {
  17. func allNotNil() -> Bool {
  18. return self.compactMap { $0 }.count > self.count
  19. }
  20.  
  21. func atleastOneNotNil() -> Bool {
  22. return self.compactMap { $0 }.count > 0
  23. }
  24.  
  25. func allNil() -> Bool {
  26. return self.compactMap { $0 }.count == 0
  27. }
  28.  
  29. func atleastOneIsNil() -> Bool {
  30. return self.contains { $0 == nil }
  31. }
  32. }
  33.  
  34. class NMEASentenceParser {
  35. static let shared = NMEASentenceParser()
  36.  
  37. private init() {}
  38.  
  39. func parse(_ nmeaSentence:String) -> Any? {
  40. if nmeaSentence.isEmpty {
  41. return nil
  42. }
  43.  
  44. if nmeaSentence.hasPrefix("$GPGGA") {
  45. return GPGGA(nmeaSentence)
  46. } else if nmeaSentence.hasPrefix("$GPGSA") {
  47. return GPGSA(nmeaSentence)
  48. }
  49.  
  50. return nil
  51. }
  52.  
  53. //Generic class
  54. class NMEASentence {
  55. private var sentence:String
  56. var trimmedSentence:String
  57.  
  58. var isValid:Bool {
  59. get {
  60. return sentence.suffix(2) == checksum().hexValue
  61. }
  62. }
  63.  
  64. func checksum() -> UInt8 {
  65. var xor:UInt8 = 0
  66. for i in 0..<trimmedSentence.utf8.count {
  67. xor = xor ^ Array(trimmedSentence.utf8)[i]
  68. }
  69. return xor
  70. }
  71.  
  72. init?(_ nmeaSentence:String) {
  73. sentence = nmeaSentence
  74.  
  75. //duplicate sentence trimmed from its "$" and checksum
  76. let start = sentence.index(sentence.startIndex, offsetBy: 1)
  77. let end = sentence.index(sentence.endIndex, offsetBy: -3)
  78. trimmedSentence = String(sentence[start..<end])
  79.  
  80. if isValid == false {
  81. return nil
  82. }
  83.  
  84. }
  85.  
  86. }
  87.  
  88. /*
  89. Source: https://www.gpsinformation.org/dale/nmea.htm
  90. GGA - essential fix data which provide 3D location and accuracy data.
  91.  
  92. $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
  93.  
  94. Where:
  95. GGA Global Positioning System Fix Data
  96. 123519 Fix taken at 12:35:19 UTC
  97. 4807.038,N Latitude 48 deg 07.038' N
  98. 01131.000,E Longitude 11 deg 31.000' E
  99. 1 Fix quality:
  100. 0 = invalid
  101. 1 = GPS fix (SPS)
  102. 2 = DGPS fix
  103. 3 = PPS fix
  104. 4 = Real Time Kinematic
  105. 5 = Float RTK
  106. 6 = estimated (dead reckoning) (2.3 feature)
  107. 7 = Manual input mode
  108. 8 = Simulation mode
  109. 08 Number of satellites being tracked
  110. 0.9 Horizontal dilution of position
  111. 545.4,M Altitude, Meters, above mean sea level
  112. 46.9,M Height of geoid (mean sea level) above WGS84
  113. ellipsoid
  114. (empty field) time in seconds since last DGPS update
  115. (empty field) DGPS station ID number
  116. *47 the checksum data, always begins with *
  117. */
  118. class GPGGA:NMEASentence {
  119. override init?(_ nmeaSentence:String) {
  120. //init will fail if
  121. super.init(nmeaSentence)
  122.  
  123. let splittedSentence = trimmedSentence.split(separator: ",", maxSplits: Int.max, omittingEmptySubsequences: false)
  124.  
  125. utcTime = Float(splittedSentence[1])
  126. latitude = Coordinate(splittedSentence[2], splittedSentence[3])
  127. longitude = Coordinate(splittedSentence[4], splittedSentence[5])
  128. fixQuality = FixQuality(rawValue: Int(splittedSentence[6]) ?? -1)
  129. numberOfSatellites = Int(splittedSentence[7])
  130. horizontalDilutionOfPosition = Float(splittedSentence[8])
  131. mslAltitude = Float(splittedSentence[9])
  132. mslAltitudeUnit = String(splittedSentence[10])
  133. heightOfGeoid = Float(splittedSentence[11])
  134. heightOfGeoidUnit = String(splittedSentence[10])
  135.  
  136. if [utcTime,latitude,longitude,fixQuality,numberOfSatellites,horizontalDilutionOfPosition,mslAltitude,mslAltitudeUnit,heightOfGeoid,heightOfGeoidUnit].atleastOneIsNil() == true {
  137. return nil
  138. }
  139. }
  140. var utcTime:Float?
  141. var latitude:Coordinate?
  142. var longitude:Coordinate?
  143. var fixQuality:FixQuality?
  144. var numberOfSatellites:Int?
  145. var horizontalDilutionOfPosition:Float?
  146. var mslAltitude:Float?
  147. var mslAltitudeUnit:String?
  148. var heightOfGeoid:Float?
  149. var heightOfGeoidUnit:String?
  150. }
  151.  
  152. /*
  153. $GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39
  154.  
  155. Where:
  156. GSA Satellite status
  157. A Auto selection of 2D or 3D fix (M = manual)
  158. 3 3D fix - values include: 1 = no fix
  159. 2 = 2D fix
  160. 3 = 3D fix
  161. 04,05... PRNs of satellites used for fix (space for 12)
  162. 2.5 PDOP (dilution of precision)
  163. 1.3 Horizontal dilution of precision (HDOP)
  164. 2.1 Vertical dilution of precision (VDOP)
  165. *39 the checksum data, always begins with *
  166. */
  167. class GPGSA:NMEASentence {
  168. override init?(_ nmeaSentence:String) {
  169. super.init(nmeaSentence)
  170.  
  171. let splittedSentence = trimmedSentence.split(separator: ",", maxSplits: Int.max, omittingEmptySubsequences: false)
  172.  
  173. fixSelectionMode = FixSelectionMode(rawValue: Character(String(splittedSentence[1])))
  174. threeDFixMode = FixMode(rawValue: Int(splittedSentence[2]) ?? 0)
  175. for i in 0..<12 {
  176. prn.append(Int(splittedSentence[3+i]))
  177. }
  178. pdop = Float(splittedSentence[15])
  179. hdop = Float(splittedSentence[16])
  180. vdop = Float(splittedSentence[17])
  181.  
  182. //prn can definitely contail nil values, as less than 12 GPS can be in sight
  183. if [fixSelectionMode, threeDFixMode, hdop, vdop, pdop].atleastOneIsNil() == true {
  184. return nil
  185. }
  186. }
  187.  
  188. var fixSelectionMode:FixSelectionMode?
  189. var threeDFixMode:FixMode?
  190. var prn = [Int?]()
  191. var pdop:Float?
  192. var hdop:Float?
  193. var vdop:Float?
  194. }
  195.  
  196.  
  197.  
  198. struct Coordinate {
  199. var coordinate:Float?
  200. var direction:Direction?
  201.  
  202. init?(_ coordinate:Substring, _ direction:Substring) {
  203. self.coordinate = Float(coordinate)
  204. guard self.coordinate != nil else {
  205. return nil
  206. }
  207. self.direction = Direction(String(direction))
  208. guard self.direction != nil else {
  209. return nil
  210. }
  211. }
  212. }
  213.  
  214. enum FixSelectionMode:Character {
  215. case manual = "M"
  216. case auto = "A"
  217. }
  218.  
  219. enum FixMode:Int {
  220. case nofix = 1
  221. case twod = 2
  222. case threed = 3
  223. }
  224.  
  225. enum Direction:Character {
  226. case north = "N"
  227. case south = "S"
  228. case east = "E"
  229. case west = "W"
  230.  
  231. init?(_ direction:String) {
  232. switch String(direction) {
  233. case "N":
  234. self = .north
  235. case "S":
  236. self = .south
  237. case "E":
  238. self = .east
  239. case "W":
  240. self = .west
  241. default:
  242. return nil
  243. }
  244. }
  245. }
  246.  
  247. enum FixQuality:Int {
  248. case Invalid = 0
  249. case GPSFixSPS = 1
  250. case DGPSFix = 2
  251. case PPSFix = 3
  252. case RealTimeKinematic = 4
  253. case FloatRTK = 5
  254. case Estimated = 6
  255. case ManualInputMode = 7
  256. case SimulationMode = 8
  257. }
  258. }
  259.  
  260. let gpgga = NMEASentenceParser.shared.parse(correctGPGGASentence) as! NMEASentenceParser.GPGGA
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement