summaryrefslogtreecommitdiff
path: root/platform/ios/scripts/script_resources/MapboxDemo/MapboxDemo/ViewController.swift
blob: 30263274d4edd8bc280c74bd8af710f0f7e9a371 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
import UIKit
import Mapbox

/*
 A simple example that uses the Mapbox Outdoors style to view
 Yosemite National Park, USA with a polygon visualization of the
 park boundary that can be interacted with.
 
 A demo access token has been provided for convenience and it may
 stop working in the future.
 
 You can obtain your own access token from the
 [Mapbox account page](https://www.mapbox.com/studio/account/tokens/)
 and add it to this application's Info.plist as the value for
 MGLMapboxAccessToken
 */
class ViewController: UIViewController {

    @IBOutlet weak var mapViewContainer: UIView!
    @IBOutlet weak var calloutView: UIView!
    @IBOutlet weak var accessTokenWarningView: UIView!
    
    var mapView: MGLMapView!
    
    // Define a name for our annotation
    let annotationIdentifier = "yosemite-fill-layer"
    
    // Define the name of the map label layer we will want our annotation to be placed under
    let styleLabelLayer = "mountain-peak-label"
    
    // The initial center geometric coordinate for the map to display - this is Yosemite National Park, USA
    let centerCoordinate = CLLocationCoordinate2DMake(37.742241, -119.576923)
    
    // MARK: - View controller life cycle
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupCalloutView()
        setupAndAddMapView()
        setupGestureHandling()
    }
    
    // MARK: - Map object interaction
    
    @objc func didTapMap(sender: UITapGestureRecognizer) {
        
        // Search for annotation at tap point
        // If the annotation is found at the tap point then modify some of its properties
        // If the annotation is not found at the tap point, reset the annotation back to the way it was when originally added
        if let layer = mapView.style?.layer(withIdentifier: annotationIdentifier) as? MGLFillExtrusionStyleLayer {
            let point = sender.location(in: mapView)
            let features = mapView.visibleFeatures(at: point, styleLayerIdentifiers: [annotationIdentifier])
            if features.count > 0 {
                layer.fillExtrusionOpacity = MGLStyleValue(rawValue: 0.9)
                layer.fillExtrusionHeight = MGLStyleValue(rawValue: 5000)
                let camera = MGLMapCamera(lookingAtCenter: centerCoordinate, fromDistance: 100000, pitch: 60, heading: 180)
                mapView.fly(to: camera, withDuration: 8, completionHandler: nil)
                calloutView.alpha = 1.0
            } else {
                layer.fillExtrusionOpacity = MGLStyleValue(rawValue: 0.5)
                layer.fillExtrusionHeight = MGLStyleValue(rawValue: 0)
                let camera = MGLMapCamera(lookingAtCenter: centerCoordinate, fromDistance: 100000, pitch: 60, heading: 0)
                mapView.fly(to: camera, withDuration: 1, completionHandler: nil)
                calloutView.alpha = 0
            }
        }
    }
    
    // MARK: - Initialization and setup
    
    /*
     Simple (but somewhat styled) stub of a view to present
     contextual information about tapped objects on the map.
     */
    fileprivate func setupCalloutView() {
        calloutView.alpha = 0
        let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
        blurView.frame = calloutView.bounds
        blurView.layer.cornerRadius = 5
        blurView.clipsToBounds = true
        blurView.translatesAutoresizingMaskIntoConstraints = false
        calloutView.insertSubview(blurView, at: 0)
        blurView.topAnchor.constraint(equalTo: calloutView.topAnchor).isActive = true
        blurView.leftAnchor.constraint(equalTo: calloutView.leftAnchor).isActive = true
        blurView.rightAnchor.constraint(equalTo: calloutView.rightAnchor).isActive = true
        blurView.bottomAnchor.constraint(equalTo: calloutView.bottomAnchor).isActive = true
    }
    
    /*
     Create and add a Mapbox map view to the the view
     intended to contain it. Setup with the style, initial
     location in the world, and make this object the delegate
     of the map. Finally, set up a camera to control how the
     user will view the map initially.
     */
    fileprivate func setupAndAddMapView() {
        mapView = MGLMapView(frame: mapViewContainer.bounds)
        mapView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
        mapView.styleURL = MGLStyle.outdoorsStyleURL(withVersion: 10)
        mapView.setCenter(centerCoordinate, zoomLevel: 10, animated: false)
        mapView.delegate = self
        mapViewContainer.addSubview(mapView)
        
        accessTokenWarningView.layer.cornerRadius = 5
        accessTokenWarningView.isHidden = true
        
        // This demo access token has been provided for convenience and it may
        // stop working in the future.
        // You can obtain your own access token from the
        // [Mapbox account page](https://www.mapbox.com/studio/account/tokens/)
        // and add it to this application's Info.plist as the value for MGLMapboxAccessToken
        if MGLAccountManager.accessToken() == "pk.eyJ1IjoibWFwYm94IiwiYSI6ImNqYThuNnZ3NTA5MGMyd3F1cmF1eW1xaGEifQ.TdBTSHHPeT1pfLZ_6x_1vA" {
            accessTokenWarningView.isHidden = false
        }
        
        let camera = MGLMapCamera(lookingAtCenter: centerCoordinate, fromDistance: 100000, pitch: 60, heading: 0)
        mapView.setCamera(camera, animated: false)
    }
    
    /*
     Add a gesture recognizer that will be used to handle user
     interactions with the map.
     */
    fileprivate func setupGestureHandling() {
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapMap))
        mapView.addGestureRecognizer(tapGestureRecognizer)
    }
}

/*
 Implementation of Mapbox map view delegate methods relevant to this application
 */
extension ViewController: MGLMapViewDelegate {
    
    /*
     When the map has finished loading the style it's a great time
     to do work like adding annotations.
     */
    func mapView(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) {
        // This project's bundle contains a geojson file that represents the park boundary of Yosemite National Park in the USA
        let url = URL(fileURLWithPath: Bundle.main.path(forResource: "yose", ofType: "geojson")!)
        
        // Create an instance of a structure to represent a polygon
        // fill visualization that can be injeced into the map's style layers
        var annotation = StyledAnnotation(identifier: annotationIdentifier, resource: url, type: .extrusion)
        annotation.color = UIColor(red:1.00, green:0.95, blue:0.75, alpha:1.0)
        annotation.opacity = 0.75
        annotation.transitionDuration = 0.35
        
        // Inject the annotation below the label layer so some map labels
        // are still visible
        mapView.addStyledAnnotation(annotation, belowLayer: styleLabelLayer)
        
        // Alternatively, a more traditional annotation / marker API
        let pointFeatureAnnotation = MGLPointFeature()
        let offsetCoordinate = CLLocationCoordinate2D(latitude: centerCoordinate.latitude+0.1, longitude: centerCoordinate.longitude-0.01)
        pointFeatureAnnotation.coordinate = offsetCoordinate
        mapView.addAnnotation(pointFeatureAnnotation)
    }
    
}

/*
 An extention of the Mapbox map view class that offers a simplified
 API for a small subset of Runtime Styling (https://www.mapbox.com/ios-sdk/api/3.6.4/runtime-styling.html)
 used in this application. This could be extended.
 */
extension MGLMapView {
    
    func addStyledAnnotation(_ annotation: StyledAnnotation, belowLayer layerName: String) {
        if let labelsLayer = self.style?.layer(withIdentifier: "mountain-peak-label") {
            let source = addSource(for: annotation)
            let layer = addLayer(for: annotation, source: source)
            self.style?.insertLayer(layer, below: labelsLayer)
        }
    }
    
    func addStyledAnnotation(_ annotation: StyledAnnotation) {
        let source = addSource(for: annotation)
        let layer = addLayer(for: annotation, source: source)
        self.style?.addLayer(layer)
    }
    
    func addSource(for styledAnnotation: StyledAnnotation) -> MGLSource {
        let source = MGLShapeSource(identifier: UUID().uuidString, url: styledAnnotation.resource, options: nil)
        self.style?.addSource(source)
        return source
    }
    
    func addLayer(for styledAnnotation: StyledAnnotation, source: MGLSource) -> MGLStyleLayer {
        switch styledAnnotation.type {
        case .extrusion:
            let layer = MGLFillExtrusionStyleLayer(identifier: styledAnnotation.identifier, source: source)
            if let height = styledAnnotation.height {
                layer.fillExtrusionHeight = MGLStyleValue<NSNumber>(rawValue: NSNumber(floatLiteral: Double(height)))
            }
            if let color = styledAnnotation.color {
                layer.fillExtrusionColor = MGLStyleValue(rawValue: color)
            }
            if let opacity = styledAnnotation.opacity {
                layer.fillExtrusionOpacity = MGLStyleValue<NSNumber>(rawValue: NSNumber(floatLiteral: Double(opacity)))
            }
            if let duration = styledAnnotation.transitionDuration {
                layer.fillExtrusionHeightTransition = MGLTransitionMake(Double(duration), 0)
                layer.fillExtrusionOpacityTransition = MGLTransitionMake(Double(duration), 0)
            }
            return layer
        }
    }
    
}

/*
 A struct to represent a small subset of the annotations that
 are possible to add to a Mapbox map. This could be extended.
 */
struct StyledAnnotation {
    
    enum StyledAnnotationType {
        case extrusion
    }
    
    init(identifier: String, resource: URL, type: StyledAnnotationType) {
        self.identifier = identifier
        self.resource = resource
        self.type = type
    }
    
    var identifier: String
    var resource: URL
    var type: StyledAnnotationType
    
    var height: Float?
    var color: UIColor?
    var opacity: Float?
    var transitionDuration: Float?
    
}