aboutsummaryrefslogtreecommitdiff
path: root/iosApp/iosApp/Map/MapView.swift
diff options
context:
space:
mode:
Diffstat (limited to 'iosApp/iosApp/Map/MapView.swift')
-rw-r--r--iosApp/iosApp/Map/MapView.swift313
1 files changed, 307 insertions, 6 deletions
diff --git a/iosApp/iosApp/Map/MapView.swift b/iosApp/iosApp/Map/MapView.swift
index a34bf63..572db64 100644
--- a/iosApp/iosApp/Map/MapView.swift
+++ b/iosApp/iosApp/Map/MapView.swift
@@ -16,14 +16,315 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import SwiftUI
+import Combine
+import WhirlyGlobeMaplyComponent
import shared
-struct MapView: View {
- @StateObject var unitsViewModel: UnitsViewModel
+struct MapView: UIViewControllerRepresentable {
+ typealias UIViewControllerType = OurMaplyViewController
- var body: some View {
- MapWrapperView(layer: $unitsViewModel.mapLayerType,
- markers: $unitsViewModel.markers,
- markerCallback: unitsViewModel.selectUnitWith)
+ @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[..<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
}
}