Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pure SwiftUI Map Snapshot Capabilities #2166

Closed
seizler opened this issue Apr 18, 2024 · 3 comments
Closed

Pure SwiftUI Map Snapshot Capabilities #2166

seizler opened this issue Apr 18, 2024 · 3 comments

Comments

@seizler
Copy link

seizler commented Apr 18, 2024

New Feature

Need to be able to snapshot a pure SwiftUI map with annotations and polylines on it and have it as a UIImage so I can upload the image to my database.

Why

Reduce memory usage and not have to display full maps for every user posted map in my app.

`@_spi(Experimental) import MapboxMaps

@available(iOS 16.0, *)
struct SnapTripMapView: View {
@State var tripFlights: [FlightDocument] = []
@State private var viewport: Viewport = .camera(center: CLLocationCoordinate2D(latitude: 0, longitude: 0), zoom: 10)
@State private var userExpDate: Date?
@State private var isProUser = false
@State var viewModel: SnapTripMapViewModel
@ObservedObject var tripManager = TripManager()
@State private var selectedGradientMode: TripMapViewModel.GradientMode = .defaultGradient
@State private var showingUpgradeView = false
@binding var isDisabled: Bool

init(tripFlights: [FlightDocument], isDisabled: Binding<Bool>, viewModel: SnapTripMapViewModel) {
    self._tripFlights = State(initialValue: tripFlights)
    self._isDisabled = isDisabled
    self.viewModel = viewModel  // Assign viewModel before calling calculateGeometry or any other instance methods

    let geometry = calculateGeometry(tripFlights: tripFlights)
    let padding = EdgeInsets()
    
    self._viewport = State(initialValue: .overview(
        geometry: geometry,
        geometryPadding: padding,
        maxZoom: 15 // Set the maxZoom if needed
    ))
}

var body: some View {
    ZStack(alignment: .topTrailing){
        Snapshotter()
        Map(viewport: $viewport) {
            ForEvery(Array(tripFlights.enumerated()), id: \.offset) { index, flight in
                
                let flightTrack = flight.flightTrack ?? []

                if flightTrack.count > 2 {
                    let firstCoordinate = flightTrack[1]  // Safely get the second element
                    let lastCoordinate = flightTrack[flightTrack.count - 2]
                    MapViewAnnotation(coordinate: CLLocationCoordinate2D(latitude: firstCoordinate.latitude!, longitude: firstCoordinate.longitude!)) {
                        Image(systemName: "airplane.departure")
                            .resizable()
                            .frame(width: 10, height: 10)
                            .offset(y: -0.69)
                            .foregroundColor(.white)
                            .padding(4)
                            .background(Color(hex: "#000000"))
                            .clipShape(Circle())
                            .shadow(radius: 2)
                    }
                    .allowOverlap(true)

                    MapViewAnnotation(coordinate: CLLocationCoordinate2D(latitude: lastCoordinate.latitude!, longitude: lastCoordinate.longitude!)) {
                        Image(systemName: "airplane.arrival")
                            .resizable()
                            .frame(width: 10, height: 10)
                            .offset(y: -0.69)
                            .foregroundColor(.white)
                            .padding(4)
                            .background(Color(hex: "#000000"))
                            .clipShape(Circle())
                            .shadow(radius: 2)
                    }
                    .allowOverlap(true)
                }
                
                let idlCenter = CLLocationCoordinate2D(latitude: 0, longitude: 180)
                let restrictedRadius = 80.47 // 50 miles in kilometers

                if flightTrack.count > 2 {
                    let coordinates = flightTrack.map { CLLocationCoordinate2D(latitude: $0.latitude!, longitude: $0.longitude!) }
                    let smoothTrack = catmullRomSplinePoints(points: coordinates, alpha: 0.5, numberOfInterpolations: 1000)
                    let totalColors = 12
                    let segmentLength = max(1, (smoothTrack.count / totalColors) + 1)
                    let totalSegments = (smoothTrack.count / segmentLength) + 1
                    
                    ForEvery(0..<totalSegments, id: \.self) { segmentIndex in
                        let start = segmentIndex * segmentLength
                        let end = min((segmentIndex + 1) * segmentLength, smoothTrack.count)
                        let segment = Array(smoothTrack[start..<end])
                        
                        if !coordinatesAreWithinRestrictedZone(coordinates: segment, restrictedZoneCenter: idlCenter, restrictedRadius: restrictedRadius) {
                            let color = gradientColor(for: segmentIndex, totalSegments: totalSegments) // Get the color from the gradient
                            
                            PolylineAnnotationGroup {
                                PolylineAnnotation(id: UUID().uuidString, lineCoordinates: segment)
                                    .lineWidth(6)
                                    .lineColor("#000000")
                                    .lineSortKey(1)
                            }
                            .lineCap(.round)
                        }
                    }

                        
                    ForEvery(0..<totalSegments, id: \.self) { segmentIndex in
                        let start = segmentIndex * segmentLength
                        let end = min((segmentIndex + 1) * segmentLength, smoothTrack.count)
                        let segment = Array(smoothTrack[start..<end])
                        
                        if !coordinatesAreWithinRestrictedZone(coordinates: segment, restrictedZoneCenter: idlCenter, restrictedRadius: restrictedRadius) {
                            let color = gradientColor(for: segmentIndex, totalSegments: totalSegments) // Get the color from the gradient
                            
                            PolylineAnnotationGroup {
                                PolylineAnnotation(id: UUID().uuidString, lineCoordinates: segment)
                                    .lineWidth(4)
                                    .lineColor(StyleColor(rawValue: color))
                                    .lineSortKey(2)
                            }
                            .lineCap(.round)
                        }
                    }
                }
            }

        }
        .mapStyle(MapStyle(uri: StyleURI(rawValue: viewModel.currentStyleURI)!))
        .onChange(of: viewModel.selectedStyleOption) { _ in
            viewModel.toggleMapStyleBasedOnSelection()
        }
        .disabled(isDisabled)
        .navigationTitle("Flight Map")
        .navigationBarTitleDisplayMode(.inline)
    }
}`
@persidskiy
Copy link
Contributor

persidskiy commented Apr 19, 2024

Hi @seizler, do I understand correctly that you want to take a snapshot from the Map rendered in SwiftUI?

You can do it with the captureSnapshot method:

MapReader { proxy in 
    Map(...)
       .onMapLoaded {
           let image = proxy.captureSnapshot(includeOverlays: true)
       }
}

@persidskiy
Copy link
Contributor

I'm closing the issue, feel free to open it if the suggested method doesn't work for you.

@seizler
Copy link
Author

seizler commented Apr 22, 2024

Hi! That worked. Totally missed it in the documentation. Thank you so much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants