Return to Resources

Indoor Orientation with Oriient and Mappedin

Oct 16, 2023

5 min read

By: Mark Sohm

Gif showcasing Indoor Orientation

Pinpointing your exact location has become effortless with the advent of smartphone maps. With just a few taps, you can see the reassuring blue dot indicating your precise position. However, what happens when you find yourself indoors, where the smartphone's location capabilities face limitations? In such situations, your smartphone loses its ability to receive signals from global positioning satellites, leaving you with potentially inaccurate cellular-based location services that could be off by hundreds of meters. The solution? Oriient’s Indoor Positioning System (IPS) and the Mappedin SDK.

The Mappedin SDK offers the capability to fetch location data directly from a smartphone's native APIs. Furthermore, it allows your app to supply location data via an external IPS. Regardless of the location source, users will enjoy a consistent experience. 

Let’s build a solution that integrates Oriient’s indoor GPS into an Android application to display a user’s location using Mappedin’s SDK for Android. The full Android Studio project can be found in Mappedin’s Android Github repository here: Mappedin With Oriient

Prerequisites

To begin, I’ll assume you already have an Android project that is using the Mappedin SDK. If you don’t, first head over to the Mappedin Getting Started with Android SDK Guide. I’m also going to assume you have an indoor location set up and mapped with Oriient and have an API key to use the Oriient SDK. Oriient will also provide access to their private Maven repository, which is used to distribute their library. Once those prerequisites are met, you’re ready to continue.

Request Permissions

The application requires the following permissions to acquire positioning information.

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

Ensure these permissions are added to the AndroidManifest.xml and requested within the application code. You can find great examples on how to do so in the Android Request location permissions guide, and also in my previous Mappedin blog post.

Map Maps to Floor Elevation

The positioning data received from Oriient includes the user’s latitude and longitude, as well as another important piece of information, which is the building floor the user is on. With each location update the app needs to verify that the floor being displayed matches the floor the user is on. If it doesn’t, we need to change the map. To allow for quickly locating the Map Id from the floor, let’s create a HashMap that stores Map Ids of all maps based on their elevation (floor number).

override fun onDataLoaded(data: MPIData) {
mMapsByFloor = HashMap<Int, String>()
data.maps.forEach(
Consumer<MPIMap> { (id, _, _, elevation): MPIMap ->
if (elevation != null) {
mMapsByFloor[elevation.toInt()] = id
}
},
)
}

Log into Oriient & Start Positioning

Once the user has granted location permissions, the app can take the first steps to display their location, which is to log into Oriient.

private fun loginToOriient() {
// TODO: Enter your userID, apiKey and ipsDomain here.
IPSCore.login(
"yourUserId",
"yourApiKey",
"yourIpsDomain",
object : IPSLoginListener {
override fun onError(loginError: IPSError) {
Log.d(TAG, "onError() called with error: " + loginError)
}
override fun onLogin(list: List<IPSSpace>) {
postOriientLogin()
}
},
)
}

After a successful login we can register an IPSPositioningListener and enable automatic calibration. While calibration is in progress we can start positioning to receive location updates immediately. The accuracy of the position updates improves as the calibration phase moves to completion.

private fun postOriientLogin() {
// The observer will notify the calibration progress and necessity.
IPSPositioning.addPositioningListener(this)
// Turn on automatic calibration.
IPSPositioning.setAutomaticCalibrationEnabled(true)
startPositioning()
}

To start receiving position updates, IPSPositioning.startPositioning is called. Once positioning has started, Blue Dot is enabled on the Mappedin SDK.

private fun startPositioning() {
// Start positioning.
IPSPositioning.startPositioning(
"",
null,
null,
object : IPSCompletionListener {
override fun onCompleted() {
// Enable Blue Dot.
mMapView?.blueDotManager?.enable(MPIOptions.BlueDot())
mIsBlueDotEnabled = true
}
override fun onError(error: IPSError) {
Log.e(
TAG,
"Failed to start positioning: error = " + error.code + " [" + error.message + "]",
)
}
},
)
}

At this point the app has requested positioning, however calibration may be required. The Oriient SDK will attempt to perform calibration in the background, which could begin before the user has navigated to a mapping screen requiring their location. In this case of this sample, the map is displayed right away so there isn’t an opportunity for background calibration. The Orrient SDK has an IPSCalibrationDialog that can be displayed to the user to help them through the calibration process. The dialog provides instructions on how to move their device to speed up calibration and displays a progress indicator showing calibration progress. We can use the onCalibrationGestureNeeded method to detect if the IPSCalibrationDialog should be shown.

override fun onCalibrationGestureNeeded(calibNeeded: Boolean, reason: Int?) {
Log.i(TAG, "onCalibrationGestureNeeded? " + calibNeeded + " Reason: " + reason)
runOnUiThread() {
if (calibNeeded) {
if (calibrationDialog == null) {
calibrationDialog = IPSCalibrationDialog.Builder(this)
.create()
}
calibrationDialog?.show()
} else {
calibrationDialog?.dismiss()
}
}
}

The final step is to provide the locations received from Oriient to the Mappedin SDK, which draws the Blue Dot at that location.  Location updates are received in the IPSPositioningListener.onPositionUpdate method. Within this method we create an MPIPosition.MPICoordinates object that holds the user’s position and use that object to create an MPIPosition. Next, we check if the user has changed floors. If they have, we look up the map Id based on the floor number and change the map that is displayed by calling setMap. The last step is to pass the user’s position to the MPIMapView, which will draw the Blue Dot in the appropriate location.

override fun onPositionUpdate(position: IPSPosition) {
val floorLevel = position.floorOrder
val coordinates = MPIPosition.MPICoordinates(
position.latitude,
position.longitude,
position.accuracy,
floorLevel,
)
val mpiPosition = MPIPosition(
System.currentTimeMillis().toDouble(),
coordinates,
"",
null,
)
mMapView?.blueDotManager?.updatePosition(mpiPosition)
}

Update the Map on Floor Changes

As users travel vertically in buildings with multiple floors the app will need to update the map to display the floor they are on. The onFloorChanged method is fired when it’s detected that the user changed floors. When it does, verify whether a new map should be shown and change the map if needed.

override fun onFloorChanged(floor: IPSFloor) {
Log.i(TAG, "onFloorChanged: " + floor)
// Change maps if the floor has changed.
if (mIsMapLoaded && mMapView?.currentMap?.elevation?.toInt() != floor.order) {
Log.i(
TAG,
"Changing Floor from " + mMapView?.currentMap?.elevation +
" to " + floor.order,
)
mMapsByFloor[floor.order]?.let {
mMapView?.setMap(it) { err ->
err?.message?.let { message ->
Log.e("setMap ", message)
}
}
}
}
}

Arrive At Your Destination

We’ve now created a solution that can receive indoor positions from Oriient and display them as a Blue Dot on a Mappedin Map. User’s everywhere can now navigate and explore without fear of becoming lost. Are you interested in learning more? Please reach out to our team and find out how Mappedin can help optimize your facility with indoor maps.