diff options
Diffstat (limited to 'iosApp/iosApp/Map')
-rw-r--r-- | iosApp/iosApp/Map/MapView.swift | 307 | ||||
-rw-r--r-- | iosApp/iosApp/Map/MapViewController.xib | 104 | ||||
-rw-r--r-- | iosApp/iosApp/Map/MapWrapperView.swift | 87 | ||||
-rw-r--r-- | iosApp/iosApp/Map/UnitMapView.swift | 6 |
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 |