LogoLogo
CMSGitHubSupportBook a demo
  • Documentation
  • Academy
  • Help Center
  • Welcome
  • SDKS & Frameworks
    • Web
      • Getting Started
        • Prerequisites
          • MapsIndoors
          • Map Engine Provider
            • Option 1: Get your Mapbox Access Token
            • Option 2: Get your Google Maps API Keys​
          • Map Engine Setup
        • Getting Started: MapsIndoors
      • Map Visualization
        • Highlight, Hover and Select
        • Remove Labels from Buildings and Venues
        • Change Building Outline
        • Managing Collisions Based on Zoom Level
        • 3D Maps
          • Managing your 3D Maps
        • Base Map Styling - Google Maps
        • Managing feature visibility for Mapbox
      • Wayfinding
        • Directions
        • Directions Service
          • Tailoring the directions to your specific needs
        • Directions Renderer
          • Customizing the Route Animation
        • Multi-stop navigation
          • Custom Icons
        • User's Location as Point of Origin
      • Search
        • Search Operations
        • Searching
        • Using External ID, Geospatial Joins
        • Utilizing MapsIndoors Web Components and Other Searches
      • Map Management
      • Data Visualization
        • Display Heatmap Overlay
      • Other guides
        • Authentication
          • Single Sign-On
            • SSO Configuration
            • SSO Authorisation
          • 2-Factor Authentication
          • Password Reset
        • Application User Roles
        • Custom Properties
        • Display Language
        • Language
        • User Positioning
          • Show User's Location aka. Blue Dot
          • Using Cisco DNA Spaces
        • Working with Events
        • Turn Off Collisions Based on Zoom Level
        • Remove Labels from Buildings and Venues for Web
        • Synchronizing data for a subset of venues
        • Custom Floor Selector
      • Display Rules in Practice
      • Offline Data
      • Managing map visibility
    • Android
      • Getting Started
        • Prerequisites
        • Create a New Project
        • Show a Map
        • Create a Search Experience
        • Getting Directions
        • Enable Live Data
        • Integrating MapsIndoors into your own App
        • Migrating from V3 to V4
          • Migrating to Mapbox V11
      • Directions
        • Directions Service
        • Directions Renderer
          • User's Location as Point of Origin
        • Wayfinding Instructions
          • See Route Element Details
        • Using multi-stop navigation
      • Searching
        • Searching on a Map
        • Creating a Search Experience
      • Switching Solutions
      • Caching & Offline Data
      • Display Language
      • Displaying Objects
        • Application User Roles
        • Getting a Polygon from a Location
        • Location Clustering
        • Location Data Sources
        • Location Details
        • Turn Off Collisions Based on Zoom Level
        • Enabling and Disabling features on the map
      • Change Building Outline Color
      • Event Logging
      • Configuring a menu with AppConfig
      • Display Heatmap Overlay
      • Custom Properties
      • Custom Floor Selector
      • External IDs
      • User Positioning
        • Show User's Location aka. Blue Dot
        • Using Cisco DNA Spaces
        • Using Google Fused Location Provider
        • Using Indoor Atlas
      • Authentication
        • Single Sign-On
          • SSO Configuration
          • SSO Authorisation
        • 2-Factor Authentication
        • Password Reset
      • Display Rules in Practice
        • Label styling through Display Rules
      • Highlight and Select
    • iOS
      • Getting Started
        • Prerequisites
        • Set Up Your Environment
        • Display a Map
        • Search
        • Getting Directions
        • Migrating from v3 to v4
      • Directions
        • Directions Renderer
          • User's Location as Point of Origin
        • Wayfinding Instructions
          • See Route Element Details
        • Directions Service
        • Using multi-stop navigation
      • Searching
        • Searching on a Map
        • Creating a Search Experience
      • Caching & Offline Data
      • Displaying Objects
        • Application User Roles
        • Getting a Polygon from a Location
        • Location Details
        • Turn Off Collisions Based on Zoom Level
        • Enabling and Disabling features on the map
      • Custom Floor Selector
      • Change Building Outline Color
      • Custom Map Padding
      • Custom Properties
      • Display Rules in Practice
        • Label styling through Display Rules
      • Switching Solutions
      • Show User's Location aka. Blue Dot
        • Using Indoor Atlas
        • Using Cisco DNA Spaces
      • Highlight and Select
      • Display Language
    • React Native
      • Getting Started
        • Prerequisites
        • Project Setup
        • Displaying a Map
        • Creating a Search Experience
        • Getting Directions
        • Enabling Live Data
      • Showing Blue Dot
    • Flutter
      • Getting Started
        • Prerequisites
        • Create a New Project
        • Show a Map
        • Create a Search Experience
        • Getting Directions
      • Migration Guide
    • Integration API
      • Integration API Access
        • Access with Swagger
        • Access with Postman
        • Access with Python
        • Client credentials flow
      • Data Description
      • Reverse Geocoding
      • Route Access
      • OpenAPI Specification
    • Built-In Map Edits
      • Getting started
      • Authentication
      • Release notes
      • Reference docs
  • Products
    • Product Overview
    • CMS
      • Interface Overview
      • Display Rules
      • Media Library
        • 2D Models and Icons
        • 3D Models
      • Editing Data
      • Solution Settings
      • Settings
      • Data Concepts
      • User Roles
      • Route Network
        • Barrier Route Element
        • Door Route Element
      • Additional Location Details
    • Map Template
      • Getting Started
        • Web Component
        • React Component
      • Configuration
        • Query Parameters
      • Customization
      • Deploying Map Template to a cloud storage provider
      • 2D/3D Visibility Switch
      • External customization of the Map Template
      • Location Details configuration
      • Kiosk
        • QR code configuration
  • Other
    • Design
      • Standard MapsIndoors Map Style
      • Using a Custom Mapbox MapStyle
    • Changelog
      • Web SDK
        • V4
        • V3
      • Android SDK
        • V4
        • V3
      • iOS SDK
        • V4
        • V3
      • React Native SDK
      • Flutter SDK
      • MI Components
      • Map Template
    • Glossary
  • Legacy Docs
    • Android SDK V3
      • Getting Started
        • Prerequisites
        • Create a New Project
        • Show a Map
        • Create a Search Experience
        • Getting Directions
        • Enable Live Data
        • Integrating MapsIndoors into your own App
    • iOS SDK V3
      • Getting Started
        • Prerequisites
        • Set Up Your Environment
        • Display a Map
        • Search
        • Directions
        • Live Data
        • Integrating MapsIndoors into your own App
      • Inspect Route Element for iOS v3
      • Using Cisco DNA Spaces
      • Using Indoor Atlas
      • Switching Solutions
      • Show User's Location aka. Blue Dot
      • Application User Roles
      • Getting a Polygon from a Location
      • Location Details
  • MapsIndoors SDK Firewall
  • Google Analytics & Logging
  • Reference Docs
    • Web SDK
    • Android SDK
    • iOS SDK
    • React Native SDK
    • Flutter SDK
Powered by GitBook
On this page

Was this helpful?

Export as PDF
  1. SDKS & Frameworks
  2. Android
  3. User Positioning

Using Cisco DNA Spaces

Last updated 1 year ago

Was this helpful?

To get started with Cisco DNA positioning, the MapsIndoors SDK offers all the required building blocks without any external dependencies.

The Position Provider implementation exists at the customer application level, and needs to use the MPPositionProvider interface from the MapsIndoors SDK. The MapsIndoors SDK can then use the positioning results given by the Position Provider when setting the Position Provider with MapsIndoors.setPositionProvider(MPPositionProvider).

Floor Mapping for Android

The Position Provider should align with the MapsIndoors Floor index convention (floors are indexed as e.g 0, 10, 20, 30 corresponding to ground floor, 1st floor, 2nd floor, 3rd floor, with negative floors indices allowed as well to indicate Floors below ground, e.g. -10). It is therefore up to the Position Provider class to convert any given Floor indexing from the positioning source to that of MapsIndoors.

For a typical Position Provider, the mapping from the positioning's index needs to be mapped to the MapsIndoors Floor format.

The MapsIndoors backend is closely integrated with the CiscoDNA platform, so the MapsIndoors backend handles the floor mapping conversion for that integration. From an application perspective no Floor mapping implementation is required when integrating CiscoDNA positioning through the MapsIndoors platform.

Fetch Attributes from Solution

You can choose to fetch the Position Provider information (CMS > Solution Details > App Settings > Position Provider) from the CMS as follows:

Map<String, Map<String, Object>> providerConfig = MapsIndoors.getSolution().getPositionProviderConfig();

The outer keyset (Map<String, Map<String, Object>>) contains the name of the positioning provider, for example, indooratlas3 for IndoorAtlas, or ciscodna when using Cisco DNA Spaces.

The inner keyset (Map<String, Object>) consist of various attribute fields for a given positioning provider, such as keys, floor mapping etc. These attribute fields will vary across different positioning providers, so refer to their own documentation for details.

Implementing Cisco DNA for Android

This Guide requires you to already have an activity that shows a MapsIndoors Map as well as a Cisco DNA network with positioning active.

Now we will start implementing the CiscoDNA position provider. Create a class called CiscoDNAPositionProvider that implements the MPPositionProvider interface from the MapsIndoors SDK. Then create a constructor that takes a Context.

class CiscoDNAPositionProvider (private val context: Context, private val config: MPCiscoDNAConfig): MPPositionProvider {
    private var mLatestPosition: MPPositionResultInterface? = null
    private val positionUpdateListeners = ArrayList<OnPositionUpdateListener>()
    //If you do not have CiscoDNA set up through MapsIndoors you can write your tenantId here instead of using one from the config
    private val tenantId: String? = config.tenantId
}

We will start by implementing logic to each of the implemented methods from the MPPositionProvider interface. Some of these will be generic, while others will be specific to CiscoDNA.

class CiscoDNAPositionProvider (private val context: Context, private val config: MPCiscoDNAConfig): MPPositionProvider {
    private var mLatestPosition: MPPositionResultInterface? = null
    private val positionUpdateListeners = ArrayList<OnPositionUpdateListener>()
    ...

    override fun addOnPositionUpdateListener(p0: OnPositionUpdateListener) {
        positionUpdateListeners.add(p0)
    }

    override fun removeOnPositionUpdateListener(p0: OnPositionUpdateListener) {
        positionUpdateListeners.remove(p0)
    }

    override fun getLatestPosition(): MPPositionResultInterface? {
        return mLatestPosition
    }
}

The CiscoDNA positioning requires three parameters:

  1. The device’s IPv4 address (LAN)

  2. The external IP address of the network in question (WAN)

  3. The Tenant ID

Start by creating a method to retrieve the LAN address:

class CiscoDNAPositionProvider (private val context: Context, private val config: MPCiscoDNAConfig): MPPositionProvider {
    private var ciscoDeviceId: String? = null
    private var lan: String? = null
    private var wan: String? = null
    ...
    private fun getLocalAddress(): String? {
        try {
            val en = NetworkInterface.getNetworkInterfaces()
            while (en.hasMoreElements()) {
                val intf = en.nextElement()
                val enumIpAddr = intf.inetAddresses
                while (enumIpAddr.hasMoreElements()) {
                    val inetAddress = enumIpAddr.nextElement()
                    if (!inetAddress.isLoopbackAddress && inetAddress is Inet4Address) {
                        return inetAddress.getHostAddress()
                    }
                }
            }
        } catch (ex: SocketException) {
            Log.e(this.javaClass.simpleName, "Failed to resolve LAN address")
        }
        return null
    }
    ...
}

Then create a method to retrieve the WAN address (we recommend using a 3rd party service for this):

class CiscoDNAPositionProvider (private val context: Context, private val config: MPCiscoDNAConfig): MPPositionProvider {
    private var ciscoDeviceId: String? = null
    private var lan: String? = null
    private var wan: String? = null
    ...
    private fun fetchExternalAddress(listener: MPReadyListener) {
        val httpClient = OkHttpClient()
        val request: Request = Request.Builder().url("https://ipinfo.io/ip").build()
        httpClient.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                listener.onResult()
            }

            @Throws(IOException::class)
            override fun onResponse(call: Call, response: Response) {
                if (response.isSuccessful) {
                    val str = response.body!!.string()
                    wan = str
                    response.close()
                }
                listener.onResult()
            }
        })
    }
    ...
}

If all of the three above mentioned strings can be acquired, you can ask our endpoint for a CiscoDNA Device ID string. A device ID is only available if there has been a recorded positioning for the device in the past 24 hours. We will implement this as a new method into our CiscoDNAPositionProvider.

class CiscoDNAPositionProvider (private val context: Context, private val config: MPCiscoDNAConfig): MPPositionProvider {
    private var ciscoDeviceId: String? = null
    private var lan: String? = null
    private var wan: String? = null
    val MAPSINDOORS_CISCO_ENDPOINT = "https://ciscodna.mapsindoors.com/"
    ...
    /**
     * This method is responsible for gathering the local and external IP addresses
     * as well as acquiring a device ID from the Cisco DNA API.
     */
    private fun updateAddressesAndId(onComplete: MPReadyListener?) {
        lan = getLocalAddress()
        //mCiscoDeviceId = null;
        fetchExternalAddress {
            if (tenantId != null && lan != null && wan != null) {
                val url: String = "$MAPSINDOORS_CISCO_ENDPOINT$tenantId/api/ciscodna/devicelookup?clientIp=$lan&wanIp=$wan"
                val client = OkHttpClient()
                val request: Request = Request.Builder().url(url).build()
                try {
                    val response = client.newCall(request).execute()
                    if (response.isSuccessful) {
                        val gson = Gson()
                        val json = response.body!!.string()
                        val jsonObject =
                            gson.fromJson(json, JsonObject::class.java)
                        ciscoDeviceId = jsonObject["deviceId"].asString
                    } else {
                        Log.d(
                            "ciscodnaprovider",
                            "Could not obtain deviceId from backend deviceID request! Code: " + response.code
                        )
                    }
                    response.close()
                } catch (e: IOException) {
                    e.printStackTrace()
                }
            }
            onComplete?.onResult()
        }
    }
    ...
}

Now you can make a method to start a subscription that we use when starting positioning to receive position updates. You use the SDKs LiveDataManager to create a subscription.

class CiscoDNAPositionProvider (private val context: Context, private val config: MPCiscoDNAConfig): MPPositionProvider {
    private var ciscoDNATopic: CiscoDNATopic? = null;
    ...
    private fun startSubscription() {
        ciscoDNATopic = CiscoDNATopic(tenantId!!, ciscoDeviceId!!)
        LiveDataManager.getInstance().setOnTopicSubscribedListener { topic: MPLiveTopic ->
            if (topic == ciscoDNATopic) {
                Log.i("CiscoDNA", "topic subscribed to succesfully")
            }
        }
        LiveDataManager.getInstance().subscribeTopic(ciscoDNATopic)
    }

    private fun unsubscribe() {
        LiveDataManager.getInstance().unsubscribeTopic(ciscoDNATopic)
    }
    ...
}

To handle the subscription we just created, we need to create some callbacks in the constructor of the MPPositionProvider to handle the position results and lifecycle of the subscription:

class CiscoDNAPositionProvider (private val context: Context, private val config: MPCiscoDNAConfig): MPPositionProvider {
    init {
        LiveDataManager.getInstance().setOnReceivedLiveUpdateListener { mpLiveTopic, liveUpdate ->
            if (liveUpdate.id == ciscoDeviceId) {
                mLatestPosition = liveUpdate.positionResult
                notifyPositionUpdate()
            }
        }
    }
    ...
}

Implement the startPositoning and stopPositioning method as well as a update and obtainInitialPosition to get the initial position from CiscoDNA:

class CiscoDNAPositionProvider (private val context: Context, private val config: MPCiscoDNAConfig): MPPositionProvider {
    val MAPSINDOORS_CISCO_ENDPOINT = "https://ciscodna.mapsindoors.com/"
    private val tenantId: String? = config.tenantId
    private var ciscoDeviceId: String? = null
    ...

    fun startPositioning() {
        update { startSubscription() }
    }

    fun stopPositioning() {
        LiveDataManager.getInstance().unsubscribeTopic(ciscoDNATopic)
    }

    fun update(onreadyListener: MPReadyListener) {
        updateAddressesAndId {
            if (!ciscoDeviceId.isNullOrEmpty()) {
                if (mLatestPosition == null) {
                    obtainInitialPosition(onreadyListener)
                }else {
                    onreadyListener.onResult()
                }
            }
        }
    }

    private fun obtainInitialPosition(listener: MPReadyListener) {
        val url = "$MAPSINDOORS_CISCO_ENDPOINT$tenantId/api/ciscodna/$ciscoDeviceId"
        val request: Request = Request.Builder().url(url).build()
        if (ciscoDeviceId != null && tenantId != null) {
            val httpClient = OkHttpClient()
            httpClient.newCall(request).enqueue(object : Callback {
                override fun onFailure(call: Call, e: IOException) {
                    listener.onResult()
                }

                @Throws(IOException::class)
                override fun onResponse(call: Call, response: Response) {
                    if (response.code == HttpURLConnection.HTTP_NOT_FOUND) {
                        listener.onResult()
                        return
                    }
                    val json = response.body!!.string()
                    val positionResult: CiscoDNAEntry = Gson().fromJson(json, CiscoDNAEntry::class.java)
                    mLatestPosition = positionResult
                    notifyPositionUpdate()
                    listener.onResult()
                    response.close()
                }
            })
        }
    }
    ...
}

Our implemented positioning provider will be handled in an activity or fragment.

class myFragment: Fragment(), OnMapReadyCallback {
    private var mPositionProvider: CiscoDNAPositionProvider? = null

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?,): View {
        ...
        MapsIndoors.load(requireActivity().applicationContext, "myapikey") {
            mPositionProvider = IndoorAtlasPositionProvider(requireActivity(), MapsIndoors.getSolution()!!.indoorAtlasConfig!!)
            // Attach the position provider to the SDK
            MapsIndoors.setPositionProvider(mPositionProvider)
        }

        //Have a buttton or other logic to start positioning:
        binding.startPositioningButton.setOnClickListener {
            //Remember to handle permissions specific to the Position provider you are using
            mPositionProvider?.startPositioning()
        }
        ...
    }
}

Lastly, we need to tell MapControl that we want to show the position on the map.

MapControl.create(mapConfig) { mapControl: MapControl?, miError: MIError? ->
    mMapControl = mapControl

    // Enable showing the position indicator (aka. the blue dot)
    mMapControl?.showUserPosition(true)
}

A full example implementation of the Cisco DNA position provider can be found here:

​
​
​
CiscoDNAPositionProvider.kt
CiscoDNAPositionProvider.kt
CiscoDNAPositionProvider.kt
CiscoDNAPositionProvider.kt
CiscoDNAPositionProvider.kt
CiscoDNAPositionProvider.kt
CiscoDNAPositionProvider.kt
CiscoDNAPositionProvider.kt
PositioningFragment.kt
PositioningFragment.kt
PositionProviders