SDK for Indoor Navigation and Beyond

infsoft’s technology is also available as plugins for integration into third party apps. This means that existing applications can be upgraded with infsoft indoor positioning and indoor navigation. The SDK (Software Development Kit) is currently available for the Android and iOS mobile operating systems and as a HTML5 plugin. In addition to a native implementation, the use of frameworks such as PhoneGap or Xamarin is also possible.

Here you can find more information on the different class libraries and their implementation:

iOS

infsoft enables you to build iOS map apps using Mapbox.
This means:

  • Mapbox-style-object generated server side, containing our maps, based on your data.
  • 2D and 3D mode
  • infsoft Locator library to display the user’s position
  • All Mapbox functionality

GETTING STARTED

Resources

The following examples show how the infsoft services can be integrated into the Mapbox SDK.

Documentation for the Mapbox Maps SDK for iOS comes in the form of:

Installation

Install the Mapbox Maps SDK as described in the instructions of the official homepage.
The current supported SDK Version is 5.6 There is no guarantee that the integration will work with other versions.

The step about placing the access token can be skipped.

Official Mapbox iOS documentation

Configuration

No access token has to be set, because the infsoft style is used for the map.

To give your users a brief explanation of how the app will use their location data if you choose to access their location, add NSLocationWhenInUseUsageDescription and NSLocationAlwaysUsageDescription key to the Info.plist file with a description.

Remove attribution

Because no data is hosted by Mapbox itself it is not necessary to include an attribution.

Mapbox attribution

Attribution and telemetry can be disabled in the following way.
Add a MGLMapboxMetricsEnabledSettingShownInApp key to the Info.plist file with the value YES

// let mapView = ...

// Disable attribution and hide buttons
UserDefaults.standard.set(false, forKey: "MGLMapboxMetricsEnabled")
mapView.attributionButton.isHidden = true
mapView.logoView.isHidden = true

DISPLAY MAP

ViewController

  • Replace the apiKey variable with your api key to display your own maps
import UIKit
import Mapbox

class ViewController: UIViewController {
    let styleHost = "tilesservices.webservices.infsoft.com"
    let stylePath = "/api/mapstyle/style/"
    let apiKey = "8c97d7c6-0c3a-41de-b67a-fb7628efba79"
  
    override func viewDidLoad() {
        super.viewDidLoad()

        var components = URLComponents()
        components.scheme = "https"
        components.host = styleHost
        components.path = stylePath + apiKey
      
        let mapView = MGLMapView(frame: view.bounds, styleURL: components.url)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.setCenter(CLLocationCoordinate2D(latitude: 49.867630660511715,
                                                 longitude: 10.89075028896332),
                          zoomLevel: 16,
                          animated: false)
        view.addSubview(mapView)

        // Disable telemetry and hide button
        UserDefaults.standard.set(false, forKey: "MGLMapboxMetricsEnabled")
        mapView.attributionButton.isHidden = true
        mapView.logoView.isHidden = true
    }
}

DISPLAY MAP 3D

ViewController

  • Replace the apiKey variable with your api key to display your own maps
import UIKit
import Mapbox

class ViewController: UIViewController, MGLMapViewDelegate {
    let styleHost = "tilesservices.webservices.infsoft.com"
    let stylePath = "/api/mapstyle/style/"
    let apiKey = "8c97d7c6-0c3a-41de-b67a-fb7628efba79"
    let initial3D = "false"

    override func viewDidLoad() {
        super.viewDidLoad()
      
        var components = URLComponents()
        components.scheme = "https"
        components.host = styleHost
        components.path = stylePath + apiKey
        let queryItemConfig = URLQueryItem(name: "config", 
                                           value: "3d:\(initial3D)")
        components.queryItems = [queryItemConfig]      
      
        let mapView = MGLMapView(frame: view.bounds, styleURL: components.url)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.delegate = self
        mapView.setCenter(CLLocationCoordinate2D(latitude: 49.867630660511715,
                                                 longitude: 10.89075028896332),
                          zoomLevel: 16,
                          animated: false)
        view.addSubview(mapView)

        // Disable telemetry and hide button
        UserDefaults.standard.set(false, forKey: "MGLMapboxMetricsEnabled")
        mapView.attributionButton.isHidden = true
        mapView.logoView.isHidden = true
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
    }

    /// Toggle 2D/3D map
    func setDimension(in mapView: MGLMapView, enabled3D: Bool) {
        guard let style = mapView.style else {
            return
        }

        if enabled3D {
            for layer in style.layers {
                if layer.identifier.contains("-loc3d-") {
                    layer.isVisible = true
                } else if layer.identifier.contains("-loc2d-") {
                    layer.isVisible = false
                }
            }
        } else {
            for layer in style.layers {
                if layer.identifier.contains("-loc3d-") {
                    layer.isVisible = false
                } else if layer.identifier.contains("-loc2d-") {
                    layer.isVisible = true
                }
            }
        }
    }

    // MARK: - Mapbox mapView delegate

    func mapView(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) {
        setDimension(in: mapView, enabled3D: true)
    }
}

SWITCH LEVELS

ViewController

  • Replace the apiKey variable with your api key to display your own maps
import UIKit
import Mapbox

class ViewController: UIViewController, MGLMapViewDelegate {
    let styleHost = "tilesservices.webservices.infsoft.com"
    let stylePath = "/api/mapstyle/style/"
    let apiKey = "8c97d7c6-0c3a-41de-b67a-fb7628efba79"
    let initial3D = "false"

    var currentLevel = 0
    var numberOfLevels = 4
    weak var mapView: MGLMapView?

    override func viewDidLoad() {
        super.viewDidLoad()

        var components = URLComponents()
        components.scheme = "https"
        components.host = styleHost
        components.path = stylePath + apiKey
        let queryItemConfig = URLQueryItem(name: "config", 
                                           value: "3d:\(initial3D)")
        components.queryItems = [queryItemConfig]
      
        let mapView = MGLMapView(frame: view.bounds, styleURL: components.url)
        self.mapView = mapView
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.delegate = self
        mapView.setCenter(CLLocationCoordinate2D(latitude: 49.867630660511715,
                                                 longitude: 10.89075028896332),
                          zoomLevel: 16,
                          animated: false)
        view.insertSubview(mapView, at: 0)

        // Disable telemetry and hide button
        UserDefaults.standard.set(false, forKey: "MGLMapboxMetricsEnabled")
        mapView.attributionButton.isHidden = true
        mapView.logoView.isHidden = true
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
    }

    // MARK: - Actions

    @IBAction private func switchLevel(_ sender: Any) {
        currentLevel = (currentLevel + 1) % numberOfLevels
        guard let mapView = mapView else {
          return
      }
        setLevel(in: mapView, level: currentLevel)   
    }

    // MARK: - Map dimension handling

    /// Toggle 2D/3D map
    func setDimension(in mapView: MGLMapView, enabled3D: Bool) {
        guard let style = mapView.style else {
            return
        }

        if enabled3D {
            for layer in style.layers {
                if layer.identifier.contains("-loc3d-") {
                    layer.isVisible = true
                } else if layer.identifier.contains("-loc2d-") {
                    layer.isVisible = false
                }
            }
        } else {
            for layer in style.layers {
                if layer.identifier.contains("-loc3d-") {
                    layer.isVisible = false
                } else if layer.identifier.contains("-loc2d-") {
                    layer.isVisible = true
                }
            }
        }
    }

    // MARK: - Map level handling

    private func setLevel(in mapView: MGLMapView, level: Int) {
        guard let style = mapView.style else {
            return
        }

        let filterLayers: [MGLVectorStyleLayer] = style.layers.compactMap {
            $0 as? MGLVectorStyleLayer
        }.filter {
            $0.identifier.contains("locls")
        }

        for layer in filterLayers {
            if let predicate = layer.predicate {
                guard let rawData = predicate.mgl_jsonExpressionObject as? [Any] else {
                    return
                }
                let newPredicate = NSPredicate(mglJSONObject: setFilter(filter: rawData, level: level))
                layer.predicate = newPredicate
            }
        }
    }

    func setFilter(filter: Any, level: Int) -> Any {
        guard var predicates = filter as? [Any] else {
            return filter
        }

        for (index, element) in predicates.enumerated() {
            if element as? [Any] != nil {
                predicates[index] = self.setFilter(filter: element, level: level)
                continue
            }

            if index == 0 {
                continue
            }

            guard let prev = predicates[index - 1] as? String else {
                continue
            }

            if predicates[index] as? Int == nil {
                continue
            }

            if prev != "level" {
                continue
            }

            predicates[index] = level
        }
        return predicates
    }

    // MARK: - Mapbox mapView delegate

    func mapView(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) {
        setDimension(in: mapView, enabled3D: true)
    }
}

DISPLAY ROUTE

RouteViewController

  • Replace the apiKey variable with your api key to display your own maps
import UIKit
import Mapbox

class RouteViewController: UIViewController, MGLMapViewDelegate {
    let styleHost = "tilesservices.webservices.infsoft.com"
    let stylePath = "/api/mapstyle/style/"
    let apiKey = "8c97d7c6-0c3a-41de-b67a-fb7628efba79"
    let initial3D = "false"

    override func viewDidLoad() {
        super.viewDidLoad()

        var components = URLComponents()
        components.scheme = "https"
        components.host = styleHost
        components.path = stylePath + apiKey
        let queryItemConfig = URLQueryItem(name: "config", 
                                           value: "3d:\(initial3D)")
        components.queryItems = [queryItemConfig]

        let mapView = MGLMapView(frame: view.bounds, styleURL: components.url)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.delegate = self
        mapView.setCenter(CLLocationCoordinate2D(latitude: 49.867630660511715,
                                                 longitude: 10.89075028896332),
                          zoomLevel: 18,
                          animated: false)
        view.insertSubview(mapView, at: 0)

        // Disable telemetry and hide button
        UserDefaults.standard.set(false, forKey: "MGLMapboxMetricsEnabled")
        mapView.attributionButton.isHidden = true
        mapView.logoView.isHidden = true
    }

    /// This creates a valid URL for requesting route information
    /// - Parameters:
    ///   - startLat: Latitude of starting point
    ///   - startLon: Longitude of starting point
    ///   - startLevel: Level ID of starting point
    ///   - endLat: Latitude of endpoint
    ///   - endLon: Longitude of endpoint
    ///   - endLevel: Level ID of endpoint
    ///   - apiKey: To be used apiKey
    ///   - localization: Localication identifier, eg. "DE", "EN"
    ///   - wayContext: Waycontext for route calculation. Empty string is the default value
    /// - Returns: A route service URL for the given parameters
    func createRouteURL(startLat: Double, startLon: Double, startLevel: Int, endLat: Double, endLon: Double, 
                        endLevel: Int, apiKey: String, localization: String = "EN", wayContext: String = "") -> URL {
        var compoments = URLComponents()
        compoments.scheme = "https"
        compoments.host = "routes.webservices.infsoft.com"
        compoments.path = "/API/Calc"
        let queryItemAPIKey = URLQueryItem(name: "apikey", value: apiKey)
        let queryItemStartLat = URLQueryItem(name: "startlat", value: "\(startLat)")
        let queryItemStartLon = URLQueryItem(name: "startlon", value: "\(startLon)")
        let queryItemStartLevel = URLQueryItem(name: "startlevel", value: "\(startLevel)")
        let queryItemEndLat = URLQueryItem(name: "endlat", value: "\(endLat)")
        let queryItemEndLon = URLQueryItem(name: "endlon", value: "\(endLon)")
        let queryItemEndLevel = URLQueryItem(name: "endlevel", value: "\(endLevel)")
        let queryItemLocalization = URLQueryItem(name: "lcid", value: "\(localization)")
        let queryItemWayContext = URLQueryItem(name: "context", value: "\(wayContext)")

        compoments.queryItems = [queryItemAPIKey, queryItemStartLat, queryItemStartLon,
                                 queryItemStartLevel, queryItemEndLat, queryItemEndLon, 
                                 queryItemEndLevel,
                                 queryItemLocalization, queryItemWayContext]

        return compoments.url!
    }

    func renderGeoJSON(from route: Route, in mapView: MGLMapView) {
        guard let routeElement = route.first?.geoJSON, let geoJSONData = try? JSONEncoder().encode(routeElement) else {
            return
        }

        guard let shapeFromGeoJSON = try? MGLShape(data: geoJSONData, encoding: String.Encoding.utf8.rawValue) else {
            return
        }

        guard let style = mapView.style, let source = style.source(withIdentifier: "loc-routes") as? MGLShapeSource else {
            return
        }
        source.shape = shapeFromGeoJSON
    }

    // MARK: - Mapbox mapView delegate

    func mapView(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) {
        let session = URLSession(configuration: .default, delegate: nil, delegateQueue: .main)
        let routeServiceURL = createRouteURL(startLat: 49.86739, startLon: 10.89190, startLevel: 0,
                                             endLat: 49.86701, endLon: 10.89054, endLevel: 0, apiKey: apiKey)

        let task = session.dataTask(with: routeServiceURL, completionHandler: { (data: Data?, _: URLResponse?,
            error: Error?) -> Void in

            guard error == nil, let routeData = data else {
                // Handle error case
                return
            }

            if let routeModel = try? JSONDecoder().decode(Route.self, from: routeData) {
                self.renderGeoJSON(from: routeModel, in: mapView)
            }
        })
        task.resume()
    }
}

Route JSON model

public typealias Route = [RouteElement]

// MARK: - RouteElement
public struct RouteElement: Codable {
    enum CodingKeys: String, CodingKey {
        case copyrights = "Copyrights"
        case distance = "Distance"
        case duration = "Duration"
        case endTS = "EndTS"
        case startTS = "StartTS"
        case endAddress = "EndAddress"
        case endLocation = "EndLocation"
        case startAddress = "StartAddress"
        case startLocation = "StartLocation"
        case steps = "Steps"
        case context = "Context"
        case valid = "Valid"
        case revision = "Revision"
        case geoJSON = "GeoJson"
    }

    let copyrights: String
    let distance: Distance
    let duration: Duration
    let endTS, startTS: String
    let endAddress: String
    let endLocation: PolyPoint
    let startAddress: String
    let startLocation: PolyPoint
    let steps: [Step]
    let context: String
    let valid: Bool
    let revision: Int
    let geoJSON: GeoJSON
}

// MARK: - Distance
struct Distance: Codable {
    enum CodingKeys: String, CodingKey {
        case value = "Value"
        case text = "Text"
    }

    let value: Int
    let text: String
}

// MARK: - Duration
struct Duration: Codable {
    enum CodingKeys: String, CodingKey {
        case value = "Value"
        case text = "Text"
    }

    let value: Int
    let text: String
}

// MARK: - EndLocation
struct PolyPoint: Codable {
    enum CodingKeys: String, CodingKey {
        case latitude = "Latitude"
        case longitude = "Longitude"
        case level = "Level"
    }

    let latitude, longitude: Double
    let level: Int
}

// MARK: - GeoJSON
struct GeoJSON: Codable {
    let type: String
    let features: [Feature]
}

// MARK: - Feature
struct Feature: Codable {
    let properties: Properties
    let type: FeatureType
    let geometry: Geometry
}

// MARK: - Geometry
struct Geometry: Codable {
    let type: GeometryType
    let coordinates: [GeometryCoordinate]
}

enum GeometryCoordinate: Codable {
    case double(Double)
    case unionArray([RouteCoordinate])

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let value = try? container.decode([RouteCoordinate].self) {
            self = .unionArray(value)
            return
        }
        if let value = try? container.decode(Double.self) {
            self = .double(value)
            return
        }
        throw DecodingError.typeMismatch(GeometryCoordinate.self,
                                         DecodingError.Context(codingPath: decoder.codingPath,
                                                               debugDescription: "Wrong type for GeometryCoordinate"))
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .double(let value):
            try container.encode(value)
        case .unionArray(let value):
            try container.encode(value)
        }
    }
}

enum RouteCoordinate: Codable {
    case double(Double)
    case doubleArray([Double])

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let value = try? container.decode([Double].self) {
            self = .doubleArray(value)
            return
        }
        if let value = try? container.decode(Double.self) {
            self = .double(value)
            return
        }
        throw DecodingError.typeMismatch(RouteCoordinate.self,
                                         DecodingError.Context(codingPath: decoder.codingPath,
                                                               debugDescription: "Wrong type for RouteCoordinate"))
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .double(let value):
            try container.encode(value)
        case .doubleArray(let value):
            try container.encode(value)
        }
    }
}

enum GeometryType: String, Codable {
    case lineString = "LineString"
    case point = "Point"
    case polygon = "Polygon"
}

// MARK: - Properties
struct Properties: Codable {
    let level: Int
    let style: Style?
    let width: Int?
    let color: String?
    let icon, title: String?
    let step: Int?
}

enum Style: String, Codable {
    case route
    case routedirection
}

enum FeatureType: String, Codable {
    case feature = "Feature"
}

// MARK: - Calc
struct Calc: Codable {
    enum CodingKeys: String, CodingKey {
        case unit = "Unit"
        case startVal = "StartVal"
        case endVal = "EndVal"
    }

    let unit: String
    let startVal, endVal: Int
}

struct Step: Codable {
    enum CodingKeys: String, CodingKey {
        case direction = "Direction"
        case endLocation = "EndLocation"
        case startLocation = "StartLocation"
        case endTS = "EndTS"
        case startTS = "StartTS"
        case distance = "Distance"
        case distanceCalc = "DistanceCalc"
        case duration = "Duration"
        case durationCalc = "DurationCalc"
        case instructions = "Instructions"
        case instructionsTemplate = "InstructionsTemplate"
        case polyline = "Polyline"
        case travelMode = "TravelMode"
        case travelObjectName = "TravelObjectName"
        case travelObjectID = "TravelObjectID"
    }

    let direction: Int
    let endLocation, startLocation: PolyPoint
    let endTS, startTS: String
    let distance: Distance
    let distanceCalc: Calc
    let duration: Duration
    let durationCalc: Calc
    let instructions, instructionsTemplate: String
    let polyline: [PolyPoint]
    let travelMode: Int
    let travelObjectName, travelObjectID: String?
}

USER LOCALIZATION

Add the Locator library

Add the locator library as embedded content to your project. It should look like the following image.

For swift projects you have to import the Locator header in your bridging header file as follows.

//
//  Use this file to import your target's public headers that you would like to expose to Swift.
//

#import <InfsoftLocatorLib/InfsoftLocator.h>

Info.plist##

Since the position of the user is determined via Bluetooth, the NSBluetoothAlwaysUsageDescription and NSBluetoothPeripheralUsageDescription key must be additionally added to your Info.plist.
The Locator library also requires NSMotionUsageDescription as a key in your Info.plist file. The library pauses the location updates if no device motion is detected. In this way the battery consumption is reduced.

UserLocalizationViewController

To display the user’s position via BLE, the map’s default location manager must be overwritten.

import UIKit
import Mapbox

class UserLocalizationViewController: UIViewController {
    // Sample UI controls. This can be extended as desired
    @IBOutlet private weak var userTrackingModeButton: UIButton!
    @IBOutlet private weak var mapLevelButton: UIButton!

    let styleHost = "tilesservices.webservices.infsoft.com"
    let stylePath = "/api/mapstyle/style/"
    let apiKey = "316cf74a-290b-4c4f-91c0-43989cdb15d3"
    let initial3D = "false"
    
    let numberOfMapLevels = 4
    var mapLevel = 0
    weak var mapView: MGLMapView?

    override func viewDidLoad() {
        super.viewDidLoad()

        var components = URLComponents()
        components.scheme = "https"
        components.host = styleHost
        components.path = stylePath + apiKey
        let queryItemConfig = URLQueryItem(name: "config", value: "3d:\(initial3D)")
        components.queryItems = [queryItemConfig]

        let mapView = MGLMapView(frame: view.bounds, styleURL: components.url)
        self.mapView = mapView
        mapView.delegate = self
        mapView.userTrackingMode = .follow
        mapView.locationManager = CustomLocationManager()
        mapView.showsUserLocation = true
        mapView.showsUserHeadingIndicator = true
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.setCenter(CLLocationCoordinate2D(latitude: 50.99319673751623,
                                                 longitude: 11.012463569641115),
                          zoomLevel: 16,
                          animated: false)
        view.insertSubview(mapView, at: 0)
        
        // Disable telemetry and hide button
        UserDefaults.standard.set(false, forKey: "MGLMapboxMetricsEnabled")
        mapView.attributionButton.isHidden = true
        mapView.logoView.isHidden = true
    }

    // MARK: - Example action outlets
  
    /**
     Example action outlet for switching the user tracking mode.
     If you want to use this feature just connect an UIButton to this outlet
     */
    @IBAction private func switchUserTrackingMode(_ sender: Any) {
        guard let mapView = mapView else {
            return
        }

        switch mapView.userTrackingMode {
        case .follow:
            mapView.userTrackingMode = .none
        default:
            mapView.userTrackingMode = .follow
        }
    }

    /**
     Example action outlet for switching the map level.
     If you want to use this feature just connect an UIButton to this outlet
     */
    @IBAction private func switchLevel(_ sender: Any) {
        let newLevel = (mapLevel + 1) % numberOfMapLevels
        guard let mapView = mapView else {
            return
        }
        mapView.userTrackingMode = .none
        setMapLevel(in: mapView, level: newLevel)

        if let userLocation = mapView.userLocation {
            updateUserAnnotation(userLocation, in: mapView)
        }
    }

    /**
     Optional method for updating the UI in case the tracking mode
     changes
     */
    func updateUserTrackingControl(for mode: MGLUserTrackingMode) {
        userTrackingModeButton.isSelected = mode == .follow
    }

    /**
      Optional method for updating the UI in case the map level
      changes
     */
    func updateMapLevelControl(for level: Int) {
        mapLevelButton.setTitle("\(level)", for: .normal)
    }

    // MARK: - Map level handling

    /**
     This method syncs the map level to the user level if the corresponding mode is selected
     */
    func syncMapLevel() {
        guard let mapView = mapView else {
            return
        }
        switch mapView.userTrackingMode {
        case .follow, .followWithHeading:
            guard let userLevel = (mapView.userLocation?.location as? CLLocation3D)?.level else {
                break
            }
            self.setMapLevel(in: mapView, level: userLevel)
        default:
            break
        }
    }

    func setMapLevel(in mapView: MGLMapView, level: Int) {
        guard let style = mapView.style, level != mapLevel else {
            return
        }
        let filterLayers: [MGLVectorStyleLayer] = style.layers.compactMap {
            $0 as? MGLVectorStyleLayer
        }.filter {
            $0.identifier.contains("locls")
        }

        for layer in filterLayers {
            if let predicate = layer.predicate {
                guard let rawData = predicate.mgl_jsonExpressionObject as? [Any] else {
                    return
                }
                let newPredicate = NSPredicate(mglJSONObject: setFilter(filter: rawData, level: level))
                layer.predicate = newPredicate
            }
        }

        mapLevel = level
        updateMapLevelControl(for: level)
    }

    func setFilter(filter: Any, level: Int) -> Any {
        guard var predicates = filter as? [Any] else {
            return filter
        }

        for (index, element) in predicates.enumerated() {
            if element as? [Any] != nil {
                predicates[index] = self.setFilter(filter: element, level: level)
                continue
            }

            if index == 0 {
                continue
            }

            guard let prev = predicates[index - 1] as? String else {
                continue
            }

            if predicates[index] as? Int == nil {
                continue
            }

            if prev != "level" {
                continue
            }

            predicates[index] = level
        }
        return predicates
    }
}

// MARK: - Mapbox mapView delegate

extension UserLocalizationViewController: MGLMapViewDelegate {
    func mapView(_ mapView: MGLMapView, didUpdate userLocation: MGLUserLocation?) {
        guard let userLocation = mapView.userLocation else {
            return
        }
        syncMapLevel()
        updateUserAnnotation(userLocation, in: mapView)
    }

    func mapView(_ mapView: MGLMapView, didChange mode: MGLUserTrackingMode, animated: Bool) {
        syncMapLevel()
        updateUserTrackingControl(for: mode)
    }

    /**
     Hide the annotation view if the wrong level is selected. If the default view is used,
     this is achieved by setting a large offset. If you implement the annotation view by yourself,
     you can do this explicitly using the update() method
     */
    func updateUserAnnotation(_ userLocation: MGLUserLocation, in mapView: MGLMapView) {
        guard let location3DCoordinate = userLocation.location as? CLLocation3D else {
            return
        }
        let view = mapView.view(for: userLocation)
        if location3DCoordinate.level != mapLevel {
             view?.centerOffset = CGVector(dx: 0, dy: Int.max)
         } else {
             view?.centerOffset = CGVector(dx: 0, dy: 0)
         }
         mapView.updateUserLocationAnnotationView()
    }
}

CustomLocationManager

import UIKit
import Mapbox

class CustomLocationManager: NSObject, MGLLocationManager {
    weak var delegate: MGLLocationManagerDelegate?
    let apiKey = "8c97d7c6-0c3a-41de-b67a-fb7628efba79"

    /// CLLocationManager is used for requesting the user heading
    lazy var clLocationManger: CLLocationManager = {
        let locationManager = CLLocationManager()
        locationManager.delegate = self
        return locationManager
    }()

    /// ILLocationManager is used for requesting the user position
    lazy var ilLocationManger: ILLocationManager = {
        let iLLocationServiceManager = ILLocationServiceManager.getInstance(ILLocationServiceManager_UseAll)
        iLLocationServiceManager?.apiKey = apiKey
        return ILLocationManager()
    }()

    var authorizationStatus: CLAuthorizationStatus {
        CLLocationManager.authorizationStatus()
    }

    var headingOrientation: CLDeviceOrientation {
        get {
            clLocationManger.headingOrientation
        }
        set {
            clLocationManger.headingOrientation = newValue
        }
    }

    func requestAlwaysAuthorization() {
        clLocationManger.requestAlwaysAuthorization()
    }

    func requestWhenInUseAuthorization() {
        clLocationManger.requestWhenInUseAuthorization()
    }

    func startUpdatingLocation() {
        ilLocationManger.requestLocationUpdates(self, minTime: 0, minDistance: 0)
    }

    func stopUpdatingLocation() {
        clLocationManger.stopUpdatingLocation()
    }

    func startUpdatingHeading() {
        clLocationManger.startUpdatingHeading()
    }

    func stopUpdatingHeading() {
        clLocationManger.stopUpdatingHeading()
    }

    func dismissHeadingCalibrationDisplay() {
        clLocationManger.dismissHeadingCalibrationDisplay()
    }
}

// MARK: - CLLocationManagerDelegate

extension CustomLocationManager: CLLocationManagerDelegate {
    /// This method should do nothing. Use ILLocationManager instead
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    }

    func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
        delegate?.locationManager(self, didUpdate: newHeading)
    }

    func locationManagerShouldDisplayHeadingCalibration(_ manager: CLLocationManager) -> Bool {
        delegate?.locationManagerShouldDisplayHeadingCalibration(self) ?? false
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        delegate?.locationManager(self, didFailWithError: error)
    }
}

// MARK: - ILLocation

extension CustomLocationManager: ILLocationListener {
    func onLocationChanged(_ location: ILLocation!) {
        let clLocation3D = CLLocation3D(latitude: location.latitude, longitude: location.longitude)
        clLocation3D.level = Int(location.level)
        delegate?.locationManager(self, didUpdate: [clLocation3D])
    }
}

/// Extension of the class so that the user level can also be specified
class CLLocation3D: CLLocation {
    var level: Int?
}

POI SELECTION

POISelectionViewController

  • Replace the apiKey variable with your api key to display your own maps
import UIKit
import Mapbox

class POISelectionViewController: UIViewController {
    // Map style
    let styleHost = "tilesservices.webservices.infsoft.com"
    let stylePath = "/api/mapstyle/style/"
    let apiKey = "8c97d7c6-0c3a-41de-b67a-fb7628efba79"
    let initial3D = "false"

    // GeoObjects
    let geoObjectsHost = "tiles.infsoft.com"
    let geoObjectsPath = "/api/geoobj/json/"

    // Revision
    let revisionHost = "tilesservices.webservices.infsoft.com"
    let revisionPath = "/api/geojson/revision"

    let numberOfMapLevels = 4
    var mapLevel = 0
    var geoObjects: GeoObjects?
    weak var mapView: MGLMapView?

    var revisionNumber: String? {
        didSet {
            loadGeoObjects()
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        var components = URLComponents()
        components.scheme = "https"
        components.host = styleHost
        components.path = stylePath + apiKey
        let queryItemConfig = URLQueryItem(name: "config", value: "3d:\(initial3D)")
        components.queryItems = [queryItemConfig]

        let mapView = MGLMapView(frame: view.bounds, styleURL: components.url)
        self.mapView = mapView
        mapView.delegate = self
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.setCenter(CLLocationCoordinate2D(latitude: 49.867630660511715,
                                                 longitude: 10.89075028896332),
                          zoomLevel: 16,
                          animated: false)
        view.insertSubview(mapView, at: 0)

        // Disable telemetry and hide button
        UserDefaults.standard.set(false, forKey: "MGLMapboxMetricsEnabled")
        mapView.attributionButton.isHidden = true
        mapView.logoView.isHidden = true

        addGestureRecognizer()
        loadRevisionNumber()
    }

    // MARK: - Handle annotation

    func addGestureRecognizer() {
        guard let mapView = mapView else {
            return
        }
        let singleTap = UITapGestureRecognizer(target: self, action: #selector(handleMapTap(sender:)))
        for recognizer in mapView.gestureRecognizers! where recognizer is UITapGestureRecognizer {
            guard let recognizer = recognizer as? UITapGestureRecognizer else {
                continue
            }
            if recognizer.numberOfTapsRequired == 1 {
                continue
            }
            singleTap.require(toFail: recognizer)
        }
        mapView.addGestureRecognizer(singleTap)
    }

    @objc func handleMapTap(sender: UITapGestureRecognizer) {
        guard let mapView = mapView else {
            return
        }
        if sender.state == .ended {
            let point = sender.location(in: sender.view!)
            let layerIdentifiers = Set<String>(arrayLiteral: "locls-pois")
            for feature in mapView.visibleFeatures(at: point, styleLayerIdentifiers: layerIdentifiers)
                where feature is MGLPointFeature {
                    guard let selectedFeature = feature as? MGLPointFeature else {
                        fatalError("Failed to cast selected feature as MGLPointFeature")
                    }
                    showAnnotation(feature: selectedFeature)
                    return
            }

            let touchCoordinate = mapView.convert(point, toCoordinateFrom: nil)
            let touchLocation = CLLocation(latitude: touchCoordinate.latitude, longitude: touchCoordinate.longitude)
            let touchRect = CGRect(origin: point, size: .zero).insetBy(dx: -22.0, dy: -22.0)
            let possibleFeatures = mapView.visibleFeatures(in: touchRect, styleLayerIdentifiers: Set(layerIdentifiers)).filter { $0 is MGLPointFeature }

            // Select the closest feature to the touch center.
            let closestFeatures = possibleFeatures.sorted(by: {
                return CLLocation(latitude: $0.coordinate.latitude, longitude: $0.coordinate.longitude).distance(from: touchLocation) < CLLocation(latitude: $1.coordinate.latitude, longitude: $1.coordinate.longitude).distance(from: touchLocation)
            })
            if let feature = closestFeatures.first {
                guard let closestFeature = feature as? MGLPointFeature else {
                    fatalError("Failed to cast selected feature as MGLPointFeature")
                }

                showAnnotation(feature: closestFeature)
                return
            }

            // If no features were found, deselect the selected annotation, if any.
            mapView.deselectAnnotation(mapView.selectedAnnotations.first, animated: true)
        }
    }

    @objc func showAnnotation(feature: MGLPointFeature) {
        guard let geoObjects = self.geoObjects, let featureUID = feature.attribute(forKey: "uid") as? String else {
            return
        }

        // Search for the matching GeoObject in the loaded data
        let matchingGeoObjects = geoObjects.filter { (geoObject) -> Bool in
            geoObject.uid == featureUID
        }

        guard let firstMatchingGeoObject = matchingGeoObjects.first else {
            return
        }

        let annotation = MGLPointAnnotation()

        // Set the custom title for the annotation.
        annotation.title = firstMatchingGeoObject.type
        annotation.coordinate = feature.coordinate
        mapView?.selectAnnotation(annotation, animated: true, completionHandler: nil)
    }

    // MARK: - GeoObjects

    func loadRevisionNumber() {
        var components = URLComponents()
        components.scheme = "https"
        components.host = revisionHost
        components.path = revisionPath + "/" + apiKey

        guard let url = components.url else {
            return
        }

        let session = URLSession(configuration: .default, delegate: nil, delegateQueue: .main)
            let task = session.dataTask(with: url, completionHandler: { (data: Data?, _: URLResponse?, error: Error?) -> Void in
                guard error == nil, let revisionData = data else {
                    // Handle error case
                    return
                }
                self.revisionNumber = String(data: revisionData, encoding: .utf8)
            })
            task.resume()
    }

    func loadGeoObjects() {
        guard let revisionNumber = revisionNumber else {
            return
        }
        var components = URLComponents()
        components.scheme = "https"
        let localization = "en"
        components.host = geoObjectsHost
        components.path = geoObjectsPath + apiKey + "/" + localization + "/" + revisionNumber

        guard let url = components.url else {
            return
        }

        let session = URLSession(configuration: .default, delegate: nil, delegateQueue: .main)
        let task = session.dataTask(with: url, completionHandler: { (data: Data?, _: URLResponse?, error: Error?) -> Void in
            guard error == nil, let objectsData = data else {
                // Handle error case
                return
            }
            self.geoObjects = try? JSONDecoder().decode(GeoObjects.self, from: objectsData)
        })
        task.resume()
    }
}

extension POISelectionViewController: MGLMapViewDelegate {
    func mapView(_ mapView: MGLMapView, didDeselect annotation: MGLAnnotation) {
        mapView.removeAnnotation(annotation)
    }

    func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
        return geoObjects != nil
    }

    func mapView(_ mapView: MGLMapView, calloutViewFor annotation: MGLAnnotation) -> MGLCalloutView? {
        return nil
    }
}

GeoObject

public typealias GeoObjects = [GeoObject]

// MARK: - GeoObject

@objc public class GeoObject: NSObject, Codable {
    public let uid: String
    public let type: String
    public let props: [String: String]

    public func propertyFor(key: String) -> String? {
       return props[key]
    }
}

Android

infsoft enables you to build Android map apps using Mapbox. This means:

  • Mapbox-style-object generated server side, containing our maps, based on your data.
  • 2D and 3D mode
  • infsoft Locator library to display the user’s position
  • All Mapbox functionality

Official Mapbox website

GETTING STARTED

MapBox dependency

Add MapBox to your build.gradle dependencies. MapBox 8.4.0 is supported:

implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:8.4.0'

Official Mapbox Android documentation

COMMON CODE

Here you can find code that is used in multiple demo apps.

Switching levels

The following code is used in every example application, to change the selected level.

import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.style.expressions.Expression;
import com.mapbox.mapboxsdk.style.layers.FillExtrusionLayer;
import com.mapbox.mapboxsdk.style.layers.FillLayer;
import com.mapbox.mapboxsdk.style.layers.Layer;
import com.mapbox.mapboxsdk.style.layers.LineLayer;
import com.mapbox.mapboxsdk.style.layers.SymbolLayer;

import java.util.List;

import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconSize;

/**
 * Helper class to change the selected level.
 */
public class LevelSwitch {

    /**
     * Changes the MapBox filters to display only data for the given level.
     *
     * @param style MapBox style object on which the filters will be changed.
     * @param level The level that should be displayed.
     */
    public static void updateLevel(Style style, int level) {
        List<Layer> layers = style.getLayers();

        for (Layer layer : layers) {
            String layerId = layer.getId();

            if (layerId.contains("locls")) {
                if (layer instanceof SymbolLayer) {
                    SymbolLayer symbolLayer = (SymbolLayer) layer;
                    String id = symbolLayer.getId();
                    Expression updatedFilter = calculateFilter(symbolLayer.getFilter().toArray(), level);
                    symbolLayer.setFilter(updatedFilter);
                } else if (layer instanceof FillExtrusionLayer) {
                    FillExtrusionLayer fillExtrusionLayer = (FillExtrusionLayer) layer;
                    Expression updatedFilter = calculateFilter(fillExtrusionLayer.getFilter().toArray(), level);
                    fillExtrusionLayer.setFilter(updatedFilter);
                } else if (layer instanceof FillLayer) {
                    FillLayer fillLayer = (FillLayer) layer;
                    Expression updatedFilter = calculateFilter(fillLayer.getFilter().toArray(), level);
                    fillLayer.setFilter(updatedFilter);
                } else if (layer instanceof LineLayer) {
                    LineLayer lineLayer = (LineLayer) layer;
                    Expression updatedFilter = calculateFilter(lineLayer.getFilter().toArray(), level);
                    lineLayer.setFilter(updatedFilter);
                }
            }
        }
    }

    private static Expression calculateFilter(Object[] currentFilter, int filterLevel) {
        ExpressionBuilder builder = new ExpressionBuilder(currentFilter, filterLevel);
        Expression updatedFilter = builder.buildExpression();

        return updatedFilter;
    }
}
import com.mapbox.mapboxsdk.style.expressions.Expression;

import java.util.ArrayList;
import java.util.List;

/**
 * Requirement: The level check in an existing filter(comes from the server) must be changed.
 * Other values must not be altered.
 * <p>
 * The filter comes in the form on an Object[]. At the end, an Expression must be returned. The Expression
 * MUST ONLY CONSIST OF OTHER EXPRESSIONS.
 * <p>
 * Every Object[] consists of 1 operator(equals, notEquals, bigger, smaller..) and X ExpressionData.
 * ExpressionData can be either ExpressionValue(a single string or float packaged in an expression)
 * or another ExpressionBuilder. Example expression:
 * ["all",
 * ["==", ["geometry-type"], "LineString"],
 * ["==", ["get", "level"], 0.0],
 * ["any",
 * ["==", ["get", "style"], "route"],
 * ["all",
 * ["==", ["get", "style"], "routedirection"],
 * ["==", ["get", "step"], 1.0]
 * ]
 * ]
 * ]
 */
class ExpressionBuilder implements ExpressionData {
    private String operator;
    private List<ExpressionData> arguments;
    private static boolean levelFound = false;

    /**
     * Breaks the given filter into Expression objects.
     *
     * @param filter Object[] representing the current MapBox layer filter.
     * @param level The level to be filtered for.
     */
    ExpressionBuilder(Object[] filter, int level) {
        if (filter[0] instanceof String) {
            operator = (String) filter[0];
        } else {
            return;
        }

        arguments = new ArrayList<>();

        for (int i = 1; i < filter.length; i++) {
            if (filter[i] instanceof Object[]) {
                Object[] subArray = (Object[]) filter[i];

                ExpressionBuilder subExpression = new ExpressionBuilder(subArray, level);
                arguments.add(subExpression);
            } else {
                Expression literal;
                if (filter[i] instanceof String) {
                    String valueString = (String) filter[i];

                    if (valueString.equals("level")) {
                        levelFound = true;
                    }

                    literal = Expression.literal(valueString);
                } else if (filter[i] instanceof Float) {
                    Float valueFloat;
                    if (levelFound) {
                        valueFloat = (float) level;
                        levelFound = false;
                    } else {
                        valueFloat = (Float) filter[i];
                    }
                    literal = Expression.literal(valueFloat);
                } else {
                    arguments = null;
                    return;
                }

                arguments.add(new ExpressionValue(literal));
            }
        }
    }

    /**
     * Reassembles the filter given in the constructor, with filters for the selected level.
     *
     * @return MapBox filter for the selected level.
     */
    Expression buildExpression() {
        Expression expression;

        List<Expression> combinedElements = new ArrayList<>();
        for (ExpressionData data : arguments) {
            if (data instanceof ExpressionBuilder) {
                ExpressionBuilder subBuilder = (ExpressionBuilder) data;
                combinedElements.add(subBuilder.buildExpression());
            } else if (data instanceof ExpressionData) {
                ExpressionValue subValue = (ExpressionValue) data;
                combinedElements.add(subValue.getValue());
            }
        }

        Expression[] expressions = new Expression[combinedElements.size()];
        expressions = combinedElements.toArray(expressions);
        expression = new Expression(operator, expressions);

        return expression;
    }
}
import com.mapbox.mapboxsdk.style.expressions.Expression;

class ExpressionValue implements ExpressionData {

    private final Expression value;

    ExpressionValue(Expression value) {
        this.value = value;
    }

    Expression getValue() {
        return value;
    }
}
interface ExpressionData {
}

DISPLAY MAP

activity_display_map

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".DisplayMapActivity">

    <com.mapbox.mapboxsdk.maps.MapView
        android:id="@+id/mapView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </com.mapbox.mapboxsdk.maps.MapView>

</RelativeLayout>

DisplayMapActivity

  • Please replace the API_KEY variable with your own api key to display your own maps
package com.infsoft.android.locaware.demos;

import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import com.infsoft.android.locaware.demos.level.LevelSwitch;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import com.mapbox.mapboxsdk.maps.Style;

import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconSize;

public class DisplayMapActivity extends AppCompatActivity implements OnMapReadyCallback, Style.OnStyleLoaded {
    private final String STYLE_URL = "https://tilesservices.webservices.infsoft.com/api/mapstyle/style/";
    private final String API_KEY = "8c97d7c6-0c3a-41de-b67a-fb7628efba79";
    private final String INITIAL_3D = "FALSE";

    private MapView mapView;
    private MapboxMap mapboxMap;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Mapbox.getInstance(this, "pk.");
        TelemetryDefinition telemetry = Mapbox.getTelemetry();
        telemetry.setUserTelemetryRequestState(false);
      
        setContentView(R.layout.activity_display_map);

        mapView = findViewById(R.id.mapView);
        mapView.getMapAsync(this);
    }

    @Override
    public void onMapReady(@NonNull MapboxMap mapboxMap) {
        this.mapboxMap = mapboxMap;

        String styleUrl = STYLE_URL + API_KEY + "?config=3d:" + INITIAL_3D;
        this.mapboxMap.setStyle(new Style.Builder().fromUri(styleUrl), this);

        this.mapboxMap.getUiSettings().setAttributionEnabled(false);
        this.mapboxMap.getUiSettings().setLogoEnabled(false);
    }

    @Override
    public void onStyleLoaded(@NonNull Style style) {
        LevelSwitch.updateLevel(style,0);

        CameraPosition cameraPosition = new CameraPosition.Builder()
                .target(new LatLng( 49.867630660511715, 10.89075028896332)) // Sets the new camera position
                .zoom(18) // Sets the zoom
                .bearing(0) // Rotate the camera
                .tilt(0) // Set the camera tilt
                .build();
        this.mapboxMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
    }

    @Override
    public void onStart() {
        super.onStart();

        if (mapView != null) {
            mapView.onStart();
        }
    }

    @Override
    public void onResume() {
        super.onResume();

        if (mapView != null) {
            mapView.onResume();
        }
    }

    @Override
    public void onPause() {
        super.onPause();

        if (mapView != null) {
            mapView.onPause();
        }
    }

    @Override
    public void onStop() {
        super.onStop();

        if (mapView != null) {
            mapView.onStop();
        }
    }

    @Override
    public void onLowMemory() {
        super.onDestroy();

        if (mapView != null) {
            mapView.onLowMemory();
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        if (mapView != null) {
            mapView.onDestroy();
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        if (mapView != null) {
            mapView.onSaveInstanceState(outState);
        }
    }
}

LevelSwitch

See common code

DISPLAY MAP 3D

activity_display_map3_d

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".DisplayMap3DActivity">

    <com.mapbox.mapboxsdk.maps.MapView
        android:id="@+id/mapView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </com.mapbox.mapboxsdk.maps.MapView>
</RelativeLayout>

DisplayMap3DActivity

  • Please replace the API_KEY variable with your own api key to display your own maps
package com.infsoft.android.locaware.demos;

import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import com.infsoft.android.locaware.demos.level.LevelSwitch;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import com.mapbox.mapboxsdk.maps.Style;

import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconSize;

public class DisplayMapActivity extends AppCompatActivity implements OnMapReadyCallback, Style.OnStyleLoaded {
    private final String STYLE_URL = "https://tilesservices.webservices.infsoft.com/api/mapstyle/style/";
    private final String API_KEY = "8c97d7c6-0c3a-41de-b67a-fb7628efba79";
    private final String INITIAL_3D = "FALSE";

    private MapView mapView;
    private MapboxMap mapboxMap;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Mapbox.getInstance(this, "pk.");
        TelemetryDefinition telemetry = Mapbox.getTelemetry();
        telemetry.setUserTelemetryRequestState(false);
      
        setContentView(R.layout.activity_display_map);

        mapView = findViewById(R.id.mapView);
        mapView.getMapAsync(this);
    }

    @Override
    public void onMapReady(@NonNull MapboxMap mapboxMap) {
        this.mapboxMap = mapboxMap;

        String styleUrl = STYLE_URL + API_KEY + "?config=3d:" + INITIAL_3D;
        this.mapboxMap.setStyle(new Style.Builder().fromUri(styleUrl), this);

        this.mapboxMap.getUiSettings().setAttributionEnabled(false);
        this.mapboxMap.getUiSettings().setLogoEnabled(false);
    }

    @Override
    public void onStyleLoaded(@NonNull Style style) {
        LevelSwitch.updateLevel(style,0);

        CameraPosition cameraPosition = new CameraPosition.Builder()
                .target(new LatLng( 49.867630660511715, 10.89075028896332)) // Sets the new camera position
                .zoom(18) // Sets the zoom
                .bearing(0) // Rotate the camera
                .tilt(0) // Set the camera tilt
                .build();
        this.mapboxMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
    }

    @Override
    public void onStart() {
        super.onStart();

        if (mapView != null) {
            mapView.onStart();
        }
    }

    @Override
    public void onResume() {
        super.onResume();

        if (mapView != null) {
            mapView.onResume();
        }
    }

    @Override
    public void onPause() {
        super.onPause();

        if (mapView != null) {
            mapView.onPause();
        }
    }

    @Override
    public void onStop() {
        super.onStop();

        if (mapView != null) {
            mapView.onStop();
        }
    }

    @Override
    public void onLowMemory() {
        super.onDestroy();

        if (mapView != null) {
            mapView.onLowMemory();
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        if (mapView != null) {
            mapView.onDestroy();
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        if (mapView != null) {
            mapView.onSaveInstanceState(outState);
        }
    }
}

LevelSwitch

See common code

SWITCH LEVELS

activity_switch_levels

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SwitchLevelsActivity">

    <com.mapbox.mapboxsdk.maps.MapView
        android:id="@+id/mapView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </com.mapbox.mapboxsdk.maps.MapView>

    <Button
        android:id="@+id/switchLevelButton"
        android:layout_marginTop="15dp"
        android:layout_marginStart="15dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="SWITCH LEVEL"
        android:layout_gravity="left|top"
        android:orientation="vertical"
        android:textColor="#444444"
        />

</RelativeLayout>

SwitchLevelsActivity

  • Please replace the API_KEY variable with your own api key to display your own maps
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import com.infsoft.android.locaware.switchlevels.level.LevelSwitch;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import com.mapbox.mapboxsdk.maps.Style;

public class SwitchLevelsActivity extends AppCompatActivity implements OnMapReadyCallback, Style.OnStyleLoaded {
    private final String STYLE_URL = "https://tilesservices.webservices.infsoft.com/api/mapstyle/style/";
    private final String API_KEY = "8c97d7c6-0c3a-41de-b67a-fb7628efba79";
    private final String INITIAL_3D = "FALSE";

    private MapView mapView;
    private MapboxMap mapboxMap;
    private Style loadedStyle;

    private int currentLevel = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Mapbox.getInstance(this, "pk.");
        TelemetryDefinition telemetry = Mapbox.getTelemetry();
        telemetry.setUserTelemetryRequestState(false);
      
        setContentView(R.layout.activity_switch_levels);

        mapView = findViewById(R.id.mapView);
        mapView.getMapAsync(this);
    }

    @Override
    public void onMapReady(@NonNull MapboxMap mapboxMap) {
        this.mapboxMap = mapboxMap;
        this.mapboxMap.getUiSettings().setAttributionEnabled(false);
        this.mapboxMap.getUiSettings().setLogoEnabled(false);

        String styleUrl = STYLE_URL + API_KEY + "?config=3d:" + INITIAL_3D;
        this.mapboxMap.setStyle(new Style.Builder().fromUri(styleUrl), this);
     }

    @Override
    public void onStyleLoaded(@NonNull Style style) {
        this.loadedStyle = style;
        LevelSwitch.updateLevel(style,0);

        CameraPosition cameraPosition = new CameraPosition.Builder()
                .target(new LatLng( 49.867630660511715, 10.89075028896332)) // Sets the new camera position
                .zoom(18) // Sets the zoom
                .bearing(0) // Rotate the camera
                .tilt(0) // Set the camera tilt
                .build();
        this.mapboxMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));

        Button levelSwitch = findViewById(R.id.switchLevelButton);
        levelSwitch.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                currentLevel = (currentLevel + 1) % 4;
                LevelSwitch.updateLevel(loadedStyle, currentLevel);
            }
        });
    }

    @Override
    public void onStart() {
        super.onStart();

        if (mapView != null) {
            mapView.onStart();
        }
    }

    @Override
    public void onResume() {
        super.onResume();

        if (mapView != null) {
            mapView.onResume();
        }
    }

    @Override
    public void onPause() {
        super.onPause();

        if (mapView != null) {
            mapView.onPause();
        }
    }

    @Override
    public void onStop() {
        super.onStop();

        if (mapView != null) {
            mapView.onStop();
        }
    }

    @Override
    public void onLowMemory() {
        super.onDestroy();

        if (mapView != null) {
            mapView.onLowMemory();
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        if (mapView != null) {
            mapView.onDestroy();
        }
    }

    @Override
    public void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);

        if (mapView != null) {
            mapView.onSaveInstanceState(outState);
        }
    }
}

LevelSwitch

See common code

DISPLAY ROUTE

activity_display_route

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".DisplayRouteActivity">

    <com.mapbox.mapboxsdk.maps.MapView
        android:id="@+id/mapView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </com.mapbox.mapboxsdk.maps.MapView>

    <Button
        android:id="@+id/calcRouteButton"
        android:layout_marginTop="15dp"
        android:layout_marginStart="15dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="CALC ROUTE"
        android:layout_gravity="left|top"
        android:orientation="vertical"
        android:textColor="#444444"
        />

</RelativeLayout>

DisplayRouteActivity

  • Please replace the API_KEY variable with your own api key to display your own maps
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import com.infsoft.android.locaware.displayroute.level.LevelSwitch;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import com.mapbox.mapboxsdk.style.sources.Source;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.List;
import java.util.concurrent.ExecutionException;

public class DisplayRouteActivity extends AppCompatActivity implements OnMapReadyCallback, Style.OnStyleLoaded {
    private final String STYLE_URL = "https://tilesservices.webservices.infsoft.com/api/mapstyle/style/";
    private final String API_KEY = "8c97d7c6-0c3a-41de-b67a-fb7628efba79";
    private final String INITIAL_3D = "TRUE";

    private MapView mapView;
    private MapboxMap mapboxMap;

    private GeoJsonSource routeSource;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Mapbox.getInstance(this, "pk.");
        TelemetryDefinition telemetry = Mapbox.getTelemetry();
        telemetry.setUserTelemetryRequestState(false);
      
        setContentView(R.layout.activity_display_route);

        mapView = findViewById(R.id.mapView);
        mapView.getMapAsync(this);
    }

    @Override
    public void onMapReady(@NonNull MapboxMap mapboxMap) {
        this.mapboxMap = mapboxMap;
        this.mapboxMap.getUiSettings().setAttributionEnabled(false);
        this.mapboxMap.getUiSettings().setLogoEnabled(false);

        String styleUrl = STYLE_URL + API_KEY + "?config=3d:" + INITIAL_3D;
        this.mapboxMap.setStyle(new Style.Builder().fromUri(styleUrl), this);
    }

    @Override
    public void onStyleLoaded(@NonNull Style style) {
        LevelSwitch.updateLevel(style, 0);
        initSource(style);
        setInitialCamera();

        Button levelSwitch = findViewById(R.id.calcRouteButton);
        levelSwitch.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                onCalcRouteClicked();
            }
        });
    }

    private void setInitialCamera(){
        CameraPosition cameraPosition = new CameraPosition.Builder()
                .target(new LatLng(49.867630660511715, 10.89075028896332)) // Sets the new camera position
                .zoom(18) // Sets the zoom
                .bearing(0) // Rotate the camera
                .tilt(45) // Set the camera tilt
                .build();
        this.mapboxMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
    }

    private void initSource(Style style) {
        List<Source> sources = style.getSources();
        for (Source source : sources) {
            if (source.getId().contains("route") && source instanceof GeoJsonSource) {
                routeSource = (GeoJsonSource) source;
            }
        }
    }

    private void onCalcRouteClicked() {
        String urlString = "https://routes.webservices.infsoft.com/API/Calc?"
                + "apikey=" + API_KEY
                + "&startlat=" + 49.86739
                + "&startlon=" + 10.89190
                + "&startlevel=" + 0
                + "&endlat=" + 49.86701
                + "&endlon=" + 10.89054
                + "&endlevel=" + 0;

        String rawRouteJson = null;
        try {
            RestCall restCall = new RestCall();
            rawRouteJson = restCall.execute(urlString).get();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        setGeoJson(rawRouteJson);
    }

    private void setGeoJson(String rawRouteJson){
        JSONArray array;
        try {
            array = new JSONArray(rawRouteJson);
            for (int i = 0; i < array.length(); i++) {
                JSONObject jsonObj = array.getJSONObject(i);
                rawRouteJson = jsonObj.getString("GeoJson");
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }

        routeSource.setGeoJson(rawRouteJson);
    }

    @Override
    public void onStart() {
        super.onStart();

        if (mapView != null) {
            mapView.onStart();
        }
    }

    @Override
    public void onResume() {
        super.onResume();

        if (mapView != null) {
            mapView.onResume();
        }
    }

    @Override
    public void onPause() {
        super.onPause();

        if (mapView != null) {
            mapView.onPause();
        }
    }

    @Override
    public void onStop() {
        super.onStop();

        if (mapView != null) {
            mapView.onStop();
        }
    }

    @Override
    public void onLowMemory() {
        super.onDestroy();

        if (mapView != null) {
            mapView.onLowMemory();
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        if (mapView != null) {
            mapView.onDestroy();
        }
    }

    @Override
    public void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);

        if (mapView != null) {
            mapView.onSaveInstanceState(outState);
        }
    }
}

RestCall

import android.os.AsyncTask;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

class RestCall extends AsyncTask<String, Void, String> {

    @Override
    protected String doInBackground(String... strings) {
        String urlString = strings[0];
        StringBuilder sb = new StringBuilder();

        try {
            URL url = new URL(urlString);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setRequestProperty("Accept", "application/json");

            BufferedReader br = new BufferedReader(new InputStreamReader(
                    (conn.getInputStream())));

            String output;
            while ((output = br.readLine()) != null) {
                sb.append(output);
            }
            conn.disconnect();
        } catch (MalformedURLException e) {
            e.printStackTrace();
            return null;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }

        return sb.toString();
    }
}

LevelSwitch

See common code

USER LOCALIZATION

Locator library

1. Add the infsoft Locator Library to your application and reference the library.

2. Add the required permissions in your AndroidManifest:

<uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

3. Add the locator service to your AndroidManifest:

<service
            android:name="com.infsoft.android.locator.LocatorService"
            android:enabled="true"
            android:exported="false">
            <intent-filter>
                <action android:name="android.infsoft.com.library.locator.LocatorService.SERVICE"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>

            <meta-data
                android:name="useBLE"
                android:value="true"/>
            <meta-data
                android:name="ignoreWIFI"
                android:value="true" />
        </service>

activity_user_localization

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".UserLocalizationActivity">

    <com.mapbox.mapboxsdk.maps.MapView
        android:id="@+id/mapView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </com.mapbox.mapboxsdk.maps.MapView>
</RelativeLayout>

UserLocalizationActivity

  • Please replace the API_KEY variable with your own api key to display your own maps
import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.infsoft.android.locator.Location;
import com.infsoft.android.locator.LocationListener;
import com.infsoft.android.locator.LocationManager;
import com.infsoft.android.locaware.userlocalization.level.LevelSwitch;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.location.LocationComponent;
import com.mapbox.mapboxsdk.location.LocationComponentActivationOptions;
import com.mapbox.mapboxsdk.location.modes.RenderMode;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.maps.TelemetryDefinition;

public class UserLocalizationActivity extends AppCompatActivity implements OnMapReadyCallback, Style.OnStyleLoaded, LocationListener {
    private final String STYLE_URL = "https://tilesservices.webservices.infsoft.com/api/mapstyle/style/";
    private final String API_KEY = "8c97d7c6-0c3a-41de-b67a-fb7628efba79";
    private final String INITIAL_LEVEL = "0";
    private final String INITIAL_3D = "FALSE";

    private MapView mapView;
    private MapboxMap mapboxMap;

    private static LocationManager locationManager;
    private LocationComponent locationComponent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Mapbox.getInstance(this, "pk.");
        TelemetryDefinition telemetry = Mapbox.getTelemetry();
        telemetry.setUserTelemetryRequestState(false);

        setContentView(R.layout.activity_user_localization);

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 200);
        }

        mapView = findViewById(R.id.mapView);
        mapView.getMapAsync(this);
    }

    @Override
    public void onMapReady(@NonNull MapboxMap mapboxMap) {
        this.mapboxMap = mapboxMap;
        this.mapboxMap.getUiSettings().setAttributionEnabled(false);
        this.mapboxMap.getUiSettings().setLogoEnabled(false);

        String styleUrl = STYLE_URL + API_KEY + "?config=level:" + INITIAL_LEVEL + "|3d:" + INITIAL_3D;
        this.mapboxMap.setStyle(new Style.Builder().fromUri(styleUrl), this);
    }

    private void setInitialCamera() {
        CameraPosition cameraPosition = new CameraPosition.Builder()
                .target(new LatLng(49.867630660511715, 10.89075028896332)) // Sets the new camera position
                .zoom(18) // Sets the zoom
                .bearing(0) // Rotate the camera
                .tilt(0) // Set the camera tilt
                .build();
        this.mapboxMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
    }

    @Override
    public void onStyleLoaded(@NonNull Style style) {
        LevelSwitch.updateLevel(style, 0);
        setInitialCamera();

        locationComponent = mapboxMap.getLocationComponent();
        initMapBoxLocationComponent(style);
        initLocator(this);
    }

    // Make MapBox show the user's position
    private void initMapBoxLocationComponent(@NonNull Style loadedMapStyle) {
        LocationComponentActivationOptions locationComponentActivationOptions =
                LocationComponentActivationOptions.builder(this, loadedMapStyle)
                        .useDefaultLocationEngine(false)
                        .build();
        locationComponent.activateLocationComponent(locationComponentActivationOptions);
        locationComponent.setRenderMode(RenderMode.COMPASS);
        locationComponent.setLocationComponentEnabled(true);
    }

    // Listen to updates from the infsoft Locator library
    private void initLocator(Activity activity) {
        if (locationManager == null)
            locationManager = LocationManager.getService(activity, API_KEY, 101677);

        locationManager.requestLocationUpdates(activity, 2000, 1000, this);
    }

    @Override
    public void onLocationChanged(Location location) {
        android.location.Location androidLocation = new android.location.Location("");
        androidLocation.setLatitude(location.getLatitude());
        androidLocation.setLongitude(location.getLongitude());
        mapboxMap.getLocationComponent().forceLocationUpdate(androidLocation);

        if (location.getLevel() == Integer.valueOf(INITIAL_LEVEL)) {
            locationComponent.setLocationComponentEnabled(true);
        } else {
            locationComponent.setLocationComponentEnabled(false);
        }
    }

    @Override
    public void onStart() {
        super.onStart();

        if (mapView != null) {
            mapView.onStart();
        }
    }

    @Override
    public void onResume() {
        super.onResume();

        if (mapView != null) {
            mapView.onResume();
        }
    }

    @Override
    public void onPause() {
        super.onPause();

        if (mapView != null) {
            mapView.onPause();
        }
    }

    @Override
    public void onStop() {
        super.onStop();

        if (mapView != null) {
            mapView.onStop();
        }
    }

    @Override
    public void onLowMemory() {
        super.onDestroy();

        if (mapView != null) {
            mapView.onLowMemory();
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        if (mapView != null) {
            mapView.onDestroy();
        }
    }

    @Override
    public void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);

        if (mapView != null) {
            mapView.onSaveInstanceState(outState);
        }
    }
}

LevelSwitch

See common code

POI SELECTION

activity_select_POI

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SelectPOIActivity">

    <com.mapbox.mapboxsdk.maps.MapView
        android:id="@+id/mapView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </com.mapbox.mapboxsdk.maps.MapView>
</RelativeLayout>

SelectPOIActivity

  • Please replace the API_KEY variable with your own api key to display your own maps
package com.infsoft.android.locaware.selectpoi;

import android.graphics.PointF;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.infsoft.android.locaware.selectpoi.level.LevelSwitch;
import com.mapbox.geojson.Feature;
import com.mapbox.geojson.Geometry;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.annotations.Marker;
import com.mapbox.mapboxsdk.annotations.MarkerOptions;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.maps.TelemetryDefinition;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;

public class SelectPOIActivity extends AppCompatActivity implements OnMapReadyCallback, Style.OnStyleLoaded {
    private final String STYLE_URL = "https://tilesservices.webservices.infsoft.com/api/mapstyle/style/";
    private final String API_KEY = "8c97d7c6-0c3a-41de-b67a-fb7628efba79";
    private final String INITIAL_3D = "FALSE";
    private final String POI_LAYER_ID = "locls-pois";

    private MapView mapView;
    private MapboxMap mapboxMap;

    private List<PoiGeoJsonObject> pois = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Mapbox.getInstance(this, "pk.");
        TelemetryDefinition telemetry = Mapbox.getTelemetry();
        telemetry.setUserTelemetryRequestState(false);

        setContentView(R.layout.activity_select_poi);

        mapView = findViewById(R.id.mapView);
        mapView.getMapAsync(this);

        loadPOIs();
    }

    @Override
    public void onMapReady(@NonNull MapboxMap mapboxMap) {
        this.mapboxMap = mapboxMap;
        this.mapboxMap.getUiSettings().setAttributionEnabled(false);
        this.mapboxMap.getUiSettings().setLogoEnabled(false);

        String styleUrl = STYLE_URL + API_KEY + "?config=3d:" + INITIAL_3D;
        this.mapboxMap.setStyle(new Style.Builder().fromUri(styleUrl), this);
    }

    @Override
    public void onStyleLoaded(@NonNull Style style) {
        LevelSwitch.updateLevel(style, 0);
        setInitialCamera();
        mapboxMap.addOnMapClickListener(new MapboxMap.OnMapClickListener() {
            @Override
            public boolean onMapClick(@NonNull LatLng point) {
                removeMarkers(mapboxMap);
                Feature selectedFeature = findSelectedFeature(point);
                PoiGeoJsonObject selectedPoi = findClickedPoi(selectedFeature);
                createMarker(selectedPoi, selectedFeature);

                return true;
            }
        });
    }

    private Feature findSelectedFeature(LatLng point) {
        PointF screenPoint = mapboxMap.getProjection().toScreenLocation(point);
        List<Feature> features = mapboxMap.queryRenderedFeatures(screenPoint, POI_LAYER_ID);
        if (features != null && !features.isEmpty()) {
            return features.get(0);
        } else {
            return null;
        }
    }

    private PoiGeoJsonObject findClickedPoi(Feature selectedFeature) {
        if (selectedFeature == null)
            return null;

        String id = selectedFeature.getStringProperty("uid");
        for (PoiGeoJsonObject poi : pois) {
            if (poi.uid.equals(id)) {
                return poi;
            }
        }

        return null;
    }

    private void createMarker(PoiGeoJsonObject selectedPoi, Feature selectedFeature) {
        if (selectedPoi == null || selectedFeature == null)
            return;

        String typeField = selectedPoi.type;
        AnnotationPoint selectedPOI = featureToAnnotationPoint(selectedFeature);

        double lat = selectedPOI.coordinates[1];
        double lon = selectedPOI.coordinates[0];
        Marker marker = mapboxMap.addMarker(new MarkerOptions()
                .position(new LatLng(lat, lon))
                .title(typeField));
        marker.showInfoWindow(mapboxMap, mapView);
    }

    private void removeMarkers(MapboxMap mapboxMap) {
        List<Marker> markers = mapboxMap.getMarkers();
        for (Marker marker : markers) {
            mapboxMap.removeMarker(marker);
        }
    }

    private void setInitialCamera() {
        CameraPosition cameraPosition = new CameraPosition.Builder()
                .target(new LatLng(49.867630660511715, 10.89075028896332)) // Sets the new camera position
                .zoom(18) // Sets the zoom
                .bearing(0) // Rotate the camera
                .tilt(0) // Set the camera tilt
                .build();
        this.mapboxMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
    }

    public void loadPOIs() {
        String geojsonbaseURL = "https://tiles.infsoft.com/api/geoobj/json/";
        String icid = "/en/";
        String revision = "0";
        String urlString = geojsonbaseURL + API_KEY + icid + revision;
        String poiGeoJson = null;

        try {
            RestCall restCall = new RestCall();
            poiGeoJson = restCall.execute(urlString).get();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        try {
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
            pois = objectMapper.readValue(poiGeoJson, new TypeReference<ArrayList<PoiGeoJsonObject>>() {
            });
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private AnnotationPoint featureToAnnotationPoint(Feature feature) {
        Geometry geometry = feature.geometry();
        AnnotationPoint annotationPoint = null;
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
        try {
            annotationPoint = objectMapper.readValue(geometry.toJson(), AnnotationPoint.class);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
        return annotationPoint;
    }

    @Override
    public void onStart() {
        super.onStart();

        if (mapView != null) {
            mapView.onStart();
        }
    }

    @Override
    public void onResume() {
        super.onResume();

        if (mapView != null) {
            mapView.onResume();
        }
    }

    @Override
    public void onPause() {
        super.onPause();

        if (mapView != null) {
            mapView.onPause();
        }
    }

    @Override
    public void onStop() {
        super.onStop();

        if (mapView != null) {
            mapView.onStop();
        }
    }

    @Override
    public void onLowMemory() {
        super.onDestroy();

        if (mapView != null) {
            mapView.onLowMemory();
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        if (mapView != null) {
            mapView.onDestroy();
        }
    }

    @Override
    public void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);

        if (mapView != null) {
            mapView.onSaveInstanceState(outState);
        }
    }
}

AnnotationPoint

package com.infsoft.android.locaware.selectpoi;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

@JsonIgnoreProperties(ignoreUnknown = true)
class AnnotationPoint {
    // longitued/latitude, order is reversed!
    @JsonProperty("coordinates")
    public double[] coordinates;
}

PoiGeoJsonObject

package com.infsoft.android.locaware.selectpoi;


import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

import java.io.Serializable;
import java.util.HashMap;

@JsonIgnoreProperties(ignoreUnknown = true)
public class PoiGeoJsonObject implements Serializable {
    @JsonProperty("uid")
    public String uid;
    @JsonProperty("type")
    public String type;
    @JsonProperty("props")
    public HashMap<String, String> props;
}

RestCall

package com.infsoft.android.locaware.selectpoi;

import android.os.AsyncTask;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

class RestCall extends AsyncTask<String, Void, String> {

    @Override
    protected String doInBackground(String... strings) {
        String urlString = strings[0];
        StringBuilder sb = new StringBuilder();
        try {
            URL url = new URL(urlString);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setRequestProperty("Accept", "application/poi");
            BufferedReader br = new BufferedReader(new InputStreamReader(
                    (conn.getInputStream())));
            String output;
            while ((output = br.readLine()) != null) {
                sb.append(output);
            }
            conn.disconnect();
        } catch (MalformedURLException e) {
            e.printStackTrace();
            return null;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
        return sb.toString();
    }
}

LevelSwitch

See common code


HTML

DISPLAY MAP

Initialize a map in an HTML element with Mapbox GL JS.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Display a map</title>
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
    <script src="https://api.mapbox.com/mapbox-gl-js/v1.6.1/mapbox-gl.js"></script>
    <link href="https://api.mapbox.com/mapbox-gl-js/v1.6.1/mapbox-gl.css" rel="stylesheet" />
    <style>
        body {
            margin: 0;
            padding: 0;
        }

        #map {
            position: absolute;
            top: 0;
            bottom: 0;
            width: 100%;
        }
    </style>
</head>
<body>
    <div id="map"></div>
    <script>

        // infsoft api key, please replace this with our own api key to display your maps
        var apiKey = '8c97d7c6-0c3a-41de-b67a-fb7628efba79';

        // generating the style url for map box
        var styleURL = 'https://tilesservices.webservices.infsoft.com/api/mapstyle/style/{apiKey}?config=level:{level}|3d:{3d}';
        styleURL = styleURL.replace('{apiKey}', apiKey);
        styleURL = styleURL.replace('{level}', '0');
        styleURL = styleURL.replace('{3d}', 'false');

        var map = new mapboxgl.Map({
            container: 'map', // container id
            style: styleURL, // stylesheet location including infsoft api key
            center: [10.89075028896332, 49.867630660511715], // starting position [lng, lat]
            zoom: 18 // starting zoom
        });
    </script>

</body>
</html>

APIKey

  • Please replace the apiKey variable with your own APIKey to display your own maps

DISPLAY MAP 3D

Initialize a 3D map in an HTML element with Mapbox GL JS.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Display a map</title>
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
    <script src="https://api.mapbox.com/mapbox-gl-js/v1.6.1/mapbox-gl.js"></script>
    <link href="https://api.mapbox.com/mapbox-gl-js/v1.6.1/mapbox-gl.css" rel="stylesheet" />
    <style>
        body {
            margin: 0;
            padding: 0;
        }

        #map {
            position: absolute;
            top: 0;
            bottom: 0;
            width: 100%;
        }
    </style>
</head>
<body>
    <div id="map"></div>
    <script>

        // infsoft api key, please replace this with our own api key to display your maps
        var apiKey = '8c97d7c6-0c3a-41de-b67a-fb7628efba79';

        // generating the style url for map box
        var styleURL = 'https://tilesservices.webservices.infsoft.com/api/mapstyle/style/{apiKey}?config=level:{level}|3d:{3d}';
        styleURL = styleURL.replace('{apiKey}', apiKey);
        styleURL = styleURL.replace('{level}', '0');
        styleURL = styleURL.replace('{3d}', 'true');

        var map = new mapboxgl.Map({
            container: 'map', // container id
            style: styleURL, // stylesheet location including infsoft api key
            center: [10.89075028896332, 49.867630660511715], // starting position [lng, lat]
            zoom: 18, // starting zoom
            pitch: 60 // starting pitch
        });
    </script>

</body>
</html>

APIKey

  • Please replace the apiKey variable with your own APIKey to display your own maps

SWITCH LEVELS

Switch level of a map in an HTML element with Mapbox GL JS.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Display a map</title>
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
    <script src="https://api.mapbox.com/mapbox-gl-js/v1.6.1/mapbox-gl.js"></script>
    <link href="https://api.mapbox.com/mapbox-gl-js/v1.6.1/mapbox-gl.css" rel="stylesheet" />
    <style>
        body {
            margin: 0;
            padding: 0;
        }

        #map {
            position: absolute;
            top: 0;
            bottom: 0;
            width: 100%;
        }

        button {
            position: absolute;
            margin: 20px;
        }
    </style>
</head>
<body>
    <script>

        // extension for LocAware SDK
        function LocAwareExt(map) {

            this.map = map;

            // internal helper
            this.setLevelPropertyOfFilter = function (filter, level) {
                if (!Array.isArray(filter))
                    return filter;

                for (var i = 0; i < filter.length; i++) {
                    var item = filter[i];
                    if (Array.isArray(item)) {
                        filter[i] = this.setLevelPropertyOfFilter(item, level);
                        continue;
                    }

                    if (i === 0)
                        continue;

                    var prev = filter[i - 1];
                    var current = filter[i];

                    if (typeof prev !== 'string')
                        continue;

                    if (prev !== 'level')
                        continue;

                    if (typeof current !== 'number')
                        continue;

                    filter[i] = level;
                }

                return filter;
            };


            // set the floor level of the map
            this.setLevel = function (level) {
                // get all layers from map box gl
                var layers = this.map.getStyle().layers;

                // iterate all layers
                for (var i = 0; i < layers.length; i++) {
                    var layer = layers[i];

                    // layer is not a specific level layer
                    if (!layer.id.includes('locls'))
                        continue;

                    // get the filter of the layer from map box gl
                    var filter = this.map.getFilter(layer.id);
                    filter = this.setLevelPropertyOfFilter(filter, level);
                    this.map.setFilter(layer.id, filter);
                }
            };
        }

    </script>


    <div id="map"></div>
    <button id="switchlevel">SWITCH LEVEL</button>
    <script>

        // infsoft api key, please replace this with our own api key to display your maps
        var apiKey = '8c97d7c6-0c3a-41de-b67a-fb7628efba79';

        // generating the style url for map box
        var styleURL = 'https://tilesservices.webservices.infsoft.com/api/mapstyle/style/{apiKey}?config=level:{level}|3d:{3d}';
        styleURL = styleURL.replace('{apiKey}', apiKey);
        styleURL = styleURL.replace('{level}', '0');
        styleURL = styleURL.replace('{3d}', 'false');

        var map = new mapboxgl.Map({
            container: 'map', // container id
            style: styleURL, // stylesheet location including infsoft api key
            center: [10.89075028896332, 49.867630660511715], // starting position [lng, lat]
            zoom: 18 // starting zoom
        });

        // add click event for button 'SWITCH LEVEL'
        var currentLevel = 0;
        var butSwitchLevel = document.getElementById('switchlevel');
        butSwitchLevel.addEventListener('click', function () {
            // apply new level
            currentLevel = (currentLevel + 1) % 4;

            // apply it to map via helper class
            var helper = new LocAwareExt(map);
            helper.setLevel(currentLevel);
        });

    </script>

</body>
</html>

APIKey

  • Please replace the apiKey variable with your own APIKey to display your own maps

DISPLAY ROUTE

Display route in an HTML element with Mapbox GL JS.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Display a map</title>
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
    <script src="https://api.mapbox.com/mapbox-gl-js/v1.6.1/mapbox-gl.js"></script>
    <link href="https://api.mapbox.com/mapbox-gl-js/v1.6.1/mapbox-gl.css" rel="stylesheet" />
    <style>
        body {
            margin: 0;
            padding: 0;
        }

        #map {
            position: absolute;
            top: 0;
            bottom: 0;
            width: 100%;
        }

        button {
            position: absolute;
            margin: 20px;
        }
    </style>
</head>
<body>
    <div id="map"></div>
    <button id="but">CALC ROUTE</button>
    <script>

        // methode to demo calc route and display route in map
        function calcRoute(map) {

            // calc route | url
            var calcRouteURL = 'https://routes.webservices.infsoft.com/API/Calc?apikey={apiKey}&startlat={startlat}&startlon={startlon}&startlevel={startlevel}&endlat={endlat}&endlon={endlon}&endlevel={endlevel}&lcid=EN&context=';

            // set correct api key
            calcRouteURL = calcRouteURL.replace('{apiKey}', apiKey);

            // set start position
            calcRouteURL = calcRouteURL.replace('{startlat}', 49.86739);
            calcRouteURL = calcRouteURL.replace('{startlon}', 10.89190);
            calcRouteURL = calcRouteURL.replace('{startlevel}', 0);

            // set destination position
            calcRouteURL = calcRouteURL.replace('{endlat}', 49.86701);
            calcRouteURL = calcRouteURL.replace('{endlon}', 10.89054);
            calcRouteURL = calcRouteURL.replace('{endlevel}', 0);

            // cal rest api to get route
            fetch(calcRouteURL).then(res => res.json()).then((out) => {

                // our calced route
                var calcedRoute = out[0];

                // we could not calc a route
                if (!calcedRoute.Valid) {
                    console.log('Calc route failed!');
                    return;
                }

                // get the geo json for the map
                var geoJsonForMap = calcedRoute.GeoJson;

                // set the layers data
                var source = map.getSource('loc-routes').setData(geoJsonForMap);
            }).catch(err => { throw err });
        }


        // infsoft api key, please replace this with our own api key to display your maps
        var apiKey = '8c97d7c6-0c3a-41de-b67a-fb7628efba79';

        // generating the style url for map box
        var styleURL = 'https://tilesservices.webservices.infsoft.com/api/mapstyle/style/{apiKey}?config=level:{level}|3d:{3d}';
        styleURL = styleURL.replace('{apiKey}', apiKey);
        styleURL = styleURL.replace('{level}', '0');
        styleURL = styleURL.replace('{3d}', 'false');

        var map = new mapboxgl.Map({
            container: 'map', // container id
            style: styleURL, // stylesheet location including infsoft api key
            center: [10.89075028896332, 49.867630660511715], // starting position [lng, lat]
            zoom: 18 // starting zoom
        });


        // add click event for button
        var but = document.getElementById('but');
        but.addEventListener('click', function () {
            // calc a route
            calcRoute(map);
        });


    </script>

</body>
</html>

APIKey

  • Please replace the apiKey variable with your own APIKey to display your own maps

Any questions or ready to get started? Get in touch with us!

Keyboard Typing
Privacy policyThe provided data will only be used for the purpose of processing your request.