NAV Navbar
  • Introduction
  • Version
  • Installation
  • Usage
  • Function & Helpers
  • Database
  • Delegates
  • Models
  • Extra
  • Troubleshooting
  • Introduction

    Welcome to the Proximi.io MapLibre iOS SDK reference, an easy helper to have fully featured map interactions and routing solution.

    A full demo in swift is available upon request.

    Version

    Current stable version is: 5.14.0 with XCode support 13.3.

    Installation

    Installation in managed by Cocoapods, (if you don't already have it installed, see the "Getting Started" guide).

    Add simply a few lines in your file like this:

    pod 'ProximiioMapLibre'
    

    And then proceed installing open your terminal to the directory where the Podfile is stored and run this command:

    pod repo update
    pod install
    

    Usage

    In order to use the ProximiioMapLibre plugin we need to have installed and working Proximiio, in particular add support for Synchronization and caching.

    Here a simple overview on how use it:

    import ProximiioMapLibre
    
    /// setup map and proximiio integration
    fileprivate func setupMap() {
        /// setup map
        let mapToken = Proximiio.sharedInstance()?.token() ?? ""
        Proximiio.sharedInstance()?.delegate = self
    
        let config = ProximiioMapLibreConfiguration(token: mapToken)
        config.showRasterFloorplans = false
        config.showGeoJSONFloorplans = true
    
        /// prepare map
        mapView = MGLMapView(frame: view.frame)
    
        /// setup ProximiioMapLibre handler
        if let mapView = mapView {
            ProximiioMapLibre.shared.setup(mapView: mapView, configuration: config)
            ProximiioMapLibre.shared.initialize { result in
                if result == .success {
                    ProximiioMapLibre.shared.mapNavigation = self
                    ProximiioMapLibre.shared.mapInteraction = self
                }
            }
        }
    }
    
    #import <ProximiioMapLibre/ProximiioMapLibre.h>
    
    - (void) setupMap {
        NSString *mapToken = [[Proximiio sharedInstance] token];
    
        ProximiioMapLibreConfiguration *configuration = [[ProximiioMapLibreConfiguration alloc] initWithToken:mapToken];
    
        self.mapView = [[MGLMapView alloc] initWithFrame:self.view.frame];
    
        [self.view addSubview:self.mapView];
    
        if (self.mapView != NULL) {
            [[ProximiioMapLibre shared] setupWithMapView:self.mapView configuration:configuration apiVersion:@"v5"];
            [[ProximiioMapLibre shared] initialize:^(enum ProximiioMapLibreAuthorizationResult result) {
                if (result == ProximiioMapLibreAuthorizationResultSuccess) {
                    [ProximiioMapLibre shared].mapNavigation = self;
                    [ProximiioMapLibre shared].mapInteraction = self;
                }
            }];
    
        }
    }
    

    Function & Helpers

    ProximiioMapLibre provides you some functions and helpers to fast manage your implementation.

    Here listed the most relevant ones.

    Follow user position

    Library has built-in automation to follow user position, you can enable/disable that with a simple snippet.

    ProximiioMapLibre.shared.followingUser = true
    
    ProximiioMapLibre.shared.followingUser = YES;
    

    Floor

    New map plugin provides easy handlers for map floor navigation:

    /// force the map to change to upper floor
    ProximiioMapLibre.shared.floorUp()
    
    /// force the map to change to lower floor
    ProximiioMapLibre.shared.floorDown()
    
    /// force the map to change to user specified floor
    ProximiioMapLibre.shared.floorAt(5)
    
    /// get current floor of the map
    let floor = ProximiioMapLibre.shared.mapFloor
    
    /// force the map to change to upper floor
    [ProximiioMapLibre.shared floorUp];
    
    /// force the map to change to lower floor
    [ProximiioMapLibre.shared floorDown];
    
    /// force the map to change to user specified floor
    [ProximiioMapLibre.shared floorAt:1];
    
    /// get current floor of the map
    NSUInteger floor = ProximiioMapLibre.shared.mapFloor;
    

    Map position helper

    You can easily manage to move the map with some quick helpers:

    /// center the map to user position
    ProximiioMapLibre.shared.centerAtUser(zoom: Double = 18, animated: Bool = true, completed: { success in
        /// callback on completed
    })
    
    /// center the map to the specified feature
    ProximiioMapLibre.shared.centerAtFeature(_ feature: ProximiioGeoJSON, zoom: Double = 18, animated: Bool = true, completed: { success in
        /// callback on completed
    })
    
    /// center the map to user position
    [ProximiioMapLibre.shared centerAtUserWithZoom:18 animated:YES
                                      completed:^(BOOL) {
    
    }];
    
    /// center the map to the specified feature
    [ProximiioMapLibre.shared centerAtFeature:feature zoom:18 animated:YES completed:^(ProximiioGeoJSON * _Nonnull) {
        /// callback on completed
    }];
    

    Both the two functions expose a callback that can be optionally triggered when the animation ends.

    Routing

    We provide a built-in, offline, solution to calculate routes between user position and a target POI. Once a route has been calculated you can proceed starting/stopping it:

    /// start route
    ProximiioMapLibre.shared.routeStart()
    
    /// stop route
    ProximiioMapLibre.shared.routeCancel(silent: true)
    
    /// start route
    [ProximiioMapLibre.shared routeStart:nil];
    
    /// stop route
    [ProximiioMapLibre.shared routeCancelWithSilent:YES];
    
    

    Routes can be calculate according some specific requirements, we add a PIORouteConfiguration model that take care of this.

    Also in order to provide async support, now the route is returned in a dedicate callback, with an optional PIORoute object.

    We provided also some quick helper for preview and auto start like:

    ProximiioMapLibre.shared.routeFindAndStart(configuration: PIORouteConfiguration, callback: ((PIORoute?) -> Void))
    ProximiioMapLibre.shared.routeFindAndPreview(configuration: PIORouteConfiguration, callback: ((PIORoute?) -> Void))
    
    [ProximiioMapLibre.shared routeFindAndStartWithConfiguration: callaback:]
    [ProximiioMapLibre.shared routeFindAndPreviewWithConfiguration: callaback:]
    
    

    Or search only:

    ProximiioMapLibre.shared.routeFind(configuration: PIORouteConfiguration, callback: ((PIORoute?) -> Void))
    
    [ProximiioMapLibre.shared routeFindWithConfiguration: callaback:]
    

    PIORouteConfiguration

    This model provide information and limitation to be applied by the offline algorithm that generates the route to a POI.

    The tweakable values are:

    let options = PIORouteConfiguration(
        start: ProximiioGeoJSON,
        destination: ProximiioGeoJSON,
        waypointList: [ProximiioWaypoint],
        wayfindingOptions: PIOWayfindingOptions
    )
    

    Let's check one by one:

    PIOWaypoint

    Waypoints are intermediary steps for a path, SDK actually supports two types: SimpleWaypoint and VariableWaypoint.

    Simple support just a ProximiioGeoJSON as feature, instead Variable support an array.

    Basically the difference between the two is that, in Variable, you can provide a list of ProximiioGeoJSON (e.g. a list of parkings) and the wayfinding algorithm will take care of figure out the best that fit the route between your from and destination.

    An example of usage is

    let waypointSimple = SimpleWaypoint(feature: feature)
    let waypointVariable = VariableWaypoint(features: [feature1, feature2, feature3])
    

    PIOWayfidingOptions

    PIOWayfidingOptions is an evolution of previous model PIORouteOptions.

    Here an example of implementation:

    let options = PIOWayfidingOptions()
    options.avoidBarriers = false
    options.avoidElevators = false
    options.avoidEscalators = false
    options.avoidNarrowPaths = false
    options.avoidRamps = false
    options.avoidRevolvingDoors = false
    options.avoidStairs = false
    options.avoidTicketGates = false
    options.pathFixDistance = 1.0
    

    By default all the values are set to false.

    PIOAnnotation

    PIOAnnotation allows you to add custom annotation on the marker with extra level support. Also it support onTap callback returning the object that triggered the event.

    let annotation = PIOAnnotationWithData()
    annotation.coordinate = CLLocationCoordinate2D(latitude: 60.1671950369849, longitude: 24.921695923476054)
    annotation.image = UIImage.add
    annotation.id = 0
    annotation.data = 1
    annotation.title = "A test annotation at level 0"
    annotation.level = 0
    annotation.onTap = { current in
        print("TAPPED")
    }
    
    let annotation2 = PIOAnnotationWithData()
    annotation2.coordinate = CLLocationCoordinate2D(latitude: 60.16746993048443, longitude: 24.922311676612107)
    annotation2.image = UIImage.remove
    annotation2.id = 1
    annotation2.data = 2
    annotation2.title = "A second annotation at level 0"
    annotation2.level = 1
    annotation2.onTap = { current in
        guard let casted = current as? PIOAnnotationWithData else { return }
        print("TAPPED \(casted.id)")
    }
    
    let annotation3 = PIOAnnotationWithData()
    annotation3.image = UIImage.actions
    annotation3.coordinate = CLLocationCoordinate2D(latitude: 60.16714117139544, longitude: 24.92290485238969)
    annotation3.id = 2
    annotation3.data = 3
    annotation3.title = "A third annotation at level 0"
    annotation3.level = 0
    annotation3.onTap = { current in
        guard let casted = current as? PIOAnnotationWithData else { return }
        print("TAPPED \(casted.id) \(casted.data)")
    }
    

    In this example we are able to create a custom annotation, visible at a specific position and for a specific level. If the map changes the shown level, the annotation will be shown/hidden according.

    Once we have an annotation in place we can render that using MapLibre function:

    mapView?.addAnnotations([annotation, annotation2, annotation3])
    

    The solution allows you to set 1 to N annotations, is just enough to create all the single annotations and then use addAnnotations passing an array of annotation.

    In order to support the onTap callback, you will have to tweak the code you use to rendere the marker with something like:

    public func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
    
            if let current = annotation as? PIOAnnotation {
    
                // weak identifier, if you have an ID prefer to use that
                let identifier = "\(current.coordinate.longitude)\(current.level)"
    
                var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
    
                if annotationView == nil {
                    annotationView = MGLAnnotationView(annotation: annotation, reuseIdentifier: identifier)
                    let imageView = UIImageView(image: current.image)
                    annotationView!.frame = CGRect(x: 0, y: 0, width: current.image.size.width, height: current.image.size.height)
                    annotationView?.addSubview(imageView)
                    annotationView?.centerOffset = CGVector(dx: 0, dy: -current.image.size.height / 2.0)
                    // introduces onTap callback
                    annotationView?.addTapGesture(handler: { gesture in
                        current.onTap?(current)
                    })
                }
    
                // handles hiding according the floor
                annotationView?.isHidden = current.isHidden
    
                return annotationView
            }
    
            return nil
        }
    

    Database

    ProximiioMapLibre has an embedded local database to keep POI information.

    You can easily access it using PIODatabase.sharedInstance() shared object.

    let db = PIODatabase.sharedInstance()
    
    // amenities
    let amenities = db.amenities
    
    // features
    let poi = db.features
    
    // poi
    let poi = db.pois
    
    // poi and level changers
    let poi = db.poisAndLevelChanger
    
    ProximiioDatabase *db = PIODatabase.sharedInstance;
    
    // amenities
    let amenities = db.amenities
    
    // features
    let poi = db.features
    
    // poi
    let poi = db.pois
    
    // poi and level changers
    let poi = db.poisAndLevelChanger
    

    Each is an array of elements of type ProximiioGeoJSON that provide out of the box helpers for different information about the entity.

    ProximiioGeoJSON

    ProximiioGeoJSON model has automated localization handling for some fields like description and title. In order to use feel free to use helpers like getTitle().

    Delegates

    ProximiioMapLibre exposes two delegates with different scopes:

    Their implementation and naming can have some difference between swift and objective-c implementation, refer below.

    ProximiioMapLibreInteraction

    Takes care of providing delegate for interaction on the mapbox instance like: changing floor, tapping on map or request a re-route.

    - (void)changeWithFloor:(NSInteger)floor;
    - (void)onTapWithFeature:(ProximiioGeoJSON * _Nonnull)feature;
    - (void)onRequestReRoute;
    
        func change(floor: Int) {
            // triggered when floor has changed
        }
    
        func onTap(feature: ProximiioGeoJSON) {
            // triggered if user taps on a POI
        }
    
        func onRequestReRoute() {
            // triggered if a re-route is manually requested
        }
    

    ProximiioMapLibreNavigation

    Takes care of providing delegate for events connected to a navigation.

    - (void)onRouteWithRoute:(PIORoute * _Nullable)route;
    - (void)routeEventWithEventType:(enum PIORouteUpdateType)type text:(NSString * _Nonnull)text additionalText:(NSString * _Nullable)additionalText data:(PIORouteUpdateData * _Nullable)data;
    - (void)onHazardEntered:(ProximiioGeoJSON * _Nonnull)hazard;
    - (void)onSegmentEntered:(ProximiioGeoJSON * _Nonnull)segment;
    - (void)onDecisionEntered:(ProximiioGeoJSON * _Nonnull)decision;
    - (void)onLandmarkEntered:(NSArray<PIOLandmark *> * _Nonnull)landmark;
    - (void)onHazardExit:(ProximiioGeoJSON * _Nonnull)hazard;
    - (void)onSegmentExit:(ProximiioGeoJSON * _Nonnull)segment;
    - (void)onDecisionExit:(ProximiioGeoJSON * _Nonnull)decision;
    - (void)onLandmarkExit:(NSArray<ProximiioGeoJSON *> * _Nonnull)landmark;
    
        func onRoute(route: PIORoute?) {
            // triggered if a route has been found
        }
    
        func routeEvent(eventType type: PIORouteUpdateType, text: String, additionalText: String?, data: PIORouteUpdateData?) {
            // triggered if a route event happen
        }
    
        func onHazardEntered(_ hazard: ProximiioGeoJSON) {
            // triggered if user is close to hazard
        }
    
        func onSegmentEntered(_ segment: ProximiioGeoJSON) {
            // triggered if user has entered a new segment
        }
    
        func onPositionUpdate(_ position: CLLocationCoordinate2D) {
            // useful to have current user position
        }
    
        func onHeadingUpdate(_ heading: Double) {
            // useful to have current user heading
        }
    
        func onDecisionEntered(_ decision: ProximiioGeoJSON){
            // useful to have a decision poi
        }
    
        func onLandmarkEntered(_ landmark: [PIOLandmark]){
            // useful to have landmarks information
        }
    
        func onHazardExit(_ hazard: ProximiioGeoJSON){
            // hazard exit
        }
    
        func onSegmentExit(_ segment: ProximiioGeoJSON){
            // segment exit
        }
    
        func onDecisionExit(_ decision: ProximiioGeoJSON){
            // decision exit
        }
    
        func onLandmarkExit(_ landmarks: [ProximiioGeoJSON]){
            // landmark exit
        }
    

    Models

    The library makes use of several model and primitives. Here a list of the most relevant, their scope and values.

    PIORouteUpdateType

    This enum is used on routeEvent response, it easily provide you information about how to handle the visual interfance for the navigation.

    Supported values are the following:

    @objc public enum PIORouteUpdateType: Int {
        case calculating, recalculating // if route is being calculate or re-calculated
        case routeNotfound // if route fails to be generated
        case canceled // if the user dimiss a route
        case  finished // if the user has reached the end of a route
        // priority of the update
        case new, update, soon, immediate
    }
    

    PIORouteUpdateData

    This model provides a reference to the current node of the route involved in an update. If exists it provides also a reference to the next node with a similar structure (but with optional values).

    @objc public class PIORouteUpdateData: NSObject {
        /// current index of the node is processed from the route nodeList
        @objc public let nodeIndex: Int
        /// bearing of current node
        @objc public let stepBearing: Double
        /// direction
        @objc public let stepDirection: PIOGuidanceDirection
        /// distance of the node
        @objc public let stepDistance: Double
    
        // optional reference to the next step
        @objc public let nextStepBearing: Double?
        @objc public let nextStepDistance: Double?
        @objc public let nextStepDirection: PIOGuidanceDirection?
    
        /// current position
        @objc public let position: CLLocationCoordinate2D
    
        /// remaining distance in meters
        @objc public let pathLengthRemaining: Double
    
        public var stepHeading: PIOHeading {
            return calculateHeading(stepBearing.degreeNormalized)
        }
    }
    

    PIOGuidanceDirection

    This model provide an helper to the direction of the user.

    @objc public enum PIOGuidanceDirection: Int, Equatable {
        case start // beginning of the route
        case turnAround // when user need to change direction
        case finish // user reaches the end
        case straight // continue straight
        /// turn types
        case leftSlight, leftNormal, leftHard
        case rightSlight, rightNormal, rightHard
        /// level change types
        case upElevator, upEscalator, upStairs
        case downElevator, downEscalator, downStairs
    }
    

    The type of turn is calculated agains the degree between user position and node according to this logic:

        /// return current information about guidance direction
         func degreesToStepDirection() -> PIOGuidanceDirection {
            let test = self.degreeNormalized
    
            if fabs(test) < 22.5 { return .straight }
            if fabs(test) > 157.2 { return .turnAround}
            if test < -112.5 { return .leftHard}
            if test <= -67.5 { return .leftNormal}
            if test <= -22.5 { return .leftSlight}
            if test > 157.5 { return .rightHard}
            if test >= 67.5 { return .rightNormal}
            if test >= 22.5 { return .rightSlight}
    
            return .straight
        }
    

    PIOHeading

    This model provide an helper to the heading of the route.

    @objc public class PIOHeading: NSObject {
        @objc public var text: String
        @objc public var rotation: Double
    }
    

    It exposes a degree value in rotation and a text, localized, about heading direction.

    PIOLandmark

    It takes care of provide information about any landmark the routing system is able to discover close to current user location.

    It provides two sub variables side and feature.

    Side is an enum that give you information on the side the landmark is:

    @objc public enum PIOLandmarkSide: Int {
        case left, right
    }
    

    Feature is a normal ProxmiioGeoJSON object.

    Extra

    Hide Amenities from map

    A new feature allows you to hide amenities from map temporary. If you want for example to run a navigation and hide any other amenity to avoid any disturb to the user you can simply use this helper:

    ProximiioMapLibre.shared.blacklistRenderAmenitiesIds = ["replace:identifier"]
    

    The helper is able to accept as input an array of amenities that we want to remove. If we want to restore the initial contidition, we have just to pass an empty array.

    Preload POI images

    Map can make use of images for POI and Amenities, an interesting way to boost performance and lower time-await is to preload that before app starts.

    In combination with Kingfisher (that you have to install as third party pod), you can easily create a function like the one presented below.

    Looping the PIODatabase will be possible to force pre-caching of the images needed.

    Actually we suggest to run this func when both the syncAmenities and syncFeatures are finished.

    import ProximiioMapLibre
    
    private func preloadFeatureImages(callback: @escaping ((Bool) -> Void)) {
            var completed = 0
            PIODatabase.shared.features.forEach { feature in
                feature.images.forEach {
                    if let imgUrl = URL(string: $0) {
                        completed += 1
                        ImageDownloader.default.downloadImage(with: imgUrl, retrieveImageTask: nil, options: [], progressBlock: nil) { (image, _, url, _) in
                            completed -= 1
    
                            if completed == 0 {
                                callback(true)
                            }
    
                            if let image =  image, let url = url {
                                ImageCache.default.store(image, forKey: url.absoluteString)
                            }
                        }
                    }
                }
            }
        }
    

    Troubleshooting

    If the library produce error on build, try add these few lines at the bottom of your Podfile.

    post_install do |installer|
        installer.pods_project.targets.each do |target|
            target.build_configurations.each do |config|
                config.build_settings['SWIFT_VERSION'] = '5.2'
                config.build_settings['ENABLE_BITCODE'] = 'NO'
            end
        end
    end
    

    Instead if during submisison you receive a warning mail for Too many simbols you can tweak your podfile like below:

    post_install do |installer|
        installer.pods_project.targets.each do |target|
            target.build_configurations.each do |config|
                config.build_settings['DEBUG_INFORMATION_FORMAT'] = 'dwarf'
            end
        end
    end
    

    If needed you can even combine the two above in one solution.