Skip to content
This repository was archived by the owner on Jul 10, 2025. It is now read-only.

Commit a854328

Browse files
committed
core location tests, onboarding view tests
1 parent 78197fe commit a854328

File tree

10 files changed

+1294
-175
lines changed

10 files changed

+1294
-175
lines changed

Marlin/Marlin.xcodeproj/project.pbxproj

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@
145145
F75324B429805E5C005C509F /* DFRSSummaryViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75324B329805E5C005C509F /* DFRSSummaryViewTests.swift */; };
146146
F75324B629806329005C509F /* DFRSDetailViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75324B529806329005C509F /* DFRSDetailViewTests.swift */; };
147147
F75324B829807F04005C509F /* SubmitReportViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75324B729807F04005C509F /* SubmitReportViewTests.swift */; };
148+
F75324BB2980906E005C509F /* CLLocationCoordinate2DExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75324BA2980906E005C509F /* CLLocationCoordinate2DExtensionsTests.swift */; };
149+
F75324BE298197E4005C509F /* OnboardingViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75324BD298197E4005C509F /* OnboardingViewTests.swift */; };
148150
F75F81D529526E9C0062A708 /* AsamSummaryViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75F81D429526E9C0062A708 /* AsamSummaryViewTests.swift */; };
149151
F75F81D8295DE0EF0062A708 /* AboutCellTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75F81D7295DE0EF0062A708 /* AboutCellTests.swift */; };
150152
F75F81DA295DE4640062A708 /* AboutViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75F81D9295DE4640062A708 /* AboutViewTests.swift */; };
@@ -262,7 +264,6 @@
262264
F7F59799285D166700F9B32E /* ModuDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F59798285D166700F9B32E /* ModuDetailView.swift */; };
263265
F7F5979D285D17EE00F9B32E /* ModuSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F5979C285D17EE00F9B32E /* ModuSummaryView.swift */; };
264266
F7F5979F285D185000F9B32E /* ModuMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F5979E285D185000F9B32E /* ModuMap.swift */; };
265-
F7F597A3286238F400F9B32E /* MSIMapCluster.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F597A2286238F400F9B32E /* MSIMapCluster.swift */; };
266267
F7F597A62862721B00F9B32E /* NavigationalWarning+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F597A52862721B00F9B32E /* NavigationalWarning+CoreDataClass.swift */; };
267268
F7F597A82864BF9D00F9B32E /* navwarnings.json in Resources */ = {isa = PBXBuildFile; fileRef = F7F597A72864BF9D00F9B32E /* navwarnings.json */; };
268269
F7F597AA2864DD3900F9B32E /* NavigationalWarningSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F597A92864DD3900F9B32E /* NavigationalWarningSummaryView.swift */; };
@@ -486,6 +487,8 @@
486487
F75324B329805E5C005C509F /* DFRSSummaryViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DFRSSummaryViewTests.swift; sourceTree = "<group>"; };
487488
F75324B529806329005C509F /* DFRSDetailViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DFRSDetailViewTests.swift; sourceTree = "<group>"; };
488489
F75324B729807F04005C509F /* SubmitReportViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubmitReportViewTests.swift; sourceTree = "<group>"; };
490+
F75324BA2980906E005C509F /* CLLocationCoordinate2DExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLLocationCoordinate2DExtensionsTests.swift; sourceTree = "<group>"; };
491+
F75324BD298197E4005C509F /* OnboardingViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewTests.swift; sourceTree = "<group>"; };
489492
F75F81D229526D5C0062A708 /* libKIF.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libKIF.a; sourceTree = BUILT_PRODUCTS_DIR; };
490493
F75F81D429526E9C0062A708 /* AsamSummaryViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsamSummaryViewTests.swift; sourceTree = "<group>"; };
491494
F75F81D7295DE0EF0062A708 /* AboutCellTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutCellTests.swift; sourceTree = "<group>"; };
@@ -607,7 +610,6 @@
607610
F7F59798285D166700F9B32E /* ModuDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModuDetailView.swift; sourceTree = "<group>"; };
608611
F7F5979C285D17EE00F9B32E /* ModuSummaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModuSummaryView.swift; sourceTree = "<group>"; };
609612
F7F5979E285D185000F9B32E /* ModuMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModuMap.swift; sourceTree = "<group>"; };
610-
F7F597A2286238F400F9B32E /* MSIMapCluster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSIMapCluster.swift; sourceTree = "<group>"; };
611613
F7F597A52862721B00F9B32E /* NavigationalWarning+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationalWarning+CoreDataClass.swift"; sourceTree = "<group>"; };
612614
F7F597A72864BF9D00F9B32E /* navwarnings.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = navwarnings.json; sourceTree = "<group>"; };
613615
F7F597A92864DD3900F9B32E /* NavigationalWarningSummaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationalWarningSummaryView.swift; sourceTree = "<group>"; };
@@ -844,7 +846,6 @@
844846
F71DF4892858BF0700686951 /* MarlinMap.swift */,
845847
F71DF4932858C0EF00686951 /* StyledPolygon.swift */,
846848
F71DF4952858C18000686951 /* StyledPolyline.swift */,
847-
F7F597A2286238F400F9B32E /* MSIMapCluster.swift */,
848849
F7D671C9286C945500758F1C /* BaseMapOverlay.swift */,
849850
F7D671D5286DFAA800758F1C /* UserTrackingButton.swift */,
850851
F7D671D7286E163E00758F1C /* MapSettings.swift */,
@@ -906,6 +907,8 @@
906907
F727A68F284E605800BD728A /* MarlinTests */ = {
907908
isa = PBXGroup;
908909
children = (
910+
F75324BC298197AF005C509F /* Onboarding */,
911+
F75324B92980905B005C509F /* Extensions */,
909912
F72281DB296F3FC700218FC0 /* SideMenu */,
910913
F75F81E529623A7C0062A708 /* BottomSheet */,
911914
F75F81DD295F68A80062A708 /* Views */,
@@ -1049,6 +1052,22 @@
10491052
path = RadioBeacon;
10501053
sourceTree = "<group>";
10511054
};
1055+
F75324B92980905B005C509F /* Extensions */ = {
1056+
isa = PBXGroup;
1057+
children = (
1058+
F75324BA2980906E005C509F /* CLLocationCoordinate2DExtensionsTests.swift */,
1059+
);
1060+
path = Extensions;
1061+
sourceTree = "<group>";
1062+
};
1063+
F75324BC298197AF005C509F /* Onboarding */ = {
1064+
isa = PBXGroup;
1065+
children = (
1066+
F75324BD298197E4005C509F /* OnboardingViewTests.swift */,
1067+
);
1068+
path = Onboarding;
1069+
sourceTree = "<group>";
1070+
};
10521071
F75F81D6295DE0DD0062A708 /* Settings */ = {
10531072
isa = PBXGroup;
10541073
children = (
@@ -1884,6 +1903,7 @@
18841903
isa = PBXSourcesBuildPhase;
18851904
buildActionMask = 2147483647;
18861905
files = (
1906+
F75324BE298197E4005C509F /* OnboardingViewTests.swift in Sources */,
18871907
F73ABC92295250D50085EDC5 /* FilterViewModelTests.swift in Sources */,
18881908
F72D89082979E3E400D022FB /* RadioBeaconSummaryViewTests.swift in Sources */,
18891909
F72281D8296E232700218FC0 /* ChartCorrectionQueryTests.swift in Sources */,
@@ -1919,6 +1939,7 @@
19191939
F72D890A2979E7FC00D022FB /* RadioBeaconDetailViewTests.swift in Sources */,
19201940
F7D8FA342936C08200E7D0A1 /* MapImageTests.swift in Sources */,
19211941
F72D88F42971B84D00D022FB /* FilterBottomSheetTests.swift in Sources */,
1942+
F75324BB2980906E005C509F /* CLLocationCoordinate2DExtensionsTests.swift in Sources */,
19221943
F72EAAB7291BF21400AC6026 /* ModuDataTests.swift in Sources */,
19231944
F7D8FA792941005E00E7D0A1 /* ChartCorrectionListViewModelTests.swift in Sources */,
19241945
F75F81DC295F3F190062A708 /* AsamDetailViewTests.swift in Sources */,
@@ -2155,7 +2176,6 @@
21552176
F7FFB2F628D6340B00C31AA3 /* Port+MapImage.swift in Sources */,
21562177
F707BA81287768FA006A94C4 /* LightDetailView.swift in Sources */,
21572178
F73ABC98295250EE0085EDC5 /* ImageLoader.swift in Sources */,
2158-
F7F597A3286238F400F9B32E /* MSIMapCluster.swift in Sources */,
21592179
F7C23E0829084F110002A7DF /* ElectronicPublication+DataSourceViewBuilder.swift in Sources */,
21602180
F7D8FA56293AA13300E7D0A1 /* DataSourceFilterComparison.swift in Sources */,
21612181
F71D286628BE453A00F1775C /* DifferentialGPSStationSummaryView.swift in Sources */,

Marlin/Marlin/DataSources/DFRS/DFRS+Decodable.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,10 @@ struct DFRSProperties: Decodable {
133133
self.txLatitude = -190.0
134134
self.txPosition = nil
135135
}
136-
137-
let mgrsPosition = MGRS.from(longitude, latitude)
138-
self.mgrs10km = mgrsPosition.coordinate(.TEN_KILOMETER)
136+
if txPosition != nil || rxPosition != nil {
137+
let mgrsPosition = MGRS.from(longitude, latitude)
138+
self.mgrs10km = mgrsPosition.coordinate(.TEN_KILOMETER)
139+
}
139140
}
140141

141142
// The keys must have the same name as the attributes of the Asam entity.

Marlin/Marlin/Extensions/CLLocationCoordinate2DExtensions.swift

Lines changed: 143 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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" {

Marlin/Marlin/Location/LocationManager.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ import geopackage_ios
1313
import mgrs_ios
1414
import ExceptionCatcher
1515

16-
class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
16+
protocol LocationManagerProtocol {
17+
var locationStatus: CLAuthorizationStatus? { get set }
18+
func requestAuthorization()
19+
}
20+
21+
class LocationManager: NSObject, ObservableObject, LocationManagerProtocol, CLLocationManagerDelegate {
1722

1823
static let shared = LocationManager()
1924

0 commit comments

Comments
 (0)