aboutsummaryrefslogtreecommitdiff
path: root/iosApp
diff options
context:
space:
mode:
Diffstat (limited to 'iosApp')
-rw-r--r--iosApp/iosApp/Map/MapView.swift11
-rw-r--r--iosApp/iosApp/Map/MapViewController.swift348
-rw-r--r--iosApp/iosApp/Map/UnitMapView.swift1
-rw-r--r--iosApp/iosApp/Units/UnitsViewModel.swift11
4 files changed, 370 insertions, 1 deletions
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 <avalos@disroot.org>, Henoch Ojeda <imhenoch@protonmail.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+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[..<end])
+ }
+ label.text = text
+ label.loc = MaplyCoordinateMakeWithDegrees(Float(marker.longitude),
+ Float(marker.latitude))
+ label.offset = CGPoint(x: 0.0, y: 25.0)
+
+ return label
+ }
+
+ if let objs = addScreenLabels(screenLabels, desc: labelDesc) {
+ objects.append(objs)
+ }
+ }
+
+ /* MARK: - Draw polyline for report */
+ if isReport && !markers.isEmpty {
+ let geoJSON: [AnyHashable : Any] = [
+ "type": "FeatureCollection",
+ "features": [
+ [
+ "type": "LineString",
+ "coordinates": points.map({ point in
+ [point.x, point.y]
+ })
+ ]
+ ]
+ ]
+ if let vector = MaplyVectorObject(fromGeoJSONDictionary: geoJSON) {
+ if let objs = addVectors([vector], desc: vectorDesc, mode: .any) {
+ objects.append(objs)
+ }
+ }
+ }
+
+ /* MARK: - Center map to bounds */
+ if center && !markers.isEmpty {
+ let box = MaplyBoundingBoxExpandByFraction(
+ MaplyBoundingBoxFromCoordinates(points, UInt32(points.count)), 0.1)
+ let center = MaplyCoordinate(x: (box.ur.x + box.ll.x) / 2,
+ y: (box.ur.y + box.ll.y) / 2)
+ let zoom = max(findHeight(toViewBounds: box, pos: center), getMinZoom())
+ setPosition(center, height: zoom)
+ }
+ }
+
+ func setZoomLimits(minZoom: Int32, maxZoom: Int32) {
+ setZoomLimitsMin(
+ height(forMapScale: Float(truncating:
+ MapCalculus.companion.zoomLevelToScale(zoom: maxZoom)
+ ?? MapCalculus.companion.zoomLevelToScale(zoom: 21)!
+ )),
+ max: height(forMapScale: Float(truncating:
+ MapCalculus.companion.zoomLevelToScale(zoom: minZoom)
+ ?? MapCalculus.companion.zoomLevelToScale(zoom: 1)!
+ )))
+ }
+
+ func dismantle() {
+ loader?.shutdown()
+ teardown()
+ }
+
+ private func getIcon(markerType: Marker.Type_) -> 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()