ios – Can a polyline be certain to an annotation?

ios – Can a polyline be certain to an annotation?


I’m making a map app for an autonomous robotic. I’m having problem with annotations and polylines shifting collectively when dragging the annotation. At present the annotation will transfer after which after a quick pause the polyline will transfer and catch as much as it.

I need each to maneuver in unison with a “rubber band” conduct. I’ve not been capable of finding a tutorial or instance of dragging an annotation that has polylines linked to it when dragging. Thanks for any assist/steering you could give.

// LAMErtkAnnotationManager.swift
// LAME-RTK
// Created: 11/19/2024

import Basis
import MapboxMaps
import CoreData
import Turf
import UIKit

// MARK: - Notification Extension

extension Notification.Title {
    /// Notification posted when an annotation's coordinate is up to date.
    static let didUpdateAnnotation = Notification.Title("didUpdateAnnotation")
}

// MARK: - LAMErtkAnnotationManager

/// Manages annotations (drag handles) and polylines on the Mapbox map.
/// Ensures that dragging an annotation updates the corresponding polyline in real-time.
class LAMErtkAnnotationManager: NSObject, AnnotationInteractionDelegate {

    // MARK: - Properties

    /// The MapView occasion the place annotations and polylines are managed.
    var mapView: MapView

    /// The Core Knowledge context for fetching and saving GPS information factors.
    var viewContext: NSManagedObjectContext

    /// Supervisor for level annotations (drag handles).
    var pointAnnotationManager: PointAnnotationManager

    /// Supervisor for polyline annotations.
    var polylineAnnotationManager: PolylineAnnotationManager

    /// Dictionary mapping `zoneID` to its corresponding polyline annotation.
    non-public var polylinesByZone: [String: PolylineAnnotation] = [:]

    /// Dictionary to maintain observe of the final recognized coordinates of annotations.
    /// Used to detect modifications throughout dragging.
    non-public var annotationCoordinates: [String: CLLocationCoordinate2D] = [:]

    /// Timer to periodically verify for annotation coordinate modifications.
    /// Since MapboxMaps SDK lacks direct drag occasion delegates, this timer facilitates synchronization.
    non-public var annotationCheckTimer: Timer?

    // MARK: - Initialization

    /// Initializes the `LAMErtkAnnotationManager` with the offered `MapView` and `NSManagedObjectContext`.
    ///
    /// - Parameters:
    ///   - mapView: The `MapView` occasion the place annotations and polylines are managed.
    ///   - viewContext: The Core Knowledge context for fetching and saving GPS information factors.
    init(mapView: MapView, viewContext: NSManagedObjectContext) {
        self.mapView = mapView
        self.viewContext = viewContext
        self.pointAnnotationManager = mapView.annotations.makePointAnnotationManager()
        self.polylineAnnotationManager = mapView.annotations.makePolylineAnnotationManager()
        tremendous.init()

        // Assign the delegate for interplay occasions.
        self.pointAnnotationManager.delegate = self
        self.polylineAnnotationManager.delegate = self

        print("***** LAMErtkAnnotationManager initialized.")

        // Initialize saved annotation coordinates.
        initializeAnnotationCoordinates()

        // Begin the timer to verify for annotation updates each second.
        annotationCheckTimer = Timer.scheduledTimer(timeInterval: 1.0,
                                                   goal: self,
                                                   selector: #selector(checkForAnnotationUpdates),
                                                   userInfo: nil,
                                                   repeats: true)

        // Observe annotation replace notifications.
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(handleAnnotationUpdate(notification:)),
                                               identify: .didUpdateAnnotation,
                                               object: nil)
    }

    deinit {
        // Invalidate the timer and take away observers to forestall reminiscence leaks.
        annotationCheckTimer?.invalidate()
        NotificationCenter.default.removeObserver(self)
    }

    // MARK: - Annotation Administration

    /// Clears all level annotations and polylines from the map.
    func clearAllAnnotations() {
        pointAnnotationManager.annotations.removeAll()
        polylineAnnotationManager.annotations.removeAll()
        polylinesByZone.removeAll()
        annotationCoordinates.removeAll()
        print("***** All annotations cleared.")
    }

    /// Masses all level annotations and corresponding polylines from Core Knowledge.
    func loadAllAnnotations() {
        clearAllAnnotations()
        loadPointAnnotations()
        loadPolylines()
        print("***** All annotations loaded.")
    }

    /// Masses level annotations (drag handles) from Core Knowledge.
    non-public func loadPointAnnotations() {
        let fetchRequest: NSFetchRequest = GPSDataPoint.fetchRequest()
        fetchRequest.sortDescriptors = [
            NSSortDescriptor(key: "zoneID", ascending: true),
            NSSortDescriptor(key: "order", ascending: true)
        ]

        do {
            let gpsDataPoints = strive viewContext.fetch(fetchRequest)
            pointAnnotationManager.annotations = gpsDataPoints.compactMap { level -> PointAnnotation? in
                guard let zoneID = level.zoneID else { return nil }
                let coordinate = CLLocationCoordinate2D(latitude: level.latitude, longitude: level.longitude)
                var annotation = PointAnnotation(coordinate: coordinate)
                annotation.isDraggable = true
                annotation.customData = ["zoneID": .string(zoneID)]
                annotation.iconImage = "diamondFill" // Set the icon picture to "diamondFill"

                // Retailer the preliminary coordinates.
                annotationCoordinates[zoneID] = coordinate

                return annotation
            }
            print("***** Loaded (pointAnnotationManager.annotations.rely) level annotations.")
        } catch {
            print("***** Error fetching level annotations: (error.localizedDescription)")
        }
    }

    /// Masses polylines from Core Knowledge, grouping them by `zoneID`.
    non-public func loadPolylines() {
        let fetchRequest: NSFetchRequest = GPSDataPoint.fetchRequest()
        fetchRequest.sortDescriptors = [
            NSSortDescriptor(key: "zoneID", ascending: true),
            NSSortDescriptor(key: "order", ascending: true)
        ]

        do {
            let gpsDataPoints = strive viewContext.fetch(fetchRequest)
            let groupedPoints = Dictionary(grouping: gpsDataPoints, by: { $0.zoneID ?? "default" })

            for (zoneID, factors) in groupedPoints {
                let coordinates = factors.map { CLLocationCoordinate2D(latitude: $0.latitude, longitude: $0.longitude) }
                if let lineString = strive? LineString(coordinates) {
                    var polyline = PolylineAnnotation(lineString: lineString)
                    polyline.lineColor = StyleColor(.systemBlue)
                    polyline.lineWidth = 3.0
                    polyline.customData = ["zoneID": .string(zoneID)]
                    polylineAnnotationManager.annotations.append(polyline)
                    polylinesByZone[zoneID] = polyline
                    print("***** Added polyline for zoneID: (zoneID) with (coordinates.rely) factors.")
                }
            }
        } catch {
            print("***** Error fetching polylines: (error.localizedDescription)")
        }
    }

    /// Initializes the saved coordinates from present annotations.
    non-public func initializeAnnotationCoordinates() {
        for annotation in pointAnnotationManager.annotations {
            if let zoneIDValue = annotation.customData["zoneID"],
               case let .string(zoneID) = zoneIDValue {
                annotationCoordinates[zoneID] = annotation.coordinate
            }
        }
    }

    // MARK: - Dynamic Polyline Updates with Turf

    /// Updates the polyline related to a given `zoneID` utilizing Turf.
    ///
    /// - Parameter zoneID: The `zoneID` whose polyline must be up to date.
    func updatePolyline(for zoneID: String) {
        guard var polyline = polylinesByZone[zoneID] else { return }
        let updatedCoordinates = pointAnnotationManager.annotations.compactMap { annotation -> CLLocationCoordinate2D? in
            guard let annotationZoneID = annotation.customData["zoneID"],
                  case let .string(annotationZone) = annotationZoneID,
                  annotationZone == zoneID else { return nil }
            return annotation.coordinate
        }
        guard !updatedCoordinates.isEmpty else { return }

        // Use Turf to validate and create a brand new LineString.
        if let updatedLineString = strive? LineString(updatedCoordinates) {
            polyline.lineString = updatedLineString
            polylineAnnotationManager.annotations = polylineAnnotationManager.annotations.map { $0.id == polyline.id ? polyline : $0 }
            print("***** Up to date polyline for zoneID: (zoneID) with (updatedCoordinates.rely) factors.")
        } else {
            print("***** Error creating LineString for zoneID: (zoneID).")
        }
    }

    // MARK: - AnnotationInteractionDelegate

    /// Handles faucet interactions on annotations.
    ///
    /// - Parameters:
    ///   - supervisor: The `AnnotationManager` occasion.
    ///   - annotations: The record of annotations that had been tapped.
    func annotationManager(_ supervisor: AnnotationManager, didDetectTappedAnnotations annotations: [Annotation]) {
        for annotation in annotations {
            if let level = annotation as? PointAnnotation,
               let zoneIDValue = level.customData["zoneID"],
               case let .string(zoneID) = zoneIDValue {
                updatePolyline(for: zoneID)
            }
        }
    }

    // MARK: - Dealing with Annotation Drags

    /// Handles updates to annotations when they're dragged.
    /// Since MapboxMaps SDK would not present direct drag occasion delegates, this technique is triggered by way of notifications.
    ///
    /// - Parameter notification: The notification containing the up to date annotation.
    @objc non-public func handleAnnotationUpdate(notification: Notification) {
        guard let userInfo = notification.userInfo,
              let updatedAnnotation = userInfo["annotation"] as? PointAnnotation,
              let zoneIDValue = updatedAnnotation.customData["zoneID"] else { return }

        // Use sample matching to extract the string from JSONValue
        guard case let .string(zoneID) = zoneIDValue else { return }

        let newCoordinate = updatedAnnotation.coordinate
        print("***** Annotation for zoneID: (zoneID) moved to new coordinate: (newCoordinate)")

        // Replace the corresponding GPSDataPoint in Core Knowledge.
        let fetchRequest: NSFetchRequest = GPSDataPoint.fetchRequest()
        fetchRequest.predicate = NSPredicate(format: "zoneID == %@", zoneID)
        fetchRequest.sortDescriptors = [NSSortDescriptor(key: "order", ascending: true)]
        fetchRequest.fetchLimit = 1

        do {
            if let gpsDataPoint = strive viewContext.fetch(fetchRequest).first {
                gpsDataPoint.latitude = newCoordinate.latitude
                gpsDataPoint.longitude = newCoordinate.longitude
                strive viewContext.save()
                print("***** Up to date GPSDataPoint for zoneID: (zoneID) with new coordinates.")
            }
        } catch {
            print("***** Error updating GPSDataPoint for zoneID: (zoneID): (error.localizedDescription)")
        }

        // Replace the corresponding polyline.
        updatePolyline(for: zoneID)
    }

    // MARK: - Timer for Checking Annotation Updates

    /// Periodically checks for modifications in annotation coordinates to detect drags.
    @objc non-public func checkForAnnotationUpdates() {
        for annotation in pointAnnotationManager.annotations {
            if let zoneIDValue = annotation.customData["zoneID"],
               case let .string(zoneID) = zoneIDValue,
               let oldCoordinate = annotationCoordinates[zoneID],
               oldCoordinate.latitude != annotation.coordinate.latitude ||
               oldCoordinate.longitude != annotation.coordinate.longitude {

                // Replace the saved coordinate.
                annotationCoordinates[zoneID] = annotation.coordinate

                // Submit a notification with the up to date annotation.
                NotificationCenter.default.submit(identify: .didUpdateAnnotation,
                                                object: nil,
                                                userInfo: ["annotation": annotation])
            }
        }
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *