@@ -107,7 +107,131 @@ extension CLLocationCoordinate2D {
107107// }
108108 }
109109
110- static func parse( coordinate: String ) -> CLLocationCoordinate2D ? {
110+ // splits the string into possibly two coordinates with all spaces removed
111+ // no further normalization takes place
112+ static func splitCoordinates( coordinates: String ? ) -> [ String ] {
113+ var split : [ String ] = [ ]
114+
115+ guard let coordinates = coordinates else {
116+ return split
117+ }
118+
119+ // trim whitespace from the start and end of the string
120+ let coordinatesToParse = coordinates. trimmingCharacters ( in: CharacterSet . whitespacesAndNewlines)
121+
122+ // if there is a comma, split on that
123+ if coordinatesToParse. firstIndex ( of: " , " ) != nil {
124+ return coordinatesToParse. split ( separator: " , " ) . map { splitString in
125+ return " \( splitString) " . components ( separatedBy: . whitespacesAndNewlines) . joined ( )
126+ }
127+ }
128+
129+ // check if there are any direction letters
130+ let firstDirectionIndex = coordinatesToParse. firstIndex { character in
131+ let uppercase = character. uppercased ( )
132+ return uppercase == " N " || uppercase == " S " || uppercase == " E " || uppercase == " W "
133+ }
134+ let hasDirection = firstDirectionIndex != nil
135+
136+ // if the string has a direction we can try to split on the dash
137+ if hasDirection && coordinatesToParse. firstIndex ( of: " - " ) != nil {
138+ return coordinatesToParse. split ( separator: " - " ) . map { splitString in
139+ return " \( splitString) " . components ( separatedBy: . whitespacesAndNewlines) . joined ( )
140+ }
141+ } else if hasDirection {
142+ // if the string has a direction but no dash, split on the direction
143+ let lastDirectionIndex = coordinatesToParse. lastIndex { character in
144+ let uppercase = character. uppercased ( )
145+ return uppercase == " N " || uppercase == " S " || uppercase == " E " || uppercase == " W "
146+ }
147+ // the direction will either be at the begining of the string, or the end
148+ // if the direction is at the begining of the string, use the second index unless there is no second index
149+ // in which case there is only one coordinate
150+ if firstDirectionIndex == coordinatesToParse. startIndex {
151+ if let lastDirectionIndex = lastDirectionIndex, lastDirectionIndex != firstDirectionIndex {
152+ split. append ( " \( coordinatesToParse. prefix ( upTo: lastDirectionIndex) ) " )
153+ split. append ( " \( coordinatesToParse. suffix ( from: lastDirectionIndex) ) " )
154+ } else {
155+ // only one coordinate
156+ split. append ( coordinatesToParse)
157+ }
158+ } else if lastDirectionIndex == coordinatesToParse. index ( coordinatesToParse. endIndex, offsetBy: - 1 ) {
159+ // if the last direction index is the end of the string use the first index unless the first and last index are the same
160+ if lastDirectionIndex == firstDirectionIndex {
161+ // only one coordinate
162+ split. append ( coordinatesToParse)
163+ } else if let firstDirectionIndex = firstDirectionIndex {
164+ split. append ( " \( coordinatesToParse. prefix ( upTo: coordinatesToParse. index ( firstDirectionIndex, offsetBy: 1 ) ) ) " )
165+ split. append ( " \( coordinatesToParse. suffix ( from: coordinatesToParse. index ( firstDirectionIndex, offsetBy: 1 ) ) ) " )
166+ }
167+ }
168+ }
169+
170+ // one last attempt to split. if there is one white space character split on that
171+ let whitespaceSplit = coordinatesToParse. components ( separatedBy: . whitespacesAndNewlines)
172+ if whitespaceSplit. count <= 2 {
173+ split = whitespaceSplit
174+ }
175+
176+ return split. map { splitString in
177+ return splitString. components ( separatedBy: . whitespacesAndNewlines) . joined ( )
178+ }
179+ }
180+
181+ // best effort parse of the string passed in
182+ // returns kCLLocationCoordinate2DInvalid if there is no way to parse
183+ // If only one of latitude or longitude can be parsed, the returned coordinate will have that value set
184+ // with the other value being CLLocationDegrees.nan. longitude will be the default returned value
185+ static func parse( coordinates: String ? ) -> CLLocationCoordinate2D ? {
186+ var latitude : CLLocationDegrees ?
187+ var longitude : CLLocationDegrees ?
188+
189+ let split = CLLocationCoordinate2D . splitCoordinates ( coordinates: coordinates)
190+ if split. count == 2 {
191+ latitude = CLLocationCoordinate2D . parse ( coordinate: split [ 0 ] , enforceLatitude: true )
192+ longitude = CLLocationCoordinate2D . parse ( coordinate: split [ 1 ] , enforceLatitude: false )
193+ }
194+ if let latitude = latitude, let longitude = longitude {
195+ return CLLocationCoordinate2D ( latitude: latitude, longitude: longitude)
196+ }
197+ return nil
198+ }
199+
200+ // takes one coordinate and translates it into a CLLocationDegrees
201+ // returns nil if nothing can be parsed
202+ static func parse( coordinate: String ? , enforceLatitude: Bool = false ) -> CLLocationDegrees ? {
203+ guard let coordinate = coordinate else {
204+ return nil
205+ }
206+
207+ let normalized = coordinate. trimmingCharacters ( in: CharacterSet . whitespacesAndNewlines)
208+ // check if it is a number and that number could be a valid latitude or longitude
209+ // could either be a decimal or a whole number representing lat/lng or a DDMMSS.sss number representing degree minutes seconds
210+ if let decimalDegrees = Double ( normalized) {
211+ // if either of these are true, parse it as a regular latitude longitude
212+ if ( !enforceLatitude && decimalDegrees >= - 180 && decimalDegrees <= 180 )
213+ || ( enforceLatitude && decimalDegrees >= - 90 && decimalDegrees <= 90 ) {
214+ return CLLocationDegrees ( decimalDegrees)
215+ }
216+ }
217+
218+ // try to just parse it as DMS
219+ let dms = CLLocationCoordinate2D . parseDMS ( coordinate: normalized)
220+ if let degrees = dms. degrees {
221+ var coordinateDegrees = Double ( degrees)
222+ if let minutes = dms. minutes {
223+ coordinateDegrees += Double ( minutes) / 60.0
224+ }
225+ if let seconds = dms. seconds {
226+ coordinateDegrees += Double ( seconds) / 3600.0
227+ }
228+ if let direction = dms. direction {
229+ if direction == " S " || direction == " W " {
230+ coordinateDegrees = - coordinateDegrees
231+ }
232+ }
233+ return CLLocationDegrees ( coordinateDegrees)
234+ }
111235
112236 return nil
113237 }
@@ -397,6 +521,22 @@ extension CLLocationCoordinate2D {
397521 }
398522
399523 init ? ( coordinateString: String ) {
524+ let initialSplit = coordinateString. trimmingCharacters ( in: . whitespacesAndNewlines) . components ( separatedBy: CharacterSet ( charactersIn: " , " ) )
525+ if initialSplit. count == 2 , let latitude = Double ( initialSplit [ 0 ] ) , let longitude = Double ( initialSplit [ 1 ] ) {
526+ self . init ( latitude: latitude, longitude: longitude)
527+ return
528+ }
529+
530+ if let coordinate = CLLocationCoordinate2D . parse ( coordinates: coordinateString) {
531+ self . init ( latitude: coordinate. latitude, longitude: coordinate. longitude)
532+ return
533+ }
534+
535+ if initialSplit. count == 1 , let _ = Double ( initialSplit [ 0 ] ) {
536+ // this is not a valid coordinate, just bail
537+ return nil
538+ }
539+
400540 let p = #"(?<latdeg>-?[0-9]*\.?\d+)[\s°-]*(?<latminutes>\d{1,2}\.?\d+)?[\s\`'-]*(?<latseconds>\d{1,2}\.?\d+)?[\s\" ]?(?<latdirection>([NOEWS])?)[\s,]*(?<londeg>-?[0-9]*\.?\d+)[\s°-]*(?<lonminutes>\d{1,2}\.?\d+)?[\s\`'-]*(?<lonseconds>\d{1,2}\.?\d+)?[\s\" ]*(?<londirection>([NOEWS])?)"#
401541
402542 var foundLat : Bool = false
@@ -417,6 +557,8 @@ extension CLLocationCoordinate2D {
417557 let range = Range ( nsrange, in: coordinateString) ,
418558 !range. isEmpty
419559 {
560+ print ( " xxx component \( component) " )
561+ print ( " xxx value \( coordinateString [ range] ) " )
420562 if component == " latdirection " {
421563 latmultiplier = " NEO " . contains ( coordinateString [ range] ) ? 1.0 : - 1.0
422564 } else if component == " latdeg " {
0 commit comments