From f848bca707c53b4348337b89696276c6960e272c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 14 Jul 2026 05:39:06 +0000 Subject: [PATCH] Add map style options to location simulator (satellite, hybrid, traffic, transit stops) - New toolbar menu on the location simulator map to switch between Standard, Satellite, and Hybrid map types - Traffic overlay toggle for Standard/Hybrid modes - Points-of-interest mode: All Places, Transit Stops (surfaces bus stops), or Hidden - Selections persist across launches via AppStorage Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01CUJbpUQiRUbjiTiYA4v7UK --- StikDebug/Views/MapSelectionView.swift | 125 ++++++++++++++++++++++++- 1 file changed, 124 insertions(+), 1 deletion(-) diff --git a/StikDebug/Views/MapSelectionView.swift b/StikDebug/Views/MapSelectionView.swift index 9552881..37b213f 100644 --- a/StikDebug/Views/MapSelectionView.swift +++ b/StikDebug/Views/MapSelectionView.swift @@ -103,6 +103,67 @@ enum SpeedProfile: String, CaseIterable, Identifiable { } } +enum MapDisplayMode: String, CaseIterable, Identifiable { + case standard + case satellite + case hybrid + + var id: String { rawValue } + + var title: String { + switch self { + case .standard: return "Standard" + case .satellite: return "Satellite" + case .hybrid: return "Hybrid" + } + } + + var systemImage: String { + switch self { + case .standard: return "map" + case .satellite: return "globe.americas.fill" + case .hybrid: return "square.2.layers.3d" + } + } + + /// Pure satellite imagery can't render traffic or labeled points of interest. + var supportsOverlays: Bool { + self != .satellite + } +} + +enum MapPointsOfInterestMode: String, CaseIterable, Identifiable { + case all + case transit + case hidden + + var id: String { rawValue } + + var title: String { + switch self { + case .all: return "All Places" + case .transit: return "Transit Stops" + case .hidden: return "Hidden" + } + } + + var systemImage: String { + switch self { + case .all: return "mappin.and.ellipse" + case .transit: return "bus.fill" + case .hidden: return "eye.slash" + } + } + + var categories: PointOfInterestCategories { + switch self { + case .all: return .all + case .transit: return .including([.publicTransport]) + case .hidden: return .excludingAll + } + } +} + private struct SpeedProfileSettings { let profile: SpeedProfile let customSpeedMetersPerSecond: CLLocationSpeed @@ -934,6 +995,11 @@ struct LocationSimulationView: View { @State private var showSaveBookmark = false @State private var newBookmarkName = "" + // Map appearance + @AppStorage("mapDisplayMode") private var mapDisplayModeRawValue: String = MapDisplayMode.standard.rawValue + @AppStorage("mapShowsTraffic") private var mapShowsTraffic: Bool = false + @AppStorage("mapPointsOfInterestMode") private var mapPointsOfInterestRawValue: String = MapPointsOfInterestMode.all.rawValue + // Speed profile @AppStorage("routeSpeedProfile") private var speedProfileRawValue: String = SpeedProfile.driving.rawValue @AppStorage("routeSpeedCustomKmh") private var customSpeedKmh: Double = 30 @@ -942,6 +1008,33 @@ struct LocationSimulationView: View { @State private var lastFallbackSpeed: CLLocationSpeed = RouteSimulationDefaults.importedRouteFallbackSpeedMetersPerSecond @State private var isImportedRoute = false + private var mapDisplayMode: MapDisplayMode { + MapDisplayMode(rawValue: mapDisplayModeRawValue) ?? .standard + } + + private var mapPointsOfInterestMode: MapPointsOfInterestMode { + MapPointsOfInterestMode(rawValue: mapPointsOfInterestRawValue) ?? .all + } + + private var mapStyle: MapStyle { + switch mapDisplayMode { + case .standard: + return .standard( + elevation: .realistic, + pointsOfInterest: mapPointsOfInterestMode.categories, + showsTraffic: mapShowsTraffic + ) + case .satellite: + return .imagery(elevation: .realistic) + case .hybrid: + return .hybrid( + elevation: .realistic, + pointsOfInterest: mapPointsOfInterestMode.categories, + showsTraffic: mapShowsTraffic + ) + } + } + private var speedProfile: SpeedProfile { SpeedProfile(rawValue: speedProfileRawValue) ?? .driving } @@ -1113,7 +1206,7 @@ struct LocationSimulationView: View { .tint(.red) } } - .mapStyle(.standard(elevation: .realistic)) + .mapStyle(mapStyle) .onTapGesture { point in if let loc = proxy.convert(point, from: .local) { applySelection(loc) @@ -1163,6 +1256,8 @@ struct LocationSimulationView: View { .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItemGroup(placement: .topBarLeading) { + mapStyleMenu + Button { showBookmarks = true } label: { @@ -1512,6 +1607,34 @@ struct LocationSimulationView: View { } } + private var mapStyleMenu: some View { + Menu { + Picker("Map Type", selection: $mapDisplayModeRawValue) { + ForEach(MapDisplayMode.allCases) { mode in + Label(mode.title, systemImage: mode.systemImage) + .tag(mode.rawValue) + } + } + + if mapDisplayMode.supportsOverlays { + Toggle(isOn: $mapShowsTraffic) { + Label("Traffic", systemImage: "car.2.fill") + } + + Picker("Points of Interest", selection: $mapPointsOfInterestRawValue) { + ForEach(MapPointsOfInterestMode.allCases) { mode in + Label(mode.title, systemImage: mode.systemImage) + .tag(mode.rawValue) + } + } + .pickerStyle(.menu) + } + } label: { + Image(systemName: "map.fill") + } + .accessibilityLabel("Map style: \(mapDisplayMode.title)") + } + private var speedProfileMenu: some View { Menu { ForEach(SpeedProfile.allCases) { profile in