Building extrusion app
Highlight an extruded building on the map.
/*This code example is part of the Mapbox Navigation SDK for iOS demo app,which you can build and run: https://github.com/mapbox/mapbox-navigation-ios-examplesTo learn more about each example in this app, including descriptions and linksto documentation, see our docs: https://docs.mapbox.com/ios/navigation/examples/building-extrusion*/ import UIKitimport MapboxCoreNavigationimport MapboxNavigationimport MapboxDirectionsimport MapboxMaps class BuildingExtrusionViewController: UIViewController, NavigationMapViewDelegate, NavigationViewControllerDelegate, UIGestureRecognizerDelegate { typealias ActionHandler = (UIAlertAction) -> Void var navigationMapView: NavigationMapView! var navigationRouteOptions: NavigationRouteOptions! var currentRouteIndex = 0 {didSet {showCurrentRoute()}} var currentRoute: Route? {return routes?[currentRouteIndex]} var routes: [Route]? {return routeResponse?.routes} var routeResponse: RouteResponse? {didSet {currentRouteIndex = 0 guard currentRoute != nil else {navigationMapView.removeRoutes()navigationMapView.removeWaypoints()waypoints = []return}}} func showCurrentRoute() {guard let currentRoute = currentRoute else { return } var routes = [currentRoute]routes.append(contentsOf: self.routes!.filter {$0 != currentRoute})navigationMapView.show(routes)navigationMapView.showWaypoints(on: currentRoute)} var waypoints: [Waypoint] = [] // MARK: - UIViewController lifecycle methods override func viewDidLoad() {super.viewDidLoad() setupNavigationMapView()setupPerformActionBarButtonItem()setupGestureRecognizers()} // MARK: - Setting-up methods func setupNavigationMapView() {navigationMapView = NavigationMapView(frame: view.bounds)navigationMapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]navigationMapView.delegate = selfnavigationMapView.userLocationStyle = .puck2D() // To make sure that buildings are rendered increase zoomLevel to value which is higher than 16.0.// More details can be found here: https://docs.mapbox.com/vector-tiles/reference/mapbox-streets-v8/#buildinglet navigationViewportDataSource = NavigationViewportDataSource(navigationMapView.mapView, viewportDataSourceType: .raw)navigationViewportDataSource.options.followingCameraOptions.zoomUpdatesAllowed = falsenavigationViewportDataSource.followingMobileCamera.zoom = 17.0navigationMapView.navigationCamera.viewportDataSource = navigationViewportDataSource view.addSubview(navigationMapView)} func setupPerformActionBarButtonItem() {let settingsBarButtonItem = UIBarButtonItem(title: NSString(string: "\u{2699}\u{0000FE0E}") as String, style: .plain, target: self, action: #selector(performAction))settingsBarButtonItem.setTitleTextAttributes([.font: UIFont.systemFont(ofSize: 30)], for: .normal)settingsBarButtonItem.setTitleTextAttributes([.font: UIFont.systemFont(ofSize: 30)], for: .highlighted)navigationItem.rightBarButtonItem = settingsBarButtonItem} // MARK: - UIGestureRecognizer related methods func setupGestureRecognizers() {let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))navigationMapView.addGestureRecognizer(longPressGestureRecognizer) let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))tapGestureRecognizer.delegate = selfnavigationMapView.addGestureRecognizer(tapGestureRecognizer)} @objc func performAction(_ sender: Any) {let alertController = UIAlertController(title: "Perform action",message: "Select specific action to perform it", preferredStyle: .actionSheet) let startNavigation: ActionHandler = { _ in self.startNavigation() }let toggleDayNightStyle: ActionHandler = { _ in self.toggleDayNightStyle() }let unhighlightBuildings: ActionHandler = { _ in self.unhighlightBuildings() }let removeRoutes: ActionHandler = { _ in self.routeResponse = nil } let actions: [(String, UIAlertAction.Style, ActionHandler?)] = [("Start Navigation", .default, startNavigation),("Toggle Day/Night Style", .default, toggleDayNightStyle),("Unhighlight Buildings", .default, unhighlightBuildings),("Remove Routes", .default, removeRoutes),("Cancel", .cancel, nil)] actions.map({ payload in UIAlertAction(title: payload.0, style: payload.1, handler: payload.2) }).forEach(alertController.addAction(_:)) if let popoverController = alertController.popoverPresentationController {popoverController.barButtonItem = navigationItem.rightBarButtonItem} present(alertController, animated: true, completion: nil)} func startNavigation() {guard let routeResponse = routeResponse else {presentAlert(message: "Please select at least one destination coordinate to start navigation.")return} let indexedRouteResponse = IndexedRouteResponse(routeResponse: routeResponse, routeIndex: currentRouteIndex)let navigationService = MapboxNavigationService(indexedRouteResponse: indexedRouteResponse,customRoutingProvider: NavigationSettings.shared.directions,credentials: NavigationSettings.shared.directions.credentials,simulating: simulationIsEnabled ? .always : .onPoorGPS)let navigationOptions = NavigationOptions(navigationService: navigationService)let navigationViewController = NavigationViewController(for: indexedRouteResponse,navigationOptions: navigationOptions)navigationViewController.routeLineTracksTraversal = truenavigationViewController.delegate = selfnavigationViewController.modalPresentationStyle = .fullScreennavigationViewController.navigationMapView?.mapView.mapboxMap.style.uri = navigationMapView.mapView?.mapboxMap.style.uri // Set `waypointStyle` to either `.building` or `.extrudedBuilding` to allow// building highighting in 2D or 3D respectively.navigationViewController.waypointStyle = .extrudedBuilding present(navigationViewController, animated: true, completion: nil)} func toggleDayNightStyle() {let style = navigationMapView.mapView?.mapboxMap.styleif style?.uri == StyleURI.navigationNight {style?.uri = StyleURI.navigationDay} else {style?.uri = StyleURI.navigationNight}} func unhighlightBuildings() {waypoints.forEach({ $0.targetCoordinate = nil })navigationMapView.unhighlightBuildings()} @objc func handleLongPress(_ gesture: UILongPressGestureRecognizer) {guard gesture.state == .began else { return } createWaypoints(for: navigationMapView.mapView.mapboxMap.coordinate(for: gesture.location(in: navigationMapView.mapView)))requestRoute()} @objc func handleTap(_ gesture: UITapGestureRecognizer) {// In case if route is already shown on map do not allow selection of buildings other than final destination.guard currentRoute == nil || navigationRouteOptions == nil else { return }navigationMapView.highlightBuildings(at: [navigationMapView.mapView.mapboxMap.coordinate(for: gesture.location(in: navigationMapView.mapView))], in3D: true)} func createWaypoints(for destinationCoordinate: CLLocationCoordinate2D?) {guard let destinationCoordinate = destinationCoordinate else { return }guard let userCoordinate = navigationMapView.mapView.location.latestLocation?.coordinate else {presentAlert(message: "User coordinate is not valid. Make sure to enable Location Services.")return} // Unhighlight all buildings in case if there are no previous destination waypoints.if waypoints.isEmpty {unhighlightBuildings()} // In case if origin waypoint is not present in list of waypoints - add it.let userLocationName = "User location"let userWaypoint = Waypoint(coordinate: userCoordinate, name: userLocationName)if waypoints.first?.name != userLocationName {waypoints.insert(userWaypoint, at: 0)} // Add destination waypoint to list of waypoints.let waypoint = Waypoint(coordinate: destinationCoordinate)waypoint.targetCoordinate = destinationCoordinatewaypoints.append(waypoint)} func requestRoute() {let navigationRouteOptions = NavigationRouteOptions(waypoints: waypoints)Directions.shared.calculate(navigationRouteOptions) { [weak self] (_, result) inswitch result {case .failure(let error):self?.presentAlert(message: error.localizedDescription) // In case if direction calculation failed - remove last destination waypoint.self?.waypoints.removeLast()case .success(let response):self?.navigationRouteOptions = navigationRouteOptionsself?.routeResponse = responseif let routes = self?.routes {self?.navigationMapView.show(routes)}if let currentRoute = self?.currentRoute {self?.navigationMapView.showWaypoints(on: currentRoute)} if let coordinates = self?.waypoints.compactMap({ $0.targetCoordinate }) {self?.navigationMapView.highlightBuildings(at: coordinates, in3D: true)}}}} // MARK: - NavigationMapViewDelegate methods func navigationMapView(_ mapView: NavigationMapView, didSelect route: Route) {self.currentRouteIndex = self.routes?.firstIndex(of: route) ?? 0} // MARK: - NavigationViewControllerDelegate methods func navigationViewController(_ navigationViewController: NavigationViewController, didArriveAt waypoint: Waypoint) -> Bool {if navigationViewController.navigationService.router.routeProgress.isFinalLeg {return true} // In case of intermediate waypoint - proceed to next leg only after specific delay.let delay = 5.0DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: {guard let navigationService = (self.presentedViewController as? NavigationViewController)?.navigationService else { return }let router = navigationService.routerguard router.route.legs.count > router.routeProgress.legIndex + 1 else { return }router.advanceLegIndex(completionHandler: nil) navigationService.start()}) return false} func navigationViewControllerDidDismiss(_ navigationViewController: NavigationViewController, byCanceling canceled: Bool) {dismiss(animated: true, completion: nil)} // MARK: - UIGestureRecognizerDelegate methods func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {// Allow both route selection and building extrusion when tapping on screen.return true} // MARK: - Utility methods func presentAlert(_ title: String? = nil, message: String? = nil) {let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (_) inalertController.dismiss(animated: true, completion: nil)})) present(alertController, animated: true, completion: nil)}}