Setup
To use the Favendo Map in your Android application you have to add our Maven repository
to your project’s build.gradle
file.
allprojects {
repositories {
google()
jcenter()
maven {
url "${favendo_artifactory_url}/backspin-android-sdk"
credentials {
username = "${favendo_artifactory_user}"
password = "${favendo_artifactory_password}"
}
name = "plugins-favendo"
}
}
}
The minimum API level of the SDK is 15. But in order to use any of the BLE related features you need at least API level 18. |
In order to use our repository you need to authenticate with a valid
username and password. It is a good practice to declare those as variables
by adding the following lines to your project’s gradle.properties
file.
favendo_artifactory_user = your_name
favendo_artifactory_password = your_password
favendo_artifactory_url = http://maven-repository.favendo.de/artifactory
Now you have to add the dependency to your module’s build.gradle
file.
Map Implementations
Favendo offers two map implementations from which you can choose.
-
Google Maps
-
Favendo MapView
dependencies {
implementation 'com.favendo.android:backspin-sdk-googlemap:3.20.18'
-- or --
implementation 'com.favendo.android:backspin-sdk-favendomap:3.20.18'
}
No matter if you imported the googlemap
or the favendomap
dependency
it includes the Backspin SDK of the same version.
If your app already uses the Backspin SDK you can simply remove it from your
build.gradle
file.
The Favendo Map uses the Backspin SDK and will only work if the SDK is already initialized and a root venue is loaded. For more information on how to work with the Backspin SDK take a look here. |
If you work with the Google Maps implementation don’t forget to add the Google API Key to your app’s manifest. Look here for further instructions.
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="YOUR_GOOGLE_MAPS_API_KEY"/>
This library uses Vector Drawables. In order to support Vector Drawables
on devices running Android versions prior to version 5.0 (API level 21) you have
to add vectorDrawables.useSupportLibrary = true to your app’s build.gradle file.
Look
here
for further information.
|
Map Fragment
The Favendo Map SDK uses a fragment to show an indoor map of the currently
loaded root venue.
In order to do this you first have to subclass from the according MapFragment
(FavendoMapFragment
or GoogleMapFragment
)
and override its abstract onMapReady()
method.
public class MyMapFragment extends FavendoMapFragment {
@Override
public void onMapReady() {
// interact with the map
// add markers, move camera, etc.
}
}
When the map is initialized and the first level plan loaded, onMapReady()
gets
called. At this point the map is ready to be used. You can add markers, move the
camera, switch levels and do various other supported operations on the map fragment.
This fragment retains its instance and can therefore not be added via
xml - use a FragmentTransaction instead. It also results in onMapReady() being
called only once, regardless of any configuration changes (like rotating the device).
|
The following code snippet shows an example of an activity that adds the map fragment
by a FragmentTransaction
. R.id.fragmentContainer
refers to an empty FrameLayout
that
exists inside the activity’s layout xml.
public class MyMapActivity extends AppCompatActivity {
private static final String TAG_FRAGMENT = "MyMapFragment";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getSupportFragmentManager().findFragmentByTag(TAG_FRAGMENT) == null) {
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fragmentContainer, new MyMapFragment(), TAG_FRAGMENT)
.commit();
}
}
}
Configuration
There are several settings that adjust the behavior of the map to your liking.
Just override the onConfiguration(MapConfig)
method in your map fragment and
modify the MapConfig
according to your needs.
public class MyMapFragment extends FavendoMapFragment {
[...]
@Override
protected void onConfiguration(MapConfig mapConfig) {
super.onConfiguration(mapConfig);
mapConfig.setRenderWorldTiles(true);
mapConfig.setInitialZoomLevel(17);
[...]
}
}
RenderWorldTiles |
Sets whether the map should render any world tiles that don’t belong to the indoor location. |
InitialLevelNumber |
Sets the initial level which is shown when the map is opened. |
InitialZoomLevel |
Sets the initial zoom level of the camera. If there is an active position, this value is ignored. |
InitialZoomLevelOnPosition |
Sets the initial zoom level of the camera. If there is no active position, this value is ignored. |
MinZoomLevel |
Sets the minimum zoom level of the camera. It is not possible to zoom out further than the zoom level set here. |
MaxZoomLevel |
Sets the maximum zoom level of the camera. It is not possible to zoom in further than the zoom level set here. |
RestoreCamera |
Sets whether the camera state should be restored after the map fragment is recreated. |
RestoreCameraTimeout |
Sets the time in milliseconds that has to pass before the saved camera state is no longer relevant. |
PositionPathSnapping |
Sets whether the position should be snapped to the navigation graph before showing it on the map. |
NavigationEnabled |
Sets whether the marker detail will show a navigation action button or not. This button can be used to start a navigation to the selected marker. |
PositionDirectionSource |
Specifies what direction source is used by the position marker.
If set to |
TiltWithSensor |
Sets if the map should tilt automatically with the device orientation. Only performs the tilt if the position follow rotate mode is enabled. |
AllGesturesEnabled |
Sets whether the user can use gestures to control the camera. |
ScrollGesturesEnabled |
Sets whether the user can use gestures to scroll the camera. |
RotateGesturesEnabled |
Sets whether the user can use gestures to rotate the camera. |
ZoomGesturesEnabled |
Sets whether the user can use gestures to zoom the camera. |
TiltGesturesEnabled |
Sets whether the user can use gestures to tilt the camera. |
FollowModeOnNavigationStart |
Sets a value which indicates if the camera follow mode should automatically start after a navigation was started. |
MarkerClusteringEnabled |
This option enables the automatic show and hide of markers which overlap each other on the map. |
MarkerClusteringOverlappingRatio |
With this value you are able to specify how much the markers must overlap before they get hidden. 0 means that the clustering starts with the first overlapping pixel. 1 means that the markers must overlap to 100% before they will be hidden. |
AssetTrackingEnabled |
Sets whether the map should enable and visualize asset
tracking. Make sure to set a valid user token in the |
AssetMarkerExpirationMillis |
Sets an interval which defines when asset markers which were not updated any more get hidden or grayed. |
AssetMarkerExpirationHandling |
The expiration handling defines what should happen which asset markers which were not received anymore for some time. You ca choose between a gray out effect, hiding them or simply leave them on the map forever. |
Image loading
Some of the UI elements like VenueMarker
and MarkerDetailDefault
depend on the loading of images from the web.
There are several libraries available like Universal Image Loader,
Picasso or Glide.
To connect the MapFragment
with the image loader from your app,
you need to override the createImageLoader
method,
implement the MapImageLoader
interface and return that instance.
This interface provides methods for asynchronous and synchronous image requests.
The following code snippet shows the method which needs to be overridden in the
MapFragment
and how the methods should be used:
@Override
protected MapImageLoader createImageLoader() {
return new MapImageLoader() {
@Override
public void loadImageAsync(String url, int width, int height, BitmapLoadedListener loadedListener) {
// use your custom image loader library and start a asynchronous request to get the image
// call onBitmapLoaded on the listener to pass the loaded bitmap back to the map or call onBitmapFailed in case of an error
}
@Override
public Bitmap loadImageSync(String url, int width, int height) {
// use your custom image loader library and start synchronous request
// finally return the loaded bitmap directly
return bitmap;
}
};
}
Indoor Markers
IndoorMarker
is the base class of all markers used by the map.
It holds information about the geo coordinate and the level it belongs to.
Use the following methods of the map fragment to manipulate the markers
on the map:
getMarkers()
|
Returns all previously added |
addMarker(IndoorMarker)
|
Add an |
removeMarker(IndoorMarker)
|
Removes a previously added |
removeMarkers()
|
Removes all previously added |
refreshMarker(IndoorMarker)
|
Forces an |
showMarkers()
|
Sets the visibility of all markers to true and refreshes them. |
hideMarkers()
|
Sets the visibility of all markers to false and refreshes them. |
hideMarkersExcept(IndoorMarker)
|
Sets the visibility of all markers except the specified ones to false and refreshes them. |
showMarkers(VisibilityPredicate)
|
Control the visibility of each marker separately by passing a custom visibility predicate. |
selectMarker(IndoorMarker)
|
Changes the state of the given marker to
|
addVenueMarkers()
|
Adds a |
An IndoorMarker
can have one of the following states:
IndoorMarker.STATE_DEFAULT
|
This is the default state.
The marker can be selected by the user or by calling
|
IndoorMarker.STATE_SELECTED
|
This is the selected state. It usually results in a highlighted visual representation. |
IndoorMarker.STATE_DISABLED
|
This is the disabled state. Markers of this state can not be selected and are usually grayed out. |
You can create your own markers by sub-classing IndoorMarker
and implementing
the loadBitmap(Context, BitmapLoadedListener)
method. Pass a
bitmap to the listener according to the markers state. This method is called
whenever you add or refresh the marker.
There is also an already implemented VenueCategoryMarker
which can be used
for a convenient visual representation of a venue.
It renders the logo of the first VenueCategory
that is associated with
the venue, together with its name below.
Marker Clustering
By clustering your markers, you can put a large number of markers on a map without making the map hard to read. If you have a lot of markers shown at nearly the same location the map starts to show and hide every marker automatically depending on your zoom, rotation and tilt. You can enable this feature with the following line:
@Override
protected void onConfiguration(MapConfig mapConfig) {
super.onConfiguration(mapConfig);
[...]
mapConfig.setMarkerClusteringEnabled(true);
}
You can control the behavior of every marker independently by calling
marker.setClusteringEnabled
with true or false. By default new markers
will be clustered automatically but the position and navigation
path markers will not.
If the user selects a marker on the map the clustering will temporarily be disabled for that specific marker (it is always visible). |
There is also an marker.getClusterCount()
method which returns the
amount of markers which belong to the current clustered one. The default
VenueCircleMarker
shows a small indicator at the right top of the image
if the cluster count is greater than one. If you wrote your own markers you
can use this to adjust the bitmap accordingly.
The marker.getClusterPriority()
method is used by the clustering
to decide which markers should be shown and which markers will be hidden.
Markers with a lower priority will be hidden if a marker with a higher
priority overlaps them. By default every VenueMarker
sets its priority
to the venues area size. All this is achieved by the following Comparator:
public Comparator<IndoorMarker> getClusteringComparator() {
return new Comparator<IndoorMarker>() {
@Override
public int compare(IndoorMarker marker1, IndoorMarker marker2) {
return Double.compare(marker2.getClusterPriority(), marker1.getClusterPriority());
}
};
}
You can always override this method inside the MapFragment
if you want a more flexible solution.
Marker Detail View
Whenever a marker is selected, the SDK will show a UI element that provides further information about the selection.
The method createMarkerDetail()
of the map fragment determines which
marker detail view is used by the map. If you want to customize the existing
implementation or inject a custom one you have to override it.
There are several constructor overloads which can be used to customize
the colors of the MarkerDetailDefault
.
The default implementation looks like this:
protected MarkerDetail createMarkerDetail() {
return new MarkerDetailDefault(this, this);
}
The MarkerDetailDefault
has the ability to show information about VenueMarker
instances, like the VenueCategoryMarker
. If you want to use it to cover other
markers in a similar way you have to subclass MarkerDetailDefault
and override
the apply(Position, IndoorMarker, boolean)
method like this:
@Override
public void apply(Position position, IndoorMarker marker, boolean navigationEnabled) {
if (marker instanceof MyCustomMarker) {
mTitle.setText(...);
mSubTitle.setText(...);
mImageContainer.setVisibility(GONE);
return;
}
super.apply(marker);
}
If you don’t want to use the MarkerDetailDefault
you can create
a custom class which implements the MarkerDetail
interface. The following
methods have to be implemented:
getView()
|
Returns the view that will be added to the map fragment layout. |
apply(Position, IndoorMarker, boolean)
|
This method gets called
when a marker is selected initially and every time the position gets updated.
The third parameter comes from the |
show()
|
This method gets called when the view should be shown. |
hide()
|
This method gets called when the view should be hidden. |
Camera
Use the following methods to control the camera:
moveCamera(CameraPosition)
|
Sets the camera to the given camera position. |
animateCamera(CameraPosition, int)
|
Animates the camera to the given camera position. Specify the animation time in milliseconds. |
getCameraLocation()
|
Returns the current camera position. |
getCameraZoomLevel()
|
Returns the current zoom level of the camera. |
moveCameraToPosition()
|
Sets the camera position to the current user’s indoor location, if there is one. |
animateCameraToPosition()
|
Animates the camera position to the current user’s indoor location. |
moveCameraAndSelectMarker(IndoorMarker)
|
Sets the camera position to the given marker’s indoor location and performs a marker selection. |
animateCameraAndSelectMarker(IndoorMarker)
|
Animates the camera position to the given marker’s indoor location and performs a marker selection. |
Position Follow Mode
The map has a position follow mode that can be enabled by using
enablePositionFollowMode()
or disabled by using disablePositionFollowMode()
.
While the position follow mode is enabled, the camera always animates to center
the latest user position. The map will also perform a level switch, if needed.
Any user interaction like gestures, a level switch or a marker selection will
disable the position follow mode.
The position follow view allows the user to enable the position follow mode. The default implementation is a button that changes its color when the follow mode is enabled or disabled. It will only show itself when a user position is available.
The method createPositionFollow()
of the map fragment determines which
position follow view is used by the map. If you want to customize the existing
position follow or inject a custom one you have to override it.
There is a constructor overload which can be used to customize
the icon and colors of the PositionFollowDefault
.
The default implementation looks like this:
protected PositionFollow createPositionFollow() {
return new PositionFollowDefault(getContext(), this);
}
If you don’t want to use the PositionFollowDefault
you can create
a custom class which implements the PositionFollow
interface. The following
methods have to be implemented:
getView()
|
Returns the view that will be added to the map fragment layout. |
enable()
|
This method gets called when the position follow mode is enabled. Update your view to represent this accordingly. |
disable()
|
This method gets called when the position follow mode is disabled. Update your view to represent this accordingly. It will also be called initially. |
show()
|
This method gets called when the view should be shown. |
hide()
|
This method gets called when the view should be hidden. It will also be called initially. |
Levels
The Favendo Map needs at least one level. You can override the defineLevels()
method to decide which levels are available to the map.
By default the system gets all levels from the current root venue.
Level Switcher
This SDK also comes with a default level switcher UI element.
It tells the user which level is currently displayed and where the user
is currently located. When switching through the different levels all
IndoorMarker
instances will automatically be shown or hidden just as the
level plan overlay.
The method createLevelSwitcher()
of the map fragment determines which
level switcher is used by the map. If you want to customize the existing level
switcher or inject a custom one you have to override it.
There are several constructor overloads which can be used to customize
the size and colors of the LevelSwitcherDefault
.
The default implementation looks like this:
protected LevelSwitcher createLevelSwitcher() {
return new LevelSwitcherDefault(
getContext(),
ThemeColorUtil.fetchPrimaryColor(getContext()),
ContextCompat.getColor(getContext(), R.color.level_switcher_foreground_selected));
}
If you don’t want to use the LevelSwitcherDefault
you can create
a custom class which implements the LevelSwitcher
interface. The following
methods have to be implemented:
getView()
|
Returns the view that will be added to the map fragment layout. |
initialize(List<Level>, LevelSwitchListener)
|
This method gets called when the map initializes. Set up your custom switcher according to the given levels. Use the listener to start a level switch. |
requestLevelSwitch(Level)
|
This method gets called when the map fragment is
about to perform a level switch, either through your call on the listener or a
|
finishedLevelSwitch(Level)
|
This method gets called when the map fragment finished a level switch. Update your UI accordingly. |
addIndicator(LevelIndicator)
|
This method gets called whenever an indicator
should be shown within the |
removeIndicator(LevelIndicator)
|
This method gets called whenever an indicator
should be removed from the |
Level Indicators
Indicators help the user to keep track of several information on different levels,
like their position, the navigation destination or associated assets.
The LevelSwitcherDefault
uses small icons to the left and to the right
of the level number, but if you implemented a custom LevelSwitcher
you are
completely free in their visual representation.
There are two system indicators which are always added by default. One is for the current users position and one for its final navigation destination. If you want to add more indicators you can use the following builder:
LevelIndicator myIndicator = new LevelIndicator.Builder(this)
.setLevel(1)
.setAlignment(IndicatorAlignment.RIGHT)
.setDrawable(R.drawable.custom_icon)
.setZIndex(1f)
.add();
The add()
method returns a new instance of type LevelIndicator
.
Keep this instance if you want to remove it later.
To achieve this you simply have to call the
LevelSwitcher.removeIndicator(LevelIndicator)
method.
Every Indicator also has an z-Index which specifies its visual priority.
This is needed whenever multiple indicators are added on the same level and
the same side.
The system indicators for position and the navigation destination
have a value of 0.5
.
If you choose an higher value the system indicator gets hidden and your icon
will be shown instead of the system one.
A new LevelIndicator
will have a z-Index of 0 and therefore the system icons
will have a higher priority by default.
Position Marker
The Favendo Map SDK automatically registers for position updates from the Backspin SDK and renders a position marker on the map.
If you want to customize what PositionMarker
is used you can simply override
the createPositionMarker(Position)
method of the map fragment.
This method gets called when the first position update is received.
The default implementation looks like this:
protected PositionDirectionMarker createPositionDirectionMarker(Position position, float bearing) {
return new PositionDirectionMarkerDefault(
position,
bearing,
GuiUtil.dipToPixel(getActivity(), 38),
ThemeColorUtil.fetchAccentColor(getContext()));
}
Use PositionMarkerBitmap
if you want to use a position marker with a custom bitmap.
If a position direction is displayed on the map a PositionDirectionMarker
is used
in addition to the PositionMaker
. If you want to customize this simply override the
createPositionDirectionMarker(Position, float)
method of the map fragment.
This method gets called when the first direction is received.
The default implementation looks like this:
protected PositionMarker createPositionMarker(Position position) {
return new PositionMarkerDefault(
position,
GuiUtil.dipToPixel(getActivity(), 38),
ThemeColorUtil.fetchAccentColor(getContext()));
}
Remember to start scanning and positioning of the Backspin SDK in order to receive position updates. |
Navigation
One of the core features of the map is indoor routing / turn by turn navigation.
It uses the navigation API provided by the Backspin SDK and displays the resulting
NavigationRoute
accordingly. The MarkerDetailDefault
view already comes with
an action button that triggers turn by turn navigation to the selected venue -
if NavigationEnabled
of the MapConfig
is true. There is no further implementation
effort needed if this behaviour suits your use case.
In any case, these methods of the map fragment are used to control the indoor navigation:
startNavigation(IndoorLocation)
|
Starts a new turn by turn navigation using
the provided |
startNavigation(Venue)
|
Starts a new turn by turn navigation using
the provided |
startNavigation(IndoorMarker)
|
Starts a new turn by turn navigation using
the provided |
startNavigation(NavigationTask)
|
Starts a new turn by turn navigation using
the provided |
stopNavigation()
|
Stops the currently running navigation. |
isNavigating()
|
Returns true if there is an active navigation. |
IndoorPath
There is also an object to manage polylines on the map. An IndoorPath
has many indoor locations which are connected via a simple line. This line can
be customized in color and width.
IndoorPath myIndoorPath = new IndoorPath(locations, Color.BLACK);
Every path segment will automatically be shown or hidden according
to the current level which is selected. You can also attach IndoorMarkers
which are added and removed together with the whole IndoorPath
object.
To do so use the addPath(IndoorPath)
method of your map fragment.
You can also remove and refresh previously added paths via the according methods.
There are several methods which are involved when showing the path
for a navigation route.
By default the system creates one IndoorPath
for an navigation which is
started via the startNavigation(NavigationTask)
method.
A new path will be created and added inside the onRouteCalculated
callback.
To customize the navigation path you can simply override
the createNavigationPath()
method. The default implementation looks like this:
protected IndoorPath createNavigationPath() {
return new IndoorPath(
GuiUtil.dipToPixel(getContext(), 4),
ThemeColorUtil.fetchAccentColor(getContext()));
}
If you want to manually add an IndoorPath
for any NavigationRoute
you can
simply call the addRoute(NavigationRoute)
which then returns
the created an added path.
I you changed locations or other fields of the IndoorPath you
have to call refreshPaths() afterwards.
|
Navigation Info
The navigation info view provides the user information about the currently active navigation. The default implementation displays the name of the navigation target together with distance and time to destination. The view also includes a button that is used to stop the current navigation.
The method createNavigationInfo()
of the map fragment determines which
navigation info view is used by the map. If you want to customize the existing
navigation info or inject a custom one you have to override it.
There is a constructor overload which can be used to customize
the foreground and background color of the NavigationInfoDefault
.
The default implementation looks like this:
protected NavigationInfo createNavigationInfo() {
return new NavigationInfoDefault(getContext(), this);
}
If you don’t want to use the NavigationInfoDefault
you can create
a custom class which implements the NavigationInfo
interface. The following
methods have to be implemented:
getView()
|
Returns the view that will be added to the map fragment layout. |
apply(Position, String, NavigationRoute)
|
This method gets called when a new
route has been calculated. Update the view with the information about the
latest |
show()
|
This method gets called when the view should be shown. |
hide()
|
This method gets called when the view should be hidden. |
Turn By Turn
The turn by turn view provides the user information about the next directions of the currently active navigation.
The method createTurnByTurn()
of the map fragment determines which
turn by turn view is used by the map. If you want to customize the existing
turn by turn or inject a custom one you have to override it and
return the corresponding instance. By default the NoTurnByTurn
instance
is created which means that no turn by turn directions are shown.
If you want to show the turn by turn instructions
you can use the default implementation which is called TurnByTurnDefault
.
This will display an arrow icon together with a short text to describe
the next two directions.
There are several constructor overloads which can be used to customize
the colors. The default implementation looks like this:
protected TurnByTurn createTurnByTurn() {
return new TurnByTurnDefault(getContext());
}
The following strings are used by the turn by turn UI but you can provide your own translations if needed.
<string name="direction_straight">Straight ahead</string>
<string name="direction_bear_left">Slightly left</string>
<string name="direction_left">Turn left</string>
<string name="direction_bear_right">Slightly right</string>
<string name="direction_right">Turn right</string>
<string name="direction_level_up">Change to %s</string>
<string name="direction_level_down">Change to %s</string>
<string name="direction_destination_reached">Destination reached</stri
If you don’t want to use the TurnByTurnDefault
you can create
a custom class which implements the TurnByTurn
interface. The following
methods have to be implemented:
getView()
|
Returns the view that will be added to the map fragment layout. |
apply(NavigationDirection, NavigationDirection)
|
This method gets called when a new route has been calculated. Update the view with the information about the next two navigation directions. |
show()
|
This method gets called when the view should be shown. |
hide()
|
This method gets called when the view should be hidden. |
Asset Tracking
When enabled in the MapConfig
the map takes care of displaying assets and receiving
live position updates.
Everything that is asset tracking related is controlled by the
AssetMapComponent
. The default implementation inside the map fragment looks like this:
protected AssetMapComponent createAssetComponent() {
return new AssetMapComponent(this);
}
Make sure to set a valid user token in the ConnectionConfig
or with the BackspinSdk.Assets.setUserToken method.
|
If you want to customize the asset tracking inside the map you have to extend the
AssetMapComponent
class and return it by overriding the createAssetComponent()
method in the map fragment:
@Override
protected AssetMapComponent createAssetComponent() {
return new CustomAssetMapComponent(this);
}
A custom map AssetMapComponent
that changes the visualization of the asset markers
could look like this:
public class CustomAssetMapComponent extends AssetMapComponent {
public CustomAssetMapComponent(BaseMapFragment mapFragment) {
super(mapFragment);
}
@Override
public AssetMarker createAssetMarker(@NotNull Asset asset) {
return new CustomAssetMarker();
}
}