From dac657032851dbd6b13be72253e4f8777c67b439 Mon Sep 17 00:00:00 2001 From: Iván Ávalos Date: Fri, 4 Feb 2022 02:20:29 -0600 Subject: - Center selected marker in map has been implemented. - Initial zoom on map works again. --- iosApp/iosApp/Map/MapView.swift | 11 + iosApp/iosApp/Map/MapViewController.swift | 348 ++++++++++++++++++++++++++++++ iosApp/iosApp/Map/UnitMapView.swift | 1 + iosApp/iosApp/Units/UnitsViewModel.swift | 11 +- 4 files changed, 370 insertions(+), 1 deletion(-) create mode 100644 iosApp/iosApp/Map/MapViewController.swift diff --git a/iosApp/iosApp/Map/MapView.swift b/iosApp/iosApp/Map/MapView.swift index 6b0fa20..989f17a 100644 --- a/iosApp/iosApp/Map/MapView.swift +++ b/iosApp/iosApp/Map/MapView.swift @@ -25,6 +25,7 @@ struct MapView: UIViewControllerRepresentable { @Binding var layer: MapLayer @Binding var markers: [Marker] + @Binding var selected: Marker? var markerCallback: MarkerCallback? class Coordinator { @@ -51,5 +52,15 @@ struct MapView: UIViewControllerRepresentable { isReport: false, center: context.coordinator.shouldCenter) context.coordinator.shouldCenter = false + + // MARK: - Center selected marker + if let selected = selected { + uiViewController.focusOn(marker: selected) + self.selected = nil + } + } + + static func dismantleUIViewController(_ uiViewController: MapViewController, coordinator: Coordinator) { + uiViewController.dismantle() } } diff --git a/iosApp/iosApp/Map/MapViewController.swift b/iosApp/iosApp/Map/MapViewController.swift new file mode 100644 index 0000000..207d0d0 --- /dev/null +++ b/iosApp/iosApp/Map/MapViewController.swift @@ -0,0 +1,348 @@ +/** + * 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 UIKit +import WhirlyGlobeMaplyComponent +import shared + +typealias MarkerCallback = (Int32?) -> () + +class MapViewController: UIViewController { + var markerCallback: MarkerCallback? = nil + var mapLayer: MapLayer = MapLayer.companion.defaultLayer + + @IBOutlet weak var mapContainer: UIView! + @IBOutlet weak var attributionText: UITextView! + + private var mapView: OurMaplyViewController! + + override func viewDidLoad() { + super.viewDidLoad() + // MARK: - Initialise MapViewController + mapView = OurMaplyViewController(mapType: .typeFlat) + mapView.delegate = self + view.sendSubviewToBack(mapContainer) + mapContainer.addSubview(mapView.view) + mapView.view.frame = mapContainer.bounds + addChild(mapView) + + setAttributionText(mapLayer) + + // MARK: - Configure MaplyViewController + let tileInfo = Utils.tileInfoFrom(layer: mapLayer) + mapView.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 + + if let loader = MaplyQuadImageLoader(params: sampleParams, + tileInfo: tileInfo, + viewC: mapView) { + mapView.setLoader(loader) + } + + DispatchQueue.main.async { + let point = MaplyCoordinateMakeWithDegrees(-100.36, 23.191) + self.mapView.setPosition(point, height: 0.4) + } + } + + func setMapLayer(_ layer: MapLayer) { + setAttributionText(layer) + mapView.setLayer(layer) + } + + func display(markers: [Marker], + isReport: Bool, + center: Bool = false) { + DispatchQueue.main.async { + self.mapView.display(markers: markers, + isReport: isReport, + center: center) + } + } + + func focusOn(marker: Marker) { + DispatchQueue.main.async { + self.mapView.focusOn(marker: marker) + } + } + + func dismantle() { + mapView.dismantle() + } + + private func setAttributionText(_ layer: MapLayer) { + DispatchQueue.main.async { + self.attributionText.attributedText = HtmlString.htmlToAttrStr( + layer.attribution, + size: UIFont.smallSystemFontSize, + color: UIColor.label) + } + attributionText.sizeToFit() + attributionText.layoutIfNeeded() + } + + @IBAction func onZoomInPressed(_ sender: UIButton) { + mapView.zoomIn() + } + + @IBAction func onZoomOutPressed(_ sender: UIButton) { + mapView.zoomOut() + } +} + +extension MapViewController: MaplyViewControllerDelegate { + + func maplyViewController(_ viewC: MaplyViewController, + didTapAt coord: MaplyCoordinate) { + markerCallback?(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 { + markerCallback?(id) + } + } + } + +} + +class OurMaplyViewController: MaplyViewController { + enum Action { + case zoomIn + case zoomOut + } + + private var loader: MaplyQuadImageLoader? = nil + 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 setLoader(_ loader: MaplyQuadImageLoader) { + self.loader = loader + } + + func setLayer(_ layer: MapLayer) { + loader?.changeTileInfo(Utils.tileInfoFrom(layer: layer)) + setZoomLimits(minZoom: layer.minZoom, + maxZoom: layer.maxZoom) + } + + 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 focusOn(marker: Marker, animated: Bool = true) { + let point = MaplyCoordinateMakeWithDegrees(Float(marker.longitude), + Float(marker.latitude)) + focusOn(point: point, animated: animated) + } + + 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 = UIColor.green + let colorLabel = UIColor.secondaryLabel + let colorLabelOutline = UIColor.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))! + } +} diff --git a/iosApp/iosApp/Map/UnitMapView.swift b/iosApp/iosApp/Map/UnitMapView.swift index 5f418f1..d2ad8a8 100644 --- a/iosApp/iosApp/Map/UnitMapView.swift +++ b/iosApp/iosApp/Map/UnitMapView.swift @@ -25,6 +25,7 @@ struct UnitMapView: View { ZStack { MapView(layer: $unitsViewModel.mapLayerType, markers: $unitsViewModel.markers, + selected: $unitsViewModel.selectedMarker, markerCallback: unitsViewModel.selectUnitWith) if let unit = unitsViewModel.selectedUnit { VStack { diff --git a/iosApp/iosApp/Units/UnitsViewModel.swift b/iosApp/iosApp/Units/UnitsViewModel.swift index dc6236f..8cf4aae 100644 --- a/iosApp/iosApp/Units/UnitsViewModel.swift +++ b/iosApp/iosApp/Units/UnitsViewModel.swift @@ -55,7 +55,16 @@ class UnitsViewModel: ObservableObject { } } @Published var markers: [Marker] = [] - @Published var selectedUnit: UnitInformation? = nil + @Published var selectedUnit: UnitInformation? = nil { + didSet { + if let unit = selectedUnit { + selectedMarker = Marker.companion.fromUnit(unit: unit) + } else { + selectedMarker = nil + } + } + } + @Published var selectedMarker: Marker? = nil @Published var mapLayerType: MapLayer = .companion.defaultLayer @Published var geofences: [Int: Geofence] = [:] @Published var camera: Camera = Camera() -- cgit v1.2.3