Return to Resources

Find Your Way with Mappedin and Indoor Atlas

May 19, 2023

7 min read

By: Mark Sohm

Office map with moving blue dot showing the user's location.

These days, finding where you are is as simple as opening up a map on your smartphone. Within seconds, that comforting blue dot appears. It shows you exactly where you are. But what about when you move indoors and the location capabilities of smartphones are more limited? When indoors, your smartphone is not able to receive signals sent from global positioning satellites. Cellular based location services may still be available, but their accuracy can be hundreds of meters off. The solution? An Indoor Positioning System (IPS) and the Mappedin SDK.

The Mappedin SDK supports retrieving location data directly from a smartphone’s native APIs. In addition to this, it also allows your app to provide it with location data through an external IPS. The experience from the user’s point of view is the same, regardless of where their location data is coming from. What they will notice is the increased accuracy when using an IPS.

In this blog post I will explain how you can integrate an IPS from Indoor Atlas into your Android application and provide location updates to the Mappedin SDK. The complete Android Studio project for the solution in this article is available from Mappedin’s Android sample repository here: Mappedin With Indoor Atlas

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 Indoor Atlas and have an Indoor Atlas application key. More information on that can be found on the Indoor Atlas Location Solution page. Once those prerequisites are met, you’re ready to continue.

Project Configuration

Let’s begin by integrating the Indoor Atlas Android SDK with our Android Studio project. First, add the Indoor Atlas Maven repository to your project. Where you add this will vary depending on the version of Android Studio and Gradle used to create the project. For projects created with the current versions, this is added in the settings.gradle file. For older projects you can find the repository listing in your build.gradle file.

repositories {
    google()
    mavenCentral()
    maven {
        url "https://dl.cloudsmith.io/public/indooratlas/mvn-public/maven/"
    }
}

Next add the library in the app’s build.gradle file as shown below. Version 3.4.12 is the latest at the time of writing. 

implementation 'com.indooratlas.android:indooratlas-android-sdk:3.4.12@aar'

The Indoor Atlas SDK requires your API key and secret to be added to the AndroidManifest.xml. Add the meta-data tags shown below to your app’s manifest along with your API key and secret.

<application>
<meta-data
android:name="com.indooratlas.android.sdk.API_KEY"
android:value="api-key-here"/>
<meta-data
android:name="com.indooratlas.android.sdk.API_SECRET"
android:value="api-secret-here"/>
</application>

Further details on the steps above can be found on the Indoor Atlas Setup Positioning SDK with Android guide.

Request Permissions

This application will make use of the permissions below. The access location permissions are obviously needed to find the user’s location so we can display it on the map. Internet access is used by the Mappedin SDK to download the map. Bluetooth scanning is used by Indoor Atlas to locate Bluetooth beacons that have been deployed within the building to assist with location accuracy.

<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" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />

Add these permissions to your project’s AndroidManifest.xml file.

We’ll define an array of these permissions to use at runtime. This will make it easy to request them all at once.

private final String[] PERMISSIONS = {android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.ACCESS_COARSE_LOCATION,
android.Manifest.permission.INTERNET,
android.Manifest.permission.BLUETOOTH_SCAN};

The permission request will be initiated from the main activity’s onCreate method. I’ve created a method called permissionsCheck to coordinate this process. First it checks if permissions are granted. If they are, the app enables the location services and starts showing the user’s location. If not, it checks if the app should provide rationale as to why it needs these permissions. If we don’t need to explain to the user why we need these permissions because the app has already done so, it asks for permissions.

private void permissionsCheck() {
if (arePermissionsGranted()) {
//Enable Blue Dot
startShowingLocation();
} else if (isPermissionRationaleNeeded()) {
//Show dialog about required permissions.
showRationaleDialog();
} else {
//Ask for permissions.
requestPermissionLauncher.launch(PERMISSIONS);
}
}

Let’s take a look at the method that checks if all permissions have been granted. This method loops through permissions and returns true if all have been granted.

private boolean arePermissionsGranted() {
for (String permission : PERMISSIONS) {
if (ContextCompat.checkSelfPermission(this, permission) !=
PackageManager.PERMISSION_GRANTED) {
//A permission has not been granted.
return false;
}
}
return true;
}

The next method permissionsCheck calls is isPermissionRationalNeeded. Here we loop through all permissions again and ask the system if the app should explain why permissions are needed. Android keeps track of this to avoid prompting the user with the same explanation over and over. Now you can see why we created the array to store all permissions.

private boolean isPermissionRationaleNeeded() {
for (String permission : PERMISSIONS) {
if (shouldShowRequestPermissionRationale(permission)) {
//A permission has not been granted.
return false;
}
}
return true;
}

The project’s showRationaleDialog method shows an alert to the user explaining why the permissions are needed, with buttons to enable or deny the permission request. If the user presses enable, permissions are requested using requestPermissionLauncher. You can view the source of showRationaleDialog in the sample project.

requestPermissionLauncher uses the Android system to manage the actual permission request. It prompts the user for all of the permissions we defined in the PERMISSIONS array. We have to consider that the user may accept some but not all permissions, so the app checks whether all have been granted. If they have, location is enabled. If not, the app explains their location will not be shown on the map.

private final ActivityResultLauncher<String[]> requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), isGranted -> {
boolean allGranted = true;
for (Map.Entry<String, Boolean> pair : isGranted.entrySet()) {
if (!pair.getValue()) {
allGranted = false;
}
}
if (allGranted) {
//Enable Blue Dot.
startShowingLocation();
} else {
//Explain that Blue Dot won't be available.
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Location Disabled");
builder.setMessage("Location based permissions are not enabled. Your location will not be shown on the map.");
builder.create().show();
}
});

Starting Location Services

After we have obtained the user’s permission to access location services, we can enable the Blue Dot on the Mappedin map and initialize the Indoor Atlas location manager. An IALocationListener is also registered in the requestLocationUpdates method so that the app continues to receive location updates as the user moves.

private void startShowingLocation() {
isBlueDotEnabled = true;
mMapView.getBlueDotManager().enable(new MPIOptions.BlueDot());
mIALocationManager = IALocationManager.create(this);
mIALocationManager.requestLocationUpdates(IALocationRequest.create(), this);
}

Note that so far, we’ve only enabled the blue dot on the map after permissions have been granted. The first time the app runs, it will take the user some time to read and accept the permission requests. While this happens, the map is loaded in the background. But what about subsequent starts after the user has already granted permissions? This could lead to a race condition where the app may attempt to enable the blue dot before the map has loaded, which will fail. To deal with this situation, we’ll use the Mappedin MPIMapViewListener. Register the MPIMapViewListener in the onCreate method where the map is initialized by calling mMapView.setListener(this).

Now we can override the onFirstMapLoaded method, after which the map is ready for the blue dot to be enabled. We don’t want to enable it if the user hasn’t given location permission, so a global boolean that stores whether location services are enabled isBlueDotEnabled is checked first.

@Override
public void onFirstMapLoaded() {
if (isBlueDotEnabled) {
mMapView.getBlueDotManager().enable(new MPIOptions.BlueDot());
}
}

Our next step is to provide the incoming location data to the map to display the blue dot. IALocationListener.onLocationChanged fires to provide latitude, longitude, building floor and an accuracy estimate. A building may have multiple levels, so the first check is to verify that the floor displayed on the map matches the user’s location. If it’s different, we switch to the appropriate map. Depending on your venue, you may have to introduce some jitter correction based on the accuracy value before changing floors. Having the map quickly cycle between floors makes for a poor user experience.

Finally, we call updatePosition to pass the user’s location to the Mappedin SDK, which it uses to draw the blue dot on the map.

@Override
public void onLocationChanged(IALocation iaLocation) {
MPIPosition.MPICoordinates coordinates = new MPIPosition.MPICoordinates(
iaLocation.getLatitude(), iaLocation.getLongitude(), iaLocation.getAccuracy(),
iaLocation.getFloorLevel());
MPIPosition position = new MPIPosition((double)System.currentTimeMillis(), coordinates,
"", null);
double floorLevel = iaLocation.getFloorLevel();
if (mMapsByFloor != null && mMapView.getCurrentMap().getElevation() != floorLevel) {
Log.i(TAG, "Changing Floor from " + mMapView.getCurrentMap().getElevation() +
" to " + floorLevel);
mMapView.setMap(mMapsByFloor.get(floorLevel), (Function1<? super MPIError, Unit>) null);
}
mMapView.getBlueDotManager().updatePosition(position);
}

Indoor Atlas Lifecycle Handling

The app we’ve built is now able to load a Mappedin map, request permissions from the user, obtain the user’s location using Indoor Atlas and display it as a blue dot on a map. Some additional code is required to ensure proper Indoor Atlas resource handling when the activity is paused or destroyed. Refer to the Indoor Atlas SDK Teardown with Android guide for those requirements.

You’ve Found Your Way!

You’ve now found your way to the end of this article and now you’ll be able to help your users find their way as well. 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.