/** * TrackerMap * Copyright (C) 2021-2022 Iván Ávalos , Henoch Ojeda * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ import SwiftUI import Combine import WhirlyGlobeMaplyComponent import shared struct MapView: UIViewControllerRepresentable { typealias UIViewControllerType = OurMaplyViewController @Binding var mapLayer: MapLayer @Binding var markers: [Marker] var markerCallback: ((Int32?) -> Void)? var link: BaseMapLink class Coordinator: NSObject, MaplyViewControllerDelegate { var parent: MapView var uiViewController: OurMaplyViewController? var loader: MaplyQuadImageLoader? = nil // Source: https://stackoverflow.com/questions/65923718 var cancellable: AnyCancellable? var link: BaseMapLink? { didSet { cancellable = link?.$action.sink(receiveValue: { action in guard let action = action else { return } self.uiViewController?.action(action) }) } } init(_ uiViewController: MapView) { self.parent = uiViewController } func maplyViewController(_ viewC: MaplyViewController, didTapAt coord: MaplyCoordinate) { if let callback = parent.markerCallback { callback(nil) } } func maplyViewController(_ viewC: MaplyViewController, didSelect selectedObj: NSObject, atLoc coord: MaplyCoordinate, onScreen screenPt: CGPoint) { if let marker = selectedObj as? MaplyScreenMarker { if let id = marker.userObject as? Int32 { if let callback = parent.markerCallback { callback(id) } } } } } func makeCoordinator() -> Coordinator { Coordinator(self) } func makeUIViewController(context: Context) -> OurMaplyViewController { let mapViewController = OurMaplyViewController(mapType: .typeFlat) mapViewController.delegate = context.coordinator let tileInfo = Utils.tileInfoFrom(layer: mapLayer) mapViewController.setZoomLimits(minZoom: mapLayer.minZoom, maxZoom: mapLayer.maxZoom) let sampleParams = MaplySamplingParams() sampleParams.coordSys = MaplySphericalMercator(webStandard: ()) sampleParams.coverPoles = true sampleParams.edgeMatching = true sampleParams.minZoom = tileInfo.minZoom sampleParams.maxZoom = tileInfo.maxZoom sampleParams.singleLevel = true sampleParams.maxTiles = 25 let loader = MaplyQuadImageLoader(params: sampleParams, tileInfo: tileInfo, viewC: mapViewController) loader?.baseDrawPriority = kMaplyImageLayerDrawPriorityDefault loader?.imageFormat = .imageUShort565 context.coordinator.loader = loader DispatchQueue.main.async { let point = MaplyCoordinateMakeWithDegrees(-100.36, 23.191) mapViewController.focusOn(point: point, height: 0.4, animated: false) } return mapViewController } func updateUIViewController(_ uiViewController: OurMaplyViewController, context: Context) { context.coordinator.uiViewController = uiViewController context.coordinator.link = link // MARK: - Set map layer context.coordinator.loader?.changeTileInfo(Utils.tileInfoFrom(layer: mapLayer)) uiViewController.setZoomLimits(minZoom: mapLayer.minZoom, maxZoom: mapLayer.maxZoom) // MARK: - Set markers uiViewController.display(markers: markers, isReport: false) } static func dismantleUIViewController(_ uiViewController: MaplyViewController, coordinator: Coordinator) { coordinator.loader?.shutdown() uiViewController.teardown() } } class OurMaplyViewController: MaplyViewController { enum Action { case zoomIn case zoomOut } private var objects = [MaplyComponentObject]() private var geofenceObjects = [MaplyComponentObject]() func action(_ action: Action) { DispatchQueue.main.async { switch action { case .zoomIn: self.zoomIn() case .zoomOut: self.zoomOut() } } } func focusOn(point: MaplyCoordinate, height: Float = 0.0000264, animated: Bool = true) { let z = max(height, getMinZoom()) if animated { animate(toPosition: point, height: z, time: 0.2) } else { setPosition(point, height: z) } } func zoomIn() { let pos = getPosition() let zoom = currentMapScale() / 2 focusOn(point: pos, height: height(forMapScale: zoom)) } func zoomOut() { let pos = getPosition() let zoom = currentMapScale() * 2 focusOn(point: pos, height: height(forMapScale: zoom)) } func clear(geofences: Bool = false) { if geofences { remove(geofenceObjects) geofenceObjects.removeAll() } else { remove(objects) objects.removeAll() } } func display(markers: [Marker], isReport: Bool, center: Bool = false) { clear() let points = markers.map { marker in MaplyCoordinateMakeWithDegrees(Float(marker.longitude), Float(marker.latitude)) } let fontSize = 11.0 let colorReport = Color.green let colorLabel = Color.secondary let colorLabelOutline = Color.systemBackground let vectorDesc: [AnyHashable : Any] = [ kMaplyColor: colorReport, kMaplyVecWidth: 20.0 ] let labelDesc: [AnyHashable : Any] = [ kMaplyFont: UIFont.boldSystemFont(ofSize: fontSize), kMaplyTextColor: colorLabel, kMaplyTextOutlineColor: colorLabelOutline, kMaplyTextOutlineSize: 3.0 ] /* MARK: - Draw markers for positions */ let screenMarkers = markers.enumerated().map { (i, marker) -> MaplyScreenMarker in let screenMarker = MaplyScreenMarker() screenMarker.layoutImportance = .greatestFiniteMagnitude screenMarker.loc = MaplyCoordinateMakeWithDegrees(Float(marker.longitude), Float(marker.latitude)) var type: Marker.Type_ = .default_ if isReport { // For reports, position, start and end icons must be different switch i { case markers.startIndex: type = .reportStart case markers.endIndex: type = .reportEnd default: type = .reportPosition } } else { type = marker.type } screenMarker.image = getIcon(markerType: type) var size = 50.0 if isReport { // For reports, position, start and end sizes must be different switch i { case markers.startIndex: size = 50.0 case markers.endIndex: size = 50.0 default: size = 25.0 } } screenMarker.size = CGSize(width: size, height: size) screenMarker.userObject = marker.id screenMarker.selectable = true return screenMarker } if let objs = addScreenMarkers(screenMarkers, desc: nil, mode: .any) { objects.append(objs) } /* MARK: - Add labels for markers */ if !isReport && !markers.isEmpty { let screenLabels = markers.map { marker -> MaplyScreenLabel in let label = MaplyScreenLabel() label.layoutImportance = .greatestFiniteMagnitude var text = marker.name if marker.name.count >= 20 { let end = marker.name.index(marker.name.startIndex, offsetBy: 20) text = String(marker.name[.. UIImage { return UIImage(named: MarkerTransformations .markerTypeToImageName(markerType: markerType))! } } // Source: https://stackoverflow.com/questions/65923718 class BaseMapLink: ObservableObject { @Published var action: OurMaplyViewController.Action? func zoomIn() { action = .zoomIn } func zoomOut() { action = .zoomOut } }