aboutsummaryrefslogtreecommitdiff
path: root/iosApp/iosApp/Map
diff options
context:
space:
mode:
Diffstat (limited to 'iosApp/iosApp/Map')
-rw-r--r--iosApp/iosApp/Map/MapView.swift307
-rw-r--r--iosApp/iosApp/Map/MapViewController.xib104
-rw-r--r--iosApp/iosApp/Map/MapWrapperView.swift87
-rw-r--r--iosApp/iosApp/Map/UnitMapView.swift6
4 files changed, 123 insertions, 381 deletions
diff --git a/iosApp/iosApp/Map/MapView.swift b/iosApp/iosApp/Map/MapView.swift
index 572db64..6b0fa20 100644
--- a/iosApp/iosApp/Map/MapView.swift
+++ b/iosApp/iosApp/Map/MapView.swift
@@ -21,310 +21,35 @@ import WhirlyGlobeMaplyComponent
import shared
struct MapView: UIViewControllerRepresentable {
- typealias UIViewControllerType = OurMaplyViewController
+ typealias UIViewControllerType = MapViewController
- @Binding var mapLayer: MapLayer
+ @Binding var layer: MapLayer
@Binding var markers: [Marker]
- var markerCallback: ((Int32?) -> Void)?
- var link: BaseMapLink
+ var markerCallback: MarkerCallback?
- 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)
- }
- }
- }
- }
+ class Coordinator {
+ var shouldCenter: Bool = true
}
func makeCoordinator() -> Coordinator {
- Coordinator(self)
+ return Coordinator()
}
- 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 makeUIViewController(context: Context) -> MapViewController {
+ let mapVC = MapViewController()
+ mapVC.markerCallback = markerCallback
+ mapVC.mapLayer = layer
+ return mapVC
}
- func updateUIViewController(_ uiViewController: OurMaplyViewController, context: Context) {
- context.coordinator.uiViewController = uiViewController
- context.coordinator.link = link
-
+ func updateUIViewController(_ uiViewController: MapViewController, context: Context) {
// MARK: - Set map layer
- context.coordinator.loader?.changeTileInfo(Utils.tileInfoFrom(layer: mapLayer))
- uiViewController.setZoomLimits(minZoom: mapLayer.minZoom,
- maxZoom: mapLayer.maxZoom)
+ uiViewController.setMapLayer(layer)
// 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[..<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)!
- )))
- }
-
- private func getIcon(markerType: Marker.Type_) -> 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
+ isReport: false,
+ center: context.coordinator.shouldCenter)
+ context.coordinator.shouldCenter = false
}
}
diff --git a/iosApp/iosApp/Map/MapViewController.xib b/iosApp/iosApp/Map/MapViewController.xib
new file mode 100644
index 0000000..9d1694f
--- /dev/null
+++ b/iosApp/iosApp/Map/MapViewController.xib
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+ <device id="retina5_9" orientation="portrait" appearance="light"/>
+ <dependencies>
+ <deployment identifier="iOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
+ <capability name="Image references" minToolsVersion="12.0"/>
+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+ <capability name="System colors in document resources" minToolsVersion="11.0"/>
+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+ </dependencies>
+ <objects>
+ <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="MapViewController" customModule="iosApp" customModuleProvider="target">
+ <connections>
+ <outlet property="attributionText" destination="R7q-da-DO1" id="Noy-FR-ZRo"/>
+ <outlet property="mapContainer" destination="T6C-8E-ftt" id="esY-iz-qsl"/>
+ <outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
+ </connections>
+ </placeholder>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+ <view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
+ <rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="XUh-lD-GKm">
+ <rect key="frame" x="317" y="52" width="50" height="50"/>
+ <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+ <constraints>
+ <constraint firstAttribute="height" constant="50" id="SFA-XB-o3z"/>
+ <constraint firstAttribute="width" constant="50" id="ezA-Bu-6Mq"/>
+ </constraints>
+ <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+ <state key="normal">
+ <color key="titleColor" systemColor="labelColor"/>
+ <imageReference key="image" image="plus" catalog="system" symbolScale="large"/>
+ <preferredSymbolConfiguration key="preferredSymbolConfiguration"/>
+ </state>
+ <connections>
+ <action selector="onZoomInPressed:" destination="-1" eventType="touchUpInside" id="x4P-sk-jdi"/>
+ </connections>
+ </button>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="kdu-l3-IK7">
+ <rect key="frame" x="317" y="110" width="50" height="50"/>
+ <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+ <constraints>
+ <constraint firstAttribute="height" relation="greaterThanOrEqual" constant="50" id="2yG-8e-OLn"/>
+ <constraint firstAttribute="width" relation="greaterThanOrEqual" constant="50" id="ACq-Cq-PTq"/>
+ <constraint firstAttribute="width" constant="50" id="Ep3-3d-iue"/>
+ <constraint firstAttribute="height" constant="50" id="gMl-lS-Lvw"/>
+ </constraints>
+ <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+ <state key="normal">
+ <color key="titleColor" systemColor="labelColor"/>
+ <imageReference key="image" image="minus" catalog="system" symbolScale="large"/>
+ <preferredSymbolConfiguration key="preferredSymbolConfiguration"/>
+ </state>
+ <connections>
+ <action selector="onZoomOutPressed:" destination="-1" eventType="touchUpInside" id="rt2-fy-VCy"/>
+ </connections>
+ </button>
+ <textView clipsSubviews="YES" multipleTouchEnabled="YES" userInteractionEnabled="NO" alpha="0.40000000000000002" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" text="Copyright (C) 2020 OpenStreetMap" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="R7q-da-DO1">
+ <rect key="frame" x="0.0" y="745" width="375" height="33"/>
+ <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+ <color key="textColor" systemColor="labelColor"/>
+ <fontDescription key="fontDescription" name=".AppleSystemUIFont" family=".AppleSystemUIFont" pointSize="14"/>
+ <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
+ </textView>
+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="T6C-8E-ftt">
+ <rect key="frame" x="0.0" y="44" width="375" height="734"/>
+ <color key="backgroundColor" systemColor="secondarySystemBackgroundColor"/>
+ </view>
+ </subviews>
+ <viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
+ <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+ <constraints>
+ <constraint firstItem="T6C-8E-ftt" firstAttribute="trailing" secondItem="fnl-2z-Ty3" secondAttribute="trailing" id="Adi-2F-rXH"/>
+ <constraint firstItem="T6C-8E-ftt" firstAttribute="top" secondItem="fnl-2z-Ty3" secondAttribute="top" id="EGw-gK-1tb"/>
+ <constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="XUh-lD-GKm" secondAttribute="trailing" constant="8" id="END-FB-FNk"/>
+ <constraint firstItem="T6C-8E-ftt" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" id="HzR-cl-3zE"/>
+ <constraint firstItem="R7q-da-DO1" firstAttribute="bottom" secondItem="fnl-2z-Ty3" secondAttribute="bottom" id="NMu-yb-3sE"/>
+ <constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="kdu-l3-IK7" secondAttribute="trailing" constant="8" id="Um2-vS-uoQ"/>
+ <constraint firstItem="R7q-da-DO1" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" id="XM1-Gu-sfE"/>
+ <constraint firstItem="kdu-l3-IK7" firstAttribute="top" secondItem="XUh-lD-GKm" secondAttribute="bottom" constant="8" id="hTB-Wm-3eQ"/>
+ <constraint firstItem="R7q-da-DO1" firstAttribute="trailing" secondItem="fnl-2z-Ty3" secondAttribute="trailing" id="heK-zH-Ob4"/>
+ <constraint firstItem="T6C-8E-ftt" firstAttribute="bottom" secondItem="fnl-2z-Ty3" secondAttribute="bottom" id="oT6-iC-7PW"/>
+ <constraint firstItem="XUh-lD-GKm" firstAttribute="top" secondItem="fnl-2z-Ty3" secondAttribute="top" constant="8" id="oXT-Ln-tOw"/>
+ </constraints>
+ <point key="canvasLocation" x="140" y="96.059113300492612"/>
+ </view>
+ </objects>
+ <resources>
+ <image name="minus" catalog="system" width="128" height="24"/>
+ <image name="plus" catalog="system" width="128" height="113"/>
+ <systemColor name="labelColor">
+ <color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+ </systemColor>
+ <systemColor name="secondarySystemBackgroundColor">
+ <color red="0.94901960784313721" green="0.94901960784313721" blue="0.96862745098039216" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+ </systemColor>
+ <systemColor name="systemBackgroundColor">
+ <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+ </systemColor>
+ </resources>
+</document>
diff --git a/iosApp/iosApp/Map/MapWrapperView.swift b/iosApp/iosApp/Map/MapWrapperView.swift
deleted file mode 100644
index 7450eed..0000000
--- a/iosApp/iosApp/Map/MapWrapperView.swift
+++ /dev/null
@@ -1,87 +0,0 @@
-/**
- * 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 SwiftUI
-import Combine
-import WhirlyGlobeMaplyComponent
-import shared
-
-struct MapWrapperView: View {
- @Binding var layer: MapLayer
- @Binding var markers: [Marker]
- var markerCallback: ((Int32?) -> Void)?
- @ObservedObject var link: BaseMapLink = BaseMapLink()
-
- var body: some View {
- ZStack {
- // MARK: - Map
- MapView(mapLayer: $layer,
- markers: $markers,
- markerCallback: markerCallback,
- link: link)
-
- // MARK: - Attribution
- VStack {
- HyperlinkText(html: layer.attribution)
- .font(.footnote)
- .lineLimit(3)
- .foregroundColor(.label.opacity(0.35))
- .padding(6.0)
- .background(.systemBackground.opacity(0.35))
- }
- .frame(maxWidth: .infinity,
- maxHeight: .infinity,
- alignment: .bottom)
- .allowsHitTesting(false)
-
- // MARK: - Controls
- VStack {
- // MARK: Zoom in
- Button {
- print ("Zoom in!")
- link.zoomIn()
- } label: {
- Image(systemName: "plus").imageScale(.large)
- }
- .buttonStyle(ControlButtonStyle())
-
- // MARK: Zoom out
- Button {
- print("Zoom out!")
- link.zoomOut()
- } label: {
- Image(systemName: "minus").imageScale(.large)
- }
- .buttonStyle(ControlButtonStyle())
- }
- .frame(maxWidth: .infinity,
- maxHeight: .infinity,
- alignment: .topTrailing)
- .padding()
- }
- }
-}
-
-struct ControlButtonStyle: ButtonStyle {
- func makeBody(configuration: Configuration) -> some View {
- configuration.label
- .frame(width: 50, height: 50)
- .foregroundColor(configuration.isPressed ? .secondary : .primary)
- .background(configuration.isPressed ? .secondarySystemBackground : .systemBackground)
- .clipShape(Circle())
- }
-}
diff --git a/iosApp/iosApp/Map/UnitMapView.swift b/iosApp/iosApp/Map/UnitMapView.swift
index e1f90a0..5f418f1 100644
--- a/iosApp/iosApp/Map/UnitMapView.swift
+++ b/iosApp/iosApp/Map/UnitMapView.swift
@@ -23,9 +23,9 @@ struct UnitMapView: View {
var body: some View {
ZStack {
- MapWrapperView(layer: $unitsViewModel.mapLayerType,
- markers: $unitsViewModel.markers,
- markerCallback: unitsViewModel.selectUnitWith)
+ MapView(layer: $unitsViewModel.mapLayerType,
+ markers: $unitsViewModel.markers,
+ markerCallback: unitsViewModel.selectUnitWith)
if let unit = unitsViewModel.selectedUnit {
VStack {
DeviceRow(unit: unit, callback: { action in