diff --git a/Meshtastic/Views/Map/MapViewSwiftUI.swift b/Meshtastic/Views/Map/MapViewSwiftUI.swift index d7942b22..90c732be 100644 --- a/Meshtastic/Views/Map/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/MapViewSwiftUI.swift @@ -44,6 +44,34 @@ struct MapViewSwiftUI: UIViewRepresentable { func updateUIView(_ mapView: MKMapView, context: Context) { mapView.mapType = mapViewType + + if self.customMapOverlay != self.presentCustomMapOverlayHash || self.loadedLastUpdatedLocalMapFile != self.lastUpdatedLocalMapFile { + mapView.removeOverlays(mapView.overlays) + if self.customMapOverlay != nil { + + let fileManager = FileManager.default + let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! + let tilePath = documentsDirectory.appendingPathComponent("offline_map.mbtiles", isDirectory: false).path + if fileManager.fileExists(atPath: tilePath) { + //if let tilePath = Bundle.main.path(forResource: "offline_map", ofType: "mbtiles") { + + print("Loading local map file") + + if let overlay = LocalMBTileOverlay(mbTilePath: tilePath) { + + overlay.canReplaceMapContent = false//customMapOverlay.canReplaceMapContent + + mapView.addOverlay(overlay) + } + } else { + print("Couldn't find a local map file to load") + } + } + DispatchQueue.main.async { + self.presentCustomMapOverlayHash = self.customMapOverlay + self.loadedLastUpdatedLocalMapFile = self.lastUpdatedLocalMapFile + } + } } func makeCoordinator() -> MapCoordinator { @@ -55,6 +83,8 @@ struct MapViewSwiftUI: UIViewRepresentable { var parent: MapViewSwiftUI var longPressRecognizer = UILongPressGestureRecognizer() + var overlays: [Overlay] = [] + init(_ parent: MapViewSwiftUI) { self.parent = parent super.init() @@ -62,6 +92,7 @@ struct MapViewSwiftUI: UIViewRepresentable { self.longPressRecognizer.minimumPressDuration = 0.2 self.longPressRecognizer.delegate = self self.parent.mapView.addGestureRecognizer(longPressRecognizer) + self.overlays = [] } func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { @@ -80,26 +111,73 @@ struct MapViewSwiftUI: UIViewRepresentable { annotationView.markerTintColor = UIColor(.accentColor) annotationView.titleVisibility = .visible return annotationView + case _ as WaypointEntity: + return nil + default: return nil } } @objc func longPressHandler(_ gesture: UILongPressGestureRecognizer) { - //if gesture.state == .ended { - // Screen Position - CGPoint - let location = longPressRecognizer.location(in: self.parent.mapView) - // Map Coordinate - CLLocationCoordinate2D - let coordinate = self.parent.mapView.convert(location, toCoordinateFrom: self.parent.mapView) - print(coordinate) - - // Add annotation: - let annotation = MKPointAnnotation() - annotation.title = "Dropped Pin" - annotation.coordinate = coordinate - parent.mapView.addAnnotation(annotation) - parent.onMarkerTap(coordinate) - UINotificationFeedbackGenerator().notificationOccurred(.success) - //} + // Screen Position - CGPoint + let location = longPressRecognizer.location(in: self.parent.mapView) + // Map Coordinate - CLLocationCoordinate2D + let coordinate = self.parent.mapView.convert(location, toCoordinateFrom: self.parent.mapView) + print(coordinate) + + // Add annotation: + let annotation = MKPointAnnotation() + annotation.title = "Dropped Pin" + annotation.coordinate = coordinate + parent.mapView.addAnnotation(annotation) + parent.onMarkerTap(coordinate) + UINotificationFeedbackGenerator().notificationOccurred(.success) + } + + public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { + + if let index = self.overlays.firstIndex(where: { overlay_ in overlay_.shape.hash == overlay.hash }) { + + let unwrappedOverlay = self.overlays[index] + if let circleOverlay = unwrappedOverlay.shape as? MKCircle { + let renderer = MKCircleRenderer(circle: circleOverlay) + renderer.fillColor = unwrappedOverlay.fillColor + renderer.strokeColor = unwrappedOverlay.strokeColor + renderer.lineWidth = unwrappedOverlay.lineWidth + return renderer + } else if let polygonOverlay = unwrappedOverlay.shape as? MKPolygon { + let renderer = MKPolygonRenderer(polygon: polygonOverlay) + renderer.fillColor = unwrappedOverlay.fillColor + renderer.strokeColor = unwrappedOverlay.strokeColor + renderer.lineWidth = unwrappedOverlay.lineWidth + return renderer + } else if let multiPolygonOverlay = unwrappedOverlay.shape as? MKMultiPolygon { + let renderer = MKMultiPolygonRenderer(multiPolygon: multiPolygonOverlay) + renderer.fillColor = unwrappedOverlay.fillColor + renderer.strokeColor = unwrappedOverlay.strokeColor + renderer.lineWidth = unwrappedOverlay.lineWidth + return renderer + } else if let polyLineOverlay = unwrappedOverlay.shape as? MKPolyline { + let renderer = MKPolylineRenderer(polyline: polyLineOverlay) + renderer.fillColor = unwrappedOverlay.fillColor + renderer.strokeColor = unwrappedOverlay.strokeColor + renderer.lineWidth = unwrappedOverlay.lineWidth + return renderer + } else if let multiPolylineOverlay = unwrappedOverlay.shape as? MKMultiPolyline { + let renderer = MKMultiPolylineRenderer(multiPolyline: multiPolylineOverlay) + renderer.fillColor = unwrappedOverlay.fillColor + renderer.strokeColor = unwrappedOverlay.strokeColor + renderer.lineWidth = unwrappedOverlay.lineWidth + return renderer + } else { + return MKOverlayRenderer() + } + + } else if let tileOverlay = overlay as? MKTileOverlay { + return MKTileOverlayRenderer(tileOverlay: tileOverlay) + } else { + return MKOverlayRenderer() + } } } @@ -107,13 +185,13 @@ struct MapViewSwiftUI: UIViewRepresentable { public struct DefaultTile: Hashable { let tileName: String let tileType: String - + public init(tileName: String, tileType: String) { self.tileName = tileName self.tileType = tileType } } - + public struct CustomMapOverlay: Equatable, Hashable { let mapName: String let tileType: String @@ -121,7 +199,7 @@ struct MapViewSwiftUI: UIViewRepresentable { var minimumZoomLevel: Int? var maximumZoomLevel: Int? let defaultTile: DefaultTile? - + public init( mapName: String, tileType: String, @@ -158,15 +236,15 @@ struct MapViewSwiftUI: UIViewRepresentable { self.defaultTile = defaultTile } } - + public class CustomMapOverlaySource: MKTileOverlay { - + // requires folder: tiles/{mapName}/z/y/y,{tileType} private var parent: MapView private let mapName: String private let tileType: String private let defaultTile: DefaultTile? - + public init( parent: MapView, mapName: String, @@ -179,7 +257,7 @@ struct MapViewSwiftUI: UIViewRepresentable { self.defaultTile = defaultTile super.init(urlTemplate: "") } - + public override func url(forTilePath path: MKTileOverlayPath) -> URL { if let tileUrl = Bundle.main.url( forResource: "\(path.y)", @@ -198,11 +276,9 @@ struct MapViewSwiftUI: UIViewRepresentable { } else { let urlstring = self.mapName+"\(path.z)/\(path.x)/\(path.y).png" return URL(string: urlstring)! - // Bundle.main.url(forResource: "surrounding", withExtension: "png", subdirectory: "tiles")! } - + } - } public struct Overlay { @@ -213,12 +289,12 @@ struct MapViewSwiftUI: UIViewRepresentable { lhs.shape.coordinate.longitude == rhs.shape.coordinate.longitude && lhs.fillColor == rhs.fillColor } - + var shape: MKOverlay var fillColor: UIColor? var strokeColor: UIColor? var lineWidth: CGFloat - + public init( shape: MKOverlay, fillColor: UIColor? = nil, diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 11162511..8278b6c6 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -18,6 +18,13 @@ struct NodeDetail: View { @State private var showingShutdownConfirm: Bool = false @State private var showingRebootConfirm: Bool = false @State private var presentingWaypointForm = false + @State private var showOverlays: Bool = true + @State private var overlays: [MapViewSwiftUI.Overlay] = [] + @State private var customMapOverlay: MapViewSwiftUI.CustomMapOverlay? = MapViewSwiftUI.CustomMapOverlay( + mapName: "offlinemap", + tileType: "png", + canReplaceMapContent: true + ) var node: NodeInfoEntity @@ -37,7 +44,10 @@ struct NodeDetail: View { MapViewSwiftUI(onMarkerTap: { coord in presentingWaypointForm = true waypointCoordinate = coord - }, positions: annotations, region: MKCoordinateRegion(center: nodeCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)), mapViewType: mapType) + }, positions: annotations, region: MKCoordinateRegion(center: nodeCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)), mapViewType: mapType, + customMapOverlay: self.customMapOverlay, + overlays: self.overlays + ) VStack { Spacer() Text(mostRecent.satsInView > 0 ? "Sats: \(mostRecent.satsInView)" : " ")