11import UIKit
22@_spi ( Experimental) import MapboxMaps
33
4- final class BuildingExtrusionsExample : UIViewController , ExampleProtocol {
5- private var cancelables = Set < AnyCancelable > ( )
6-
7- private lazy var lightPositionButton : UIButton = {
8- let button = UIButton ( type: . system)
4+ extension UIButton {
5+ static func exampleActionButton( ) -> UIButton {
6+ let button = UIButton ( type: . custom)
97 button. translatesAutoresizingMaskIntoConstraints = false
108 button. backgroundColor = . systemBlue
119 button. tintColor = . white
1210 button. layer. cornerRadius = 4
1311 button. clipsToBounds = true
14- button. contentEdgeInsets = UIEdgeInsets ( top: 4 , left: 4 , bottom: 4 , right: 4 )
15- if #available( iOS 13 . 0 , * ) {
12+ button. contentEdgeInsets = UIEdgeInsets ( top: 8 , left: 8 , bottom: 8 , right: 8 )
13+ return button
14+ }
15+ }
16+
17+ final class BuildingExtrusionsExample : UIViewController , ExampleProtocol {
18+ private var cancelables = Set < AnyCancelable > ( )
19+
20+ private lazy var lightPositionButton : UIButton = {
21+ let button = UIButton . exampleActionButton ( )
22+ if #available( iOS 13 . 1 , * ) {
1623 button. setImage ( UIImage ( systemName: " flashlight.on.fill " ) , for: . normal)
1724 } else {
1825 button. setTitle ( " Position " , for: . normal)
1926 }
20- button. addTarget ( self , action: #selector( lightPositionButtonTapped ( _: ) ) , for: . touchUpInside )
27+ button. addTarget ( self , action: #selector( lightPositionButtonTapped ( _: ) ) , for: . primaryActionTriggered )
2128 return button
2229 } ( )
2330
2431 private lazy var lightColorButton : UIButton = {
25- let button = UIButton ( type: . system)
26- button. translatesAutoresizingMaskIntoConstraints = false
27- button. backgroundColor = . systemBlue
28- button. tintColor = . white
29- button. layer. cornerRadius = 4
30- button. clipsToBounds = true
31- button. contentEdgeInsets = UIEdgeInsets ( top: 10 , left: 10 , bottom: 10 , right: 10 )
32+ let button = UIButton . exampleActionButton ( )
3233 if #available( iOS 13 . 0 , * ) {
3334 button. setImage ( UIImage ( systemName: " paintbrush.fill " ) , for: . normal)
3435 } else {
3536 button. setTitle ( " Color " , for: . normal)
3637 }
37- button. addTarget ( self , action: #selector( lightColorButtonTapped ( _: ) ) , for: . touchUpInside )
38+ button. addTarget ( self , action: #selector( lightColorButtonTapped ( _: ) ) , for: . primaryActionTriggered )
3839 return button
3940 } ( )
4041
42+ private lazy var heightAlignmentButton : UIButton = {
43+ let button = UIButton . exampleActionButton ( )
44+
45+ if #available( iOS 15 . 0 , * ) {
46+ button. setImage ( UIImage ( systemName: " align.vertical.top " ) , for: . normal)
47+ button. setImage ( UIImage ( systemName: " align.vertical.top.fill " ) , for: . selected)
48+ } else {
49+ button. setTitle ( " Height Alignment " , for: . normal)
50+ }
51+ button. addTarget ( self , action: #selector( heightAlignmentButtonTapped ( _: ) ) , for: . primaryActionTriggered)
52+ return button
53+ } ( )
54+
55+ private lazy var baseAlignmentButton : UIButton = {
56+ let button = UIButton . exampleActionButton ( )
57+
58+ if #available( iOS 15 . 0 , * ) {
59+ button. setImage ( UIImage ( systemName: " align.vertical.bottom " ) , for: . normal)
60+ button. setImage ( UIImage ( systemName: " align.vertical.bottom.fill " ) , for: . selected)
61+ } else {
62+ button. setTitle ( " Height Alignment " , for: . normal)
63+ }
64+ button. addTarget ( self , action: #selector( baseAlignmentButtonTapped ( _: ) ) , for: . primaryActionTriggered)
65+ return button
66+ } ( )
67+
68+ private lazy var terrainSwitchButton : UIButton = {
69+ let button = UIButton . exampleActionButton ( )
70+ if #available( iOS 15 . 0 , * ) {
71+ button. setImage ( UIImage ( systemName: " mountain.2 " ) , for: . normal)
72+ button. setImage ( UIImage ( systemName: " mountain.2.fill " ) , for: . selected)
73+ } else {
74+ button. setTitle ( " Terrain " , for: . normal)
75+ }
76+
77+ button. addTarget ( self , action: #selector( terrainButtonTapped ( _: ) ) , for: . primaryActionTriggered)
78+ return button
79+ } ( )
80+
81+ lazy var buttons = [
82+ heightAlignmentButton,
83+ baseAlignmentButton,
84+ lightPositionButton,
85+ lightColorButton,
86+ terrainSwitchButton
87+ ]
88+
4189 private var ambientLight : AmbientLight = {
4290 var light = AmbientLight ( )
4391 light. color = . constant( StyleColor ( . blue) )
@@ -68,21 +116,27 @@ final class BuildingExtrusionsExample: UIViewController, ExampleProtocol {
68116 self . setupExample ( )
69117 } . store ( in: & cancelables)
70118
71- view. addSubview ( lightPositionButton)
72- view. addSubview ( lightColorButton)
119+
120+ buttons. forEach ( view. addSubview ( _: ) )
121+ terrainSwitchButton. isSelected = isTerrainEnabled
122+
123+ let accessoryButtonsStackView = UIStackView ( arrangedSubviews: buttons)
124+ accessoryButtonsStackView. axis = . vertical
125+ accessoryButtonsStackView. spacing = 20
126+ accessoryButtonsStackView. translatesAutoresizingMaskIntoConstraints = false
127+
128+ view. addSubview ( accessoryButtonsStackView)
73129
74130 NSLayoutConstraint . activate ( [
75- view. trailingAnchor. constraint ( equalToSystemSpacingAfter: lightPositionButton. trailingAnchor, multiplier: 1 ) ,
76- view. bottomAnchor. constraint ( equalTo: lightPositionButton. bottomAnchor, constant: 100 ) ,
77- view. trailingAnchor. constraint ( equalToSystemSpacingAfter: lightColorButton. trailingAnchor, multiplier: 1 ) ,
78- lightPositionButton. topAnchor. constraint ( equalToSystemSpacingBelow: lightColorButton. bottomAnchor, multiplier: 1 ) ,
79- lightColorButton. widthAnchor. constraint ( equalTo: lightPositionButton. widthAnchor) ,
80- lightColorButton. heightAnchor. constraint ( equalTo: lightPositionButton. heightAnchor)
131+ mapView. ornaments. attributionButton. topAnchor. constraint ( equalToSystemSpacingBelow: accessoryButtonsStackView. bottomAnchor, multiplier: 1 ) ,
132+ view. trailingAnchor
133+ . constraint ( equalToSystemSpacingAfter: accessoryButtonsStackView. trailingAnchor, multiplier: 1 )
81134 ] )
82135 }
83136
84137 internal func setupExample( ) {
85- addBuildingExtrusions ( )
138+ try ! addTerrain ( )
139+ try ! addBuildingExtrusions ( )
86140
87141 let cameraOptions = CameraOptions ( center: CLLocationCoordinate2D ( latitude: 40.7135 , longitude: - 74.0066 ) ,
88142 zoom: 15.5 ,
@@ -97,18 +151,20 @@ final class BuildingExtrusionsExample: UIViewController, ExampleProtocol {
97151 }
98152
99153 // See https://docs.mapbox.com/mapbox-gl-js/example/3d-buildings/ for equivalent gl-js example
100- internal func addBuildingExtrusions( ) {
154+ internal func addBuildingExtrusions( ) throws {
101155 let wallOnlyThreshold = 20
102156 let extrudeFilter = Exp ( . eq) {
103157 Exp ( . get) { " extrude " }
104158 " true "
105159 }
106160 var layer = FillExtrusionLayer ( id: " 3d-buildings " , source: " composite " )
161+ . minZoom ( 15 )
162+ . sourceLayer ( " building " )
163+ . fillExtrusionColor ( . lightGray)
164+ . fillExtrusionOpacity ( 0.8 )
165+ . fillExtrusionAmbientOcclusionIntensity ( 0.3 )
166+ . fillExtrusionAmbientOcclusionRadius ( 3.0 )
107167
108- layer. minZoom = 15
109- layer. sourceLayer = " building "
110- layer. fillExtrusionColor = . constant( StyleColor ( . lightGray) )
111- layer. fillExtrusionOpacity = . constant( 0.6 )
112168
113169 layer. filter = Exp ( . all) {
114170 extrudeFilter
@@ -141,13 +197,10 @@ final class BuildingExtrusionsExample: UIViewController, ExampleProtocol {
141197 }
142198 )
143199
144- layer. fillExtrusionAmbientOcclusionIntensity = . constant( 0.3 )
145-
146- layer. fillExtrusionAmbientOcclusionRadius = . constant( 3.0 )
147-
148- try ! mapView. mapboxMap. addLayer ( layer)
200+ try mapView. mapboxMap. addLayer ( layer)
149201
150202 var wallsOnlyExtrusionLayer = layer
203+ . fillExtrusionLineWidth ( 2 )
151204 wallsOnlyExtrusionLayer. id = " 3d-buildings-wall "
152205 wallsOnlyExtrusionLayer. filter = Exp ( . all) {
153206 extrudeFilter
@@ -157,13 +210,75 @@ final class BuildingExtrusionsExample: UIViewController, ExampleProtocol {
157210 }
158211 }
159212
160- wallsOnlyExtrusionLayer. fillExtrusionLineWidth = . constant( 2 )
213+ try mapView. mapboxMap. addLayer ( wallsOnlyExtrusionLayer)
214+ }
161215
162- try ! mapView. mapboxMap. addLayer ( wallsOnlyExtrusionLayer)
216+ func addTerrain( ) throws {
217+ let terrainSourceID = " mapbox-dem "
218+
219+ if !mapView. mapboxMap. sourceExists ( withId: terrainSourceID) {
220+ try addTerrainSource ( id: terrainSourceID)
221+ }
222+
223+ try mapView. mapboxMap. setTerrain ( Terrain ( sourceId: terrainSourceID)
224+ . exaggeration ( 1.5 ) )
225+ }
226+
227+ func addTerrainSource( id: String ) throws {
228+ var demSource = RasterDemSource ( id: id)
229+ demSource. url = " mapbox://mapbox.mapbox-terrain-dem-v1 "
230+ // Setting the `tileSize` to 514 provides better performance and adds padding around the outside
231+ // of the tiles.
232+ demSource. tileSize = 514
233+ demSource. maxzoom = 14.0
234+ try mapView. mapboxMap. addSource ( demSource)
163235 }
164236
165237 // MARK: - Actions
166238
239+ var isTerrainEnabled = true
240+
241+ @objc private func terrainButtonTapped( _ sender: UIButton ) {
242+ if isTerrainEnabled {
243+ mapView. mapboxMap. removeTerrain ( )
244+ } else {
245+ try ! addTerrain ( )
246+ }
247+
248+ isTerrainEnabled. toggle ( )
249+ sender. isSelected = isTerrainEnabled
250+ }
251+
252+ var baseAlignment : FillExtrusionBaseAlignment = . flat
253+ var heightAlignment : FillExtrusionHeightAlignment = . flat
254+
255+ @objc private func baseAlignmentButtonTapped( _ sender: UIButton ) {
256+ if baseAlignment == . flat {
257+ baseAlignment = . terrain
258+ } else {
259+ baseAlignment = . flat
260+ }
261+ sender. backgroundColor = . systemBlue
262+ sender. isSelected = baseAlignment == . terrain
263+
264+ try ! mapView. mapboxMap. updateLayer ( withId: " 3d-buildings " , type: FillExtrusionLayer . self) { layer in
265+ layer. fillExtrusionBaseAlignment = . constant( baseAlignment)
266+ }
267+ }
268+
269+ @objc private func heightAlignmentButtonTapped( _ sender: UIButton ) {
270+ if heightAlignment == . flat {
271+ heightAlignment = . terrain
272+ } else {
273+ heightAlignment = . flat
274+ }
275+ sender. isSelected = heightAlignment == . terrain
276+
277+ try ! mapView. mapboxMap. updateLayer ( withId: " 3d-buildings " , type: FillExtrusionLayer . self) { layer in
278+ layer. fillExtrusionHeightAlignment = . constant( heightAlignment)
279+ }
280+ }
281+
167282 @objc private func lightColorButtonTapped( _ sender: UIButton ) {
168283 if case . constant( let color) = ambientLight. color, color == StyleColor ( . red) {
169284 ambientLight. color = . constant( StyleColor ( . blue) )
0 commit comments