Android guide

Indoorway Android SDK

Twitter

Indoorway lets you find your way indoors. Check it out!

Features

  • Indoor location
  • Navigation
  • Map view
  • Documentation

Modules

Indoorway SDK consists two main modules:

  • location SDK - for indoor positioning,
  • map SDK - used for displaying map.

You can use one of them or both.

Requirements

  • Java 8 must be used for project compilation.

LocationSDK

  • Android 4.3+ (SDK v18),
  • BLE (Bluetooth Low Energy) support.

MapSDK

  • Android 4.0+ (SDK v14),
  • OpenGL ES 2.0 support.

Installation

In order to use Indoorway SDK you need to set up your build script.

Attention: error may occur during project build due to known third-party library (Jackson) bug. It is neccessary to include in application module script:

android {
    packagingOptions {
        pickFirst 'META-INF/LICENSE'
    }
    // ...
}

Gradle / Maven

{latest.version} is 1.0.0. You can check another versions here.

Module com.indoorway.android:common contains common set of required SDK classes. Librares com.indoorway.android:map and com.indoorway.android:location are independend to each other, they can be used separately or linked together.

Gradle:

Setup repository:

repositories {
    maven { url 'https://indoorway.com/sdk/android/repo/maven/' }
}

Setup dependencies:

// required for map and location modules
compile('com.indoorway.android:common:{latest.version}')

// optional if you don't want to use map view
compile('com.indoorway.android:map:{latest.version}')

// optional if you don't want to use indoor positioning
compile('com.indoorway.android:location:{latest.version}')  

Maven:

Setup repository:

<repositories>
    <repository>
      <id>indoorway</id>
      <name>Indoorway</name>
      <url>https://indoorway.com/sdk/android/repo/maven/</url>
    </repository>
</repositories>

Setup dependencies:

<!-- required for map and location modules -->
<dependency>
  <groupId>com.indoorway.android</groupId>
  <artifactId>common</artifactId>
  <version>{latest.version}</version>
</dependency>

<!-- optional if you don't want to use map view -->
<dependency>
  <groupId>com.indoorway.android</groupId>
  <artifactId>map</artifactId>
  <version>{latest.version}</version>
</dependency>

<!-- optional if you don't want to use indoor positioning -->
<dependency>
  <groupId>com.indoorway.android</groupId>
  <artifactId>location</artifactId>
  <version>{latest.version}</version>
</dependency>

Usage

Initial confguration

Before any use of the framework you must configure it using your API key. The best place to apply configuration is your Application class onCreate function.

Tip: see reference for creating custom implementation of Application class.

/**
 * Application's class. Defined in AndroidManifest.xml
 * Initializes Indoorway sdk modules.
 */
public class CustomApplication extends Application {

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

        // ...

        // define your api key
        String trafficApiKey = "<Your API key>";

        // sdk for map view and fetching map objects
        IndoorwayMapSdk.init(this, trafficApiKey);

        // sdk for indoor positioning
        IndoorwayLocationSdk.init(this, trafficApiKey);
    }

}

Map displaying

Firstly, define com.indoorway.android.map.sdk.view.IndoorwayMapView inside your layout:

<com.indoorway.android.map.sdk.view.IndoorwayMapView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_above="@+id/bottomLayout"
    android:layout_alignParentTop="true"
    android:id="@+id/mapView" />

Then refer it in your code:

IndoorwayMapView indoorwayMapView = (IndoorwayMapView) findViewById(R.id.mapView);

indoorwayMapView
    // optional: assign callback for map loaded event
    .setOnMapLoadCompletedListener(new OnMapLoadedListener() {
        @Override
        public void onAction(BuildingAndMapId buildingAndMapId) {
            // called on every new map load success
        }
    })
    // optional: assign callback for map loading failure
    .setOnMapLoadFailedListener(new GenericListenerArg0() {
        @Override
        public void onAction() {
            // called on every map load error
        }
    })
    // perform map loading using building UUID and map UUID
    .loadMap("<building UUID>", "<map UUID>");

Custom map rendering

To customize map view's displaying attributes simply call setters on object returned by mapSdk.getConfig() before any map loading:

IndoorwayMapSdk mapSdk = IndoorwayMapSdk.getInstance();

mapSdk.getConfig()
    // eg. custom background color for different room types
    .setCustomBackgroundColor("inaccessible", Color.parseColor("#23252C"))
    .setCustomBackgroundColor("elevator", Color.parseColor("#23252C"))
    // custom map outdoor background
    .setMapOutdoorBackgroundColor(Color.parseColor("#282a30"));

There are more properties which can be set. Refer to full documentation for complete attributes list and their default values.

Custom markers

Attention: custom markers can be added only after map loading. You may use OnMapLoadedListener in your map view.

indoorwayMapView
    .setOnMapLoadCompletedListener(new OnMapLoadedListener() {
        @Override
        public void onAction(BuildingAndMapId buildingAndMapId) {
            // LOAD CUSTOM MARKERS HERE
        }
    });

Text

To add custom text marker on a map, call:

indoorwayMapView.getMarkerControl()
    .add(
        new DrawableText(
            "<marker-id>",
            new Coordinates(latitude, longitude),
            "Sample label",
            textHeight // eg. 2f
        )
    );

Identifiers of markers (like <marker-id>) added through add should be unique. They are used to replace on duplicate or removal.

At any time, marker can be removed by:

indoorwayMapView.getMarkerControl().remove("<marker-id>");

Icons

Adding icons to map requires a little more work. Firstly, you need to register a "texture". Texture is a grid of images grouped into a single image (know also as sprite sheet).

Secondly, you can add DrawableIcon using registered texture identifier.

See an example below:

// load images grid as a bitmap
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.sample_texture);

indoorwayMapView.getMarkerControl()
                // register texture with "<texture-id>" identifier
                .registerTexture(new DrawableTexture("<texture-id>", bitmap))
                // first icon
                .add(
                    new DrawableIcon(
                        "<icon1-id>",   // icon identifier
                        "<texture-id>", // texture identifier
                        new Coordinates(latitude1, longitude1),
                        textureCoordsForIcon, 
                        iconXSize1,  // eg. 2f
                        iconYSize1   // eg. 2f
                    )
                )
                // second icon
                .add(
                    new DrawableIcon(
                        "<icon2-id>",    // icon identifier
                        "<texture-id>", // texture identifier
                        new Coordinates(latitude2, longitude2),
                        textureCoordsForIcon, 
                        iconXSize2,  // eg. 2f
                        iconYSize2   // eg. 2f
                    )
                );
                // etc...

Indoor objects selection

If you want to control which objects can be selected and receive callbacks, set listener on IndoorwayMapView:

indoorwayMapView.getSelectionControl().setOnObjectSelectedListener(new OnObjectSelectedListener() {

    @Override
    public boolean canObjectBeSelected(IndoorwayObjectParameters objectParameters) {
        // return true if object with given parameters can be selected
        return !"inaccessible".equals(objectParameters.getType());
    }

    @Override
    public void onObjectSelected(IndoorwayObjectParameters objectParameters) {
        // called on object selection, check objectParameters for details
    }

    @Override
    public void onSelectionCleared() {
        // called when no object is selected
    }

});

Indoor objects can be also selected programmatically by:

  • identifier:

    indoorwayMapView.getSelectionControl().selectObject("<object identifier>");
  • latitude and longitude:

    // object containing given point will be selected
    indoorwayMapView.getSelectionControl().selectObject(latitude, longitude);

If you want to clear selection, call:

indoorwayMapView.getSelectionControl().deselect();

Navigation

There are several ways you can show navigation path in map view:

  • navigate from current user's location to specific destination object (see indoor positioning section for more information and example with currentPosition field):

    indoorwayMapView.getNavigationControl().start(currentPosition, "<object identifier>");
  • navigate from current user's location to specific position (latitude, longitude):

    indoorwayMapView.getNavigationControl().start(currentPosition, destinationLatitude, destinationLongitude);
  • navigate from given latitude, longitude to specific destination object:

    indoorwayMapView.getNavigationControl().start(startLatitude, startLongitude, "<object identifier>");
  • navigate between two positions defined by latitude and longitude:

    indoorwayMapView.getNavigationControl().start(startLatitude, startLongitude, destinationLatitude, destinationLongitude);

To stop navigation call:

indoorwayMapView.getNavigationControl().stop();

Fetching buildings and maps objects

Map SDK can be used to fetch buildings and map objects. Each request is processed on background and it's result is delivered on UI thread.

Firstly, obtain map SDK instance:

IndoorwayMapSdk mapSdk = IndoorwayMapSdk.getInstance();

Getting buildings list

mapSdk.getBuildingsApi()
        .getBuildings()
        .setOnCompletedListener(new GenericListenerArg1<List<IndoorwayBuildingParameters>>() {
            @Override
            public void onAction(List<IndoorwayBuildingParameters> indoorwayBuildingParameters) {
                // handle buildings list
            }
        })
        .setOnFailedListener(new GenericListenerArg1<IndoorwayTask.ProcessingException>() {
            @Override
            public void onAction(IndoorwayTask.ProcessingException e) {
                // handle error, original exception is given on e.getCause()
            }
        })
        .execute();

Getting maps list for building

mapSdk.getBuildingsApi()
        .getMaps("<building UUID>")
        .setOnCompletedListener(new GenericListenerArg1<List<IndoorwayObjectId>>() {
            @Override
            public void onAction(List<IndoorwayObjectId> indoorwayObjectIds) {
                // handle maps list
            }
        })
        .setOnFailedListener(new GenericListenerArg1<IndoorwayTask.ProcessingException>() {
            @Override
            public void onAction(IndoorwayTask.ProcessingException e) {
                // handle error, original exception is given on e.getCause()
            }
        })
        .execute();

Getting map objects

mapSdk.getBuildingsApi()
        .getMapObjects("<building UUID>", "<map UUID>")
        .setOnCompletedListener(new GenericListenerArg1<List<IndoorwayObjectParameters>>() {
            @Override
            public void onAction(List<IndoorwayObjectParameters> indoorwayObjectParameterses) {
                // handle map objects
            }
        })
        .setOnFailedListener(new GenericListenerArg1<IndoorwayTask.ProcessingException>() {
            @Override
            public void onAction(IndoorwayTask.ProcessingException e) {
                // handle error, original exception is given on e.getCause()
            }
        })
        .execute();

Getting list of available images

mapSdk.getLogosApi()
        .getLogotypes()
        .setOnCompletedListener(new GenericListenerArg1<List<IndoorwayIconParameters>>() {
            @Override
            public void onAction(List<IndoorwayIconParameters> indoorwayIconParameterses) {
                // handle available logo images
            }
        })
        .setOnFailedListener(new GenericListenerArg1<IndoorwayTask.ProcessingException>() {
            @Override
            public void onAction(IndoorwayTask.ProcessingException e) {
                // handle error, original exception is given on e.getCause()
            }
        })
        .execute();

Getting list of available poi types

mapSdk.getPoisApi()
        .getPois()
        .setOnCompletedListener(new GenericListenerArg1<List<IndoorwayIconParameters>>() {
            @Override
            public void onAction(List<IndoorwayIconParameters> indoorwayIconParameterses) {
                // handle poi types (eg. for legend)
            }
        })
        .setOnFailedListener(new GenericListenerArg1<IndoorwayTask.ProcessingException>() {
            @Override
            public void onAction(IndoorwayTask.ProcessingException e) {
                // handle error, original exception is given on e.getCause()
            }
        })
        .execute();

Indoor positioning

In order to find user location you need to do few steps:

1. Obtain service connection

Call getPositioningServiceConnection on you activity's onResume function and store it's result as a field:

Attention: remember to stop serviceConnection on onPause method.

/**
 * Connection with positioning service.
 * Used for control: starting, stopping and receiving position updates.
 */
PositioningServiceConnection serviceConnection;

@Override
protected void onResume() {
    super.onResume();
    // ...
    serviceConnection = IndoorwayLocationSdk.getInstance().getPositioningServiceConnection();
}

@Override
protected void onPause() {
    serviceConnection.stop(this);
    // ...
    super.onPause();
}

2. Set position and heading change listeners

serviceConnection
    .setOnPositionChangedListener(new OnPositionChangedListener() {
        @Override
        public void onPositionChanged(IndoorwayPosition position) {
            // store last position as a field
            currentPosition = position;

            // react for position changes...

            // If you are using map view, you can pass position.
            // Second argument indicates if you want to auto reload map on position change
            // for eg. after going to different building level.
            indoorwayMapView.getPositionControl().setPosition(position, true);
        }
    })
    .setOnHeadingChangedListener(new OnHeadingChangedListener() {
        @Override
        public void onHeadingChanged(float angle) {
            // react for heading changes...

            // If you are using map view, you can pass heading.
            indoorwayMapView.getPositionControl().setHeading(angle);
        }
    });

3. Start positioning service

Service connection wil throw an exception during start if some conditions are met:

  • BLE is not supported on device,
  • bluetooth is disabled,
  • one of following permissions is missing:
    • Manifest.permission.ACCESS_FINE_LOCATION,
    • Manifest.permission.BLUETOOTH,
    • Manifest.permission.BLUETOOTH_ADMIN,
  • location provider is disabled.

Application must ask user for specified permissions and settings change if necessary. To properly handle all of these wrap serviceConnection.start(this) in try-catch block. When permission is granted or user changes required setting, call start again.

Tip: there is no need to register broadcast receiver for bluetooth and location state change, SDK will take care for eg. if user clicks on statusbar switch.

try {
    serviceConnection.start(this);
} catch (MissingPermissionException e) {
    // some permission is missing, ask for it and try again
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        String[] permissions = {e.getPermission()};
        // handle asking for permissions
    }
} catch (BLENotSupportedException e) {
    // ble is not supported, indoor positioning won't work
} catch (BluetoothDisabledException e) {
    // ask user to enable bluetooth
} catch (LocationDisabledException e) {
    // ask user to enable location
}

User tracking

If you want to track user position for analytic purposes, call:

Visitor visitor = new Visitor()
        // optional: set more detailed informations if you have one
        .setGroupUuid("<users group identifier>")   // user group
        .setName("John Smith")                      // user name
        .setAge(60)                                 // user age
        .setSex(Visitor.Sex.MALE);                  // user gender

// You can call setupVisitor at any time if you have serviceConnection reference.
// Tracking will be set up automatically as soon as position is found.
serviceConnection.setupVisitor(visitor);

Documentation

Full documentation will be available soon.

If you need more informations contact us at [email protected].

Support

If you want to contact us please send email at [email protected]. Any suggestions or reports of technical issues are welcome!

License

Indoorway Android SDK is available under the custom license. See the LICENSE file for more info.