"How to" for the Android SDK

Prerequisites

  • Android 4.0 - API level 14
  • Java 1.6
  • OpenGL ES 2.0 support

Integrating the SDK

The SDK is packaged as a zip archive containing:

  • static framework - .jar file
  • native libraries - .so files
  • map resources and audio advisor resources- SKMaps.zip file
  • demo project

Adding the SDK to the Project

To integrate the SDK, follow these steps:

  • In your project folder, add:

    • /jniLibs/arm64-v8a/libngnative.so
    • /jniLibs/armeabi/libngnative.so
    • /jniLibs/armeabi-v7a/libngnative.so
    • /jniLibs/x86/libngnative.so

    If you are targeting just one of these architectures (arm64-v8a, armeabi, armeabi-v7a , x86) you can remove the other folders to achieve a smaller size for your application.

  • Add SKMaps.jar in your libs folder and to build paths.
  • Copy SKMaps.zip file in your project to assets folder.
  • Add these setting to your AndroidManifest.xml:

    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> 
  • Add SKMaps-docs.jar to the project:

Open .idea/libraries/SKMaps.xml add the SKMaps-docs.jar reference for the library. For example, for current demo project this will result into:

<JAVADOC>
    <root url="jar://$PROJECT_DIR$/sdktools/libs/SKMaps-docs.jar!/" />
</JAVADOC>

Press Ctrl/Cmd+alt+y to sync the project.

ATTENTION!
Don't select "Sync Project with Gradle files", because the changes will be deleted.

You can activate method information in Android Studio by going to: Preferences > Editor > General> Other> Show doc on mouse move.

IMPORTANT
Keep in mind, that when you sync the project, the setting gets overwritten. So this may be considered a temporary solution until Android Studio is capable of catching javadoc *.jars *

Adding the SDK to the Project using Gradle

Add the following code fragment to your build.gradle script:

apply plugin: 'java'

repositories {
    maven {
        url "http://developer.skobbler.com/maven/"
    }
}

configurations {
    skobblersdk
}
dependencies {
    skobblersdk "com.skobbler.ngx:SKMaps:3.2.0"
    compile files('libs/SKMaps.jar')
}

def assetsPath = "$projectDir/src/main/assets"
def libsPath = "$projectDir/libs"
def jniLibsPath = "$projectDir/src/main/jniLibs"

task installSKMaps << {
    copy {
        from configurations.skobblersdk
        into "$buildDir/skobblersdk-down"
        rename { String fileName -> 'skobblersdkres.zip' }
    }
    copy {
        from zipTree("$buildDir/skobblersdk-down/skobblersdkres.zip")
        into "$buildDir/skobblersdk-down"
    }
    delete("$jniLibsPath",
            "$assetsPath/SKMaps.zip",
            "$libsPath/SKMaps.jar")
    copy {
        from "${buildDir}/skobblersdk-down/jniLibs"
        into "$jniLibsPath"
    }
    copy {
        from "${buildDir}/skobblersdk-down/"
        into "$assetsPath"
    }
    copy {
        from "${buildDir}/skobblersdk-down/SKMaps.jar"
        into "$libsPath"
    }
    delete("$buildDir/skobblersdk-down")
    delete(configurations.skobblersdk)
}

Run the "installSKMaps" task to download and install all the required resources.

NOTE
Subsequent task runs will overwrite the existing resources (SKMaps.jar, SKMaps.zip, native libraries).

Getting the Eclipse Project Up and Running

  1. First download Android SDK + demo project (Eclipse) and Android SDKTools (Eclipse) projects from the download page and extract them;
  2. Make sure that both projects are in your workspace. If one of the projects is missing, import it in your workspace;
  3. In the Package Explorer, right-click the AndroidOpenSourceDemo project and select Properties;
  4. In the Properties window, select the "Android" properties group at left and locate the Library properties at right;
  5. Click Add to open the Project Selection dialog;
  6. Select SDKTools and click OK;
  7. When the dialog closes, click Apply in the Properties window;
  8. Click OK to close the Properties window.

As soon as the Properties dialog is closed, Eclipse will rebuild the project, including the contents of the library project.

Setting the API Key and Initializing the Library

Setting the API Key

The mobile SDK API key can be visualised/requested here.

There are two ways of setting the API KEY:

Method 1: Set the api key in your application's AndroidManifest.xml file, by adding the following element as a child of the <application> element:

<meta-data
    android:name="com.skobbler.ngx.API_KEY"
    android:value="PUT_API_KEY"/>

Method 2: Call the setDeveloperKey method from SKMaps before the initializeSKMaps method.


Initializing the library

The SKMaps Framework must be initialized in SKMaps class using the initializeSKMaps method. There are two possibilities of initializing the map:

1. The custom initialization provides the flexibility to set the caches path, startup connectivity mode, etc. (see com.skobbler.ngx.SKMapsInitSettings):

SKMaps.getInstance().initializeSKMaps(Application context, SKMapsInitializationListener mapsInitListener, final SKMapsInitSettings mapInitSettings)

The settings must contain valid paths for maps, current map style folder and common resources.

SKMapsInitializationListener mapsInitListener set the listener in order to be notified when the initialization process ended.

2. The default initialization procedure doesn't offer the possibility to use the custom settings. The phone storage that is suitable for installing the library, the path where are the map resources, the current map style, etc. will receive default values:

SKMaps.getInstance().initializeSKMaps(Application context, SKMapsInitializationListener mapsInitListener)

IMPORTANT
Regardless of the method used for initializing the library note that this method has to be called only once per application.

In case the client Android application doesn't call the SKMaps framework initialization method, the application will crash, throwing the following exception message:

"SKMaps was not initialized. In order to initialize it, please call:
SKMaps.getInstance().initializeSKMaps(context, mapInitSettings)".
(com.skobbler.ngx.SKMapsPathsNotInitializedException)

If the developer key is null or it’s an empty string then the application will crash , throwing the following exception message :

"Developer key was not set."
(com.skobbler.ngx.SKDeveloperKeyException)

Working with the Map

Displaying the Map

There are two ways of displaying a map:

1) Using SKMapFragment

The map fragment can be added using the xml or programatically:

1.1. Initialize the SKMaps framework with the steps described above and add the fragment to the XML file.

Declare the fragment inside the activity's layout file:

<fragment
    class="com.skobbler.ngx.map.SKMapFragment"
    android:id="@+id/mapfragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Initialize the instance of the SKMapFragment class, this will associate the Map with the SKMapFragment declared in the xml file. Only after it is initialized, the SKMapFragment UI element will contain a Map object.

SKMapFragment mapFragment = (SKMapFragment)getFragmentManager().findFragmentById( R.id.mapfragment);
mapFragment.initialise();

1.2. Initialize the SKMaps framework with the steps described above and programmatically add the fragment to an existing ViewGroup.

SKMapFragment mapFragment = new SKMapFragment();
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.fragmentContainer, mapFragment).commit();

2) Using a SKMapViewHolder

The user can access the map view instance by calling getMapSurfaceView() from SKMapViewHolder, only after onSurfaceCreated is called.

  • Define com.skobbler.ngx.map.SKMapViewHolder in the xml:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
        <com.skobbler.ngx.map.SKMapViewHolder
            android:id="@+id/map_surface_holder"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent" />
    </RelativeLayout>
  • Get the map view:

    SKMapViewHolder mapViewGroup = (SKMapViewHolder) findViewById(R.id.map_surface_holder);
    SKMapSurfaceView mapView = mapViewGroup.getMapSurfaceView();
    

    The activity/fragment that uses the SKMapViewHolder has to override its onPause and onResume methods and call the corresponding methods of SKMapViewHolder.

    Other operations for the map can be performed (e.g. centering the map on a specific position), only after onSurfaceCreated is called. SKMapSurfaceView is null until then.

    public class MapActivity extends Activity implements SKMapSurfaceCreatedListener {
    
    /**
     * Surface view for displaying the map
     */
     private SKMapSurfaceView mapView;
    
    /**
     * the view that holds the map view
     */
     private SKMapViewHolder mapHolder;
    
     @Override
     protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_map);
            SKMapViewHolder mapHolder = (SKMapViewHolder)
                    findViewById(R.id.view_group_map);
            mapHolder.setMapSurfaceListener(this);
     }
    
     @Override
     protected void onPause() {
            super.onPause();
            mapHolder.onPause();
     }
    
     @Override
     protected void onResume() {
            super.onResume();
            mapHolder.onResume();
     }
    
     @Override
     public void onSurfaceCreated(SKMapViewHolder skMapViewHolder) {
            mapView = mapHolder.getMapSurfaceView();
     }
    
    }
    

Changing the Map Settings

The configuration of any map view instance can be set through its settings property. The following code disables the rotation and the panning inertia of the map. For further details about the map view’s configurations see the com.skobbler.ngx.map.SKMapSettings class.

mapView.getMapSettings().setMapPanningEnabled(false);
mapView.getMapSettings().setInertiaPanningEnabled(false);

Configuring Visual Elements

The SKMapSurfaceView has three additional visual elements: the compass, the scale view and the callout view.

  • The compass is used for indicating the bearing of the map. By default it is hidden. When the map is rotated and SKMapSurfaceListener onRotateMap method is called the compass can be shown:
    mapView.getMapSettings().setCompassPosition(new SKScreenPoint(10, 50));  // right top corner
    mapView.getMapSettings().setCompassShown(true);
    SKScreenPoint(0,0) set for compass is actually the top right corner of the screen so when setting the compass position this has to be kept in mind.
  • The SKMapScaleView illustrates the correspondence between distances on the map and real distances. The scale view can be shown or hidden and clients may customise it by modifying its properties as in the code below:
    // Display the scale view on the map
    mapHolder.setScaleViewEnabled(true);
    // Set the scale view’s position
    mapHolder.setScaleViewPosition(0, 80, RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.ALIGN_PARENT_BOTTOM);
    // Get the map scale object from the map holder object that contains it
    SKMapScaleView scaleView = mapHolder.getScaleView();
    // Set one of the color used to render the scale
    scaleView.setLighterColor(Color.argb(255, 255, 200, 200));
    // Disable fade out animation on the scale view
    scaleView.setFadeOutEnabled(false);
    // Set the distance units displayed to miles and feet
    scaleView.setDistanceUnit(SKDistanceUnitType.DISTANCE_UNIT_MILES_FEET);
  • The callout view element can be used for displaying in a popup some relevant information about map places such as POIs. By default the SKCalloutView class provides a default UI that includes text fields on two rows (title and subtitle) and two images to the left and to the right of the text. Clients may provide their text and image content for these elements as well as set listeners for events on these items:
    // Get the callout view object from the map holder
    SKCalloutView mapPopup = mapHolder.getCalloutView();
    // Chained calls to set title and description and make the callout view visible
    mapPopup.setTitle("My title").setDescription("My description").setVisibility(View.VISIBLE);
    // Set the callout view’s background color
    mapPopup.setViewColor(Color.argb(255, 200, 200, 255));
    // Set the left image from a drawable
    mapPopup.setLeftImage(getResources().getDrawable(R.drawable.icon_map_popup_navigate));
    // Set a listener for click events on the left image
    mapPopup.setOnLeftImageClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
        Toast.makeText(MapActivity.this, "Left image clicked", Toast.LENGTH_SHORT).show();
        }
    });
    

    The client can show the popup at a certain location by calling:

    // This should be calculated based on the annotation height
    mapPopup.setVerticalOffset(110);
    mapPopup.showAtLocation(new SKCoordinate(23.56, 46.77), true);

    When panning, zooming or rotating the map, the view will move around accordingly.
    The API also allows setting a custom UI from a View object to the callout view as in the example below. Note that in this case the methods that concern the default UI of the callout view will not work anymore.

    // Inflate a view defined in an XML
    View view = inflater.inflate(R.layout.layout_popup, null);
    // Set that view to the map popup
    mapPopup.setCustomView(view);

Map Internationalization Settings

The map internationalization option can be updated in SKMapSettings class.

The map internationalization feature is used to customize the labeling of the map. First the primary option will be applied for displaying the name of the map elements (country, city) on the first row.

Using the example below the labels of the map will appear in local format. If some of the map elements don't have local format then the secondary option will be applied for them, they will appear in English. If these map elements don't exist in English they will appear in German. By specifying that both options should be displayed, setShowBothLabels(true), map elements having both representation will appear on the map in two rows.

The map view supports several languages English, German, French, Italian, Spanish, Russian, Turkish and local.

private void setMapInternationalization() {
            final SKMapInternationalizationSettings mapInternationalizationSettings = new SKMapInternationalizationSettings();
            mapInternationalizationSettings.setPrimaryLanguage(SKLanguage.LANGUAGE_EN);
            mapInternationalizationSettings.setFallbackLanguage(SKLanguage.LANGUAGE_DE);
            mapInternationalizationSettings.setFirstLabelOption(SKMapInternationalizationOption.MAP_INTERNATIONALIZATION_OPTION_INTL);
            mapInternationalizationSettings.setSecondLabelOption(SKMapInternationalizationOption.MAP_INTERNATIONALIZATION_OPTION_LOCAL);
            mapInternationalizationSettings.setShowBothLabels(true);
            mapView.getMapSettings().setMapInternationalizationSettings(mapInternationalizationSettings);
  }

Changing the Map Style

The map style is represented by a set of predefined colours and display rules. The SKMaps.zip contains four basic styles that can be applied to the map. The style can be updated in SKMapSettings class.

final SKMapViewStyle style = new
SKMapViewStyle(Environment.getExternalStorageDirectory().getAbsolutePath() + "/daystyle/", "daystyle.json");
mapView.getMapSettings().setMapStyle(style);

Adding Annotations and Overlays

Annotations are used for marking places specified by the users. The SKAnnotation class provides the interface for configuring any annotation before it will be added to the map view. The SKAnnotation supports predefined types, PNG images or native views. Use the addAnnotation() method of a SKMapSurfaceView object to add an annotation on the map. Overlays can be used to mark specific areas from the map. The overlay types that can be added are polylines, polygons or circles. For customising these elements, use the properties of the SKCircle, SKPolygon and SKPolyline classes.

// Center map on a position
mapView.animateToLocation(new SKCoordinate(37.7765, -122.4200), 1000);

// Add annotation using texture ID - from the json files.
// Get the annotation object
SKAnnotation annotationWithTextureId = new SKAnnotation(10);
// Set annotation location
annotationWithTextureId.setLocation(new SKCoordinate(37.7765, -122.4200));
// Set minimum zoom level at which the annotation should be visible
annotationWithTextureId.setMininumZoomLevel(5);
// Set the annotation's type
annotationWithTextureId.setAnnotationType(SKAnnotation.SK_ANNOTATION_TYPE_BLUE);
// Render annotation on map
mapView.addAnnotation(annotationWithTextureId, SKAnimationSettings.ANIMATION_NONE);

// Add an annotation with a view
SKAnnotation annotationFromView = new SKAnnotation(11);
annotationFromView.setLocation(new SKCoordinate(37.761349, -122.423573));
annotationFromView.setMininumZoomLevel(5);
SKAnnotationView annotationView = new SKAnnotationView();
customView =
    (RelativeLayout) ((LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(
            R.layout.layout_custom_view, null, false);
//  If width and height of the view  are not power of 2 the actual size of the image will be the next power of 2 of max(width,height).
annotationView.setView(findViewById(R.id.customView));
annotationFromView.setAnnotationView(annotationView);
mapView.addAnnotation(annotationFromView, SKAnimationSettings.ANIMATION_NONE);

// Get a circle mask shape object
SKCircle circleMask = new SKCircle();
// set the shape's mask scale
circleMask.setMaskedObjectScale(1.3f);
// Set the colors
circleMask.setColor(new float[]{ 1f, 1f, 0.5f, 0.67f });
circleMask.setOutlineColor(new float[]{ 0f, 0f, 0f, 1f });
circleMask.setOutlineSize(3);
// Set circle center and radius
circleMask.setCircleCenter(new SKCoordinate(37.7665,-122.4200));
circleMask.setRadius(300);
// Set outline properties
circleMask.setOutlineDottedPixelsSkip(6);
circleMask.setOutlineDottedPixelsSolid(10);
// Set the number of points for rendering the circle
circleMask.setNumberOfPoints(150);
circleMask.setIdentifier(11);
// Render the circle mask
mapView.addCircle(circleMask);

// Get a polygon shape object
SKPolygon polygon = new SKPolygon();
// Set the polygon's nodes
List<SKCoordinate> nodes = new ArrayList<SKCoordinate>();
nodes.add(new SKCoordinate(37.7765, -122.4342));
nodes.add(new SKCoordinate(37.7765, -122.4141));
nodes.add(new SKCoordinate(37.7620, -122.4342));
polygon.setNodes(nodes);
// Set the outline size
polygon.setOutlineSize(3);
// Set colors used to render the polygon
polygon.setOutlineColor(new float[]{ 1f, 0f, 0f, 1f});
polygon.setColor(new float[]{ 1f, 0f, 0f, 0.2f});
polygon.setIdentifier(10);
// Render the polygon on the map
mapView.addPolygon(polygon);

// Get a polyline object
SKPolyline polyline = new SKPolyline();
// set the nodes on the polyline
nodes = new ArrayList<SKCoordinate>();
nodes.add(new SKCoordinate(37.7898, -122.4342));
nodes.add(new SKCoordinate(37.7898, -122.4141));
nodes.add(new SKCoordinate(37.7753, -122.4342));
polyline.setNodes(nodes);
// Set polyline color
polyline.setColor(new float[]{ 0f, 0f, 1f, 1f});
// Set properties for the outline
polyline.setOutlineColor(new float[]{ 0f, 0f, 1f, 1f});
polyline.setOutlineSize(4);
polyline.setOutlineDottedPixelsSolid(3);
polyline.setOutlineDottedPixelsSkip(3);
polyline.setIdentifier(12);
mapView.addPolyline(polyline);

Observing Map Events

The SKMapSurfaceListener interface is responsible for sending map related events to the client application. For instance: visible region changing, gesture events, etc.

Check the documentation of the following listeners (SKCompassListener, SKInternetConnectionListener, SKCurrentPositionSelectedListener, SKAnnotationListener, SKGLInitializationListener, SKDebugListener, build_publicSDK.properties, SKMapBoundingBoxListener, SKMapActionListener, SKZoomListener, SKViewModeChangedListener, SKPOIListener, SKPanListener, SKObjectSelectedListener, SKMapTrafficListener, SKMapTapListener, SKMapSurfaceCreatedListener, SKMapScreenshotListener, SKMapRegionChangedListener, SKMapInternationalisationListener) for further information. All notifications done through the specified listeners are called on the UI Thread.

public class MapActivity extends Activity implements SKMapSurfaceCreatedListener, SKRouteListener, SKNavigationListener,
        SKRealReachListener, SKPOITrackerListener, SKCurrentPositionListener, SensorEventListener,
        SKMapVersioningListener, SKToolsNavigationListener, SKMapsInitializationListener, SKAnnotationListener,
        SKMapScreenshotListener, SKMapTapListener, SKCompassListener, SKGLInitializationListener, SKPanListener,
        SKZoomListener, SKMapInternationalisationListener, SKMapRegionChangedListener, SKDebugListener, SKCurrentPositionSelectedListener, SKObjectSelectedListener, SKPOIListener,
        SKViewModeChangedListener {
/**
 * Surface view for displaying the map
 */
 private SKMapSurfaceView mapView;

/**
 * The view that holds the map view
 */
 private SKMapViewHolder mapHolder;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_map);
        mapHolder = (SKMapViewHolder) findViewById(R.id.view_group_map);
        setMapActionListeners();
 }

 @Override
 protected void onPause() {
        super.onPause();
        mapHolder.onPause();
 }

 @Override
 protected void onResume() {
        super.onResume();
        mapHolder.onResume();
 }
 
 private void setMapActionListeners() {
    mapHolder.setMapSurfaceCreatedListener(this);
    mapHolder.setMapAnnotationListener(this);
    mapHolder.setMapScreenshotListener(this);
    mapHolder.setMapTapListener(this);
    mapHolder.setCompassListener(this);
    mapHolder.setGLInitializationListener(this);
    mapHolder.setPanListener(this);
    mapHolder.setZoomListener(this);
    mapHolder.setInternationalisationListener(this);
    mapHolder.setMapRegionChangedListener(this);
    mapHolder.setDebugListener(this);
    mapHolder.setCurrentPositionSelectedListener(this);
    mapHolder.setObjectSelectedListener(this);
    mapHolder.setPoiListener(this);
    mapHolder.setViewModeChangedListener(this);
 }

 @Override
 public void onActionPan() { }

 @Override
 public void onActionZoom() { }

 @Override
 public void onSurfaceCreated(SKMapViewHolder skMapViewHolder) {
        mapView = mapHolder.getMapSurfaceView();
 }

 @Override
 public void onMapRegionChanged(SKCoordinateRegion skCoordinateRegion) { }

 @Override
 public void onMapRegionChangeStarted(SKCoordinateRegion skCoordinateRegion) { }

 @Override
 public void onMapRegionChangeEnded(SKCoordinateRegion skCoordinateRegion) { }

 @Override
 public void onDoubleTap(SKScreenPoint skScreenPoint) { }

 @Override
 public void onSingleTap(SKScreenPoint skScreenPoint) { }

 @Override
 public void onRotateMap() { }

 @Override
 public void onLongPress(SKScreenPoint skScreenPoint) { }

 @Override
 public void onInternetConnectionNeeded() { }

 @Override
 public void onMapActionDown(SKScreenPoint skScreenPoint) { }

 @Override
 public void onMapActionUp(SKScreenPoint skScreenPoint) { }

 @Override
 public void onPOIClusterSelected(SKPOICluster skpoiCluster) { }

 @Override
 public void onMapPOISelected(SKMapPOI skMapPOI) { }

 @Override
 public void onAnnotationSelected(SKAnnotation skAnnotation) { }

 @Override
 public void onCustomPOISelected(SKMapCustomPOI skMapCustomPOI) { }

 @Override
 public void onCompassSelected() { }

 @Override
 public void onCurrentPositionSelected() { }

 @Override
 public void onObjectSelected(int i) { }

 @Override
 public void onInternationalisationCalled(int i) { }

 @Override
 public void onBoundingBoxImageRendered(int i) { }

 @Override
 public void onGLInitializationError(String errorMessage) { }
 
 @Override
 public void onViewModeChanged() { }
}

Multiple Map Support

The SDK doesn't support multiple map views on the same screen, however, the case with activities each having one map works. In order for the second option to work ( multiple map instances in different activities ) set SKMapSurfaceView.preserveGLContext = false; before initializing the first map.
To test this functionality in the demo set in AndroidManifest provideMultipleMapSupport value to true. Then the option "Interaction between maps" will be available in the menu options.
The state of the first map will be cached and will be restored when again visible.

What will be cached:

  • map overlays (annotations, shapes);
  • the map settings as recorded in the SKMapSettings class (e.g. isCityPoisShown(), isMapPanningEnabled(), etc.).

What will not be cached:

  • Routes;
  • Real Reach;
  • Heat map;
  • Navigation status (once you enter navigation on a particular map instance, this will be pushed on all the map instances).

For caching the routes you can use the route caching mechanism described in the Routing chapter.

Using RealReach™

The SKMapSurfaceView class contains APIs for calculating and rendering the RealReach component on the map. RealReach is used to provide information to the users about how far they can get from a specified position, considering the transport mode (pedestrian, bike, car) and the measurement unit (time, distance, energy).

The realReach component contains two APIs:

  • displayRealReachWithSettings: calculates and renders the realReach component based on the given configuration provided by the SKRealReachSettings object.
  • clearRealReachDisplay: removes the realReach layer from the map.


mapView.animateToLocation(new SKCoordinate(13.387165, 52.516929),0);
//Display the real reach layer

SKRealReachSettings realReachSettings = new SKRealReachSettings();
realReachSettings.setLocation(new SKCoordinate(13.4127, 52.5233));
realReachSettings.setMeasurementUnit(SKRealReachSettings.SKRealReachMeasurementUnit.SECOND);
realReachSettings.setRange(15 * 60);
realReachSettings.setTransportMode(SKRealReachSettings.SKRealReachVehicleType.BICYCLE);
mapView.displayRealReachWithSettings(realReachSettings);

Using the MapCreator Settings File

The map creator feature is used for configuring the map’s basic settings (internationalisation, showing bicycle lanes, one way arrows, etc.) through a configuration JSON file. After the JSON file is generated and saved, it should be placed in the application’s assets folder. The following code snippet shows how to configure the map using a configuration file placed in the application’s assets:

mapView.applySettingsFromFile(filePath)

Map Update

The Map Update feature allows the client to update the version of the maps to a newer version available on the server. The SKVersioningManager class provides information related to the current map version, the available versions and provides functionality for updating the library to a newer map version.

The SKMapUpdateListener provides some methods which will notify the client if a new version is available on the server when initializing the library. The user can also ask for updates by calling checkNewVersion(int timeoutInSeconds) from SKVersioningManager class.

Map Packages

Multiple Storages for the Map

On Android there are 3 types of storages :

  • internal memory;
  • internal SD card (application storage);
  • external SD card.

As the user might not have enough space to install all the needed maps on a single storage, the framework has the possibility to add multiple storages for the maps. For example, the user might choose to install two maps on internal memory, two maps on external sd card and so on. When the map is initialised the user has to provide a path to the maps. The NGX library has a list of possible storages.

At this point the library automatically adds this path to its storage list. Storage id for this first storage will be 0. If the user wants to add new maps on another storage he has to add the storage to the library: see addStoragePath(String).

The client has to ensure that this method is called just once for the same storage. After the storage is added the user has to change the storage path (so that the library knows that future maps will be installed on this storage): see changeStoragePath(int) - storageID is the id returned by addStoragePath.

There is also the possibility to remove a storage: see removeStoragePath(int) - storageID is the id returned by addStoragePath.

The client has to keep a list of the added storages in order to add them again the next time the map is initialized.

Considerations when using Offline Maps

The SDK requires some meta files in order to be able to render the map.

SKMetaDataListener provides a callback ( public void onMetaDataDownloadFinished(int versionNumber) ) that tells you when these meta files are ready. Only after receiving this callback the map can be successfully rendered.

Structure of an Offline Map Package

Information about offline packages can be retrieved using the SKPackageManager. The getMapsXMLPathForVersion can be used to retrieve an XML containing information about all available offline packages. After deciding what package to download, information about a package can be retrieved using getPackageURLInfoForCountryWithCode.

A map package consists of three components:

  • .skm file - the actual map file, with tile information.
  • name browser files: .ngi, .ngi.dat (zipped in one file) - an archive containing two files used for POI and address searching in the package. This has to be unarchived before installing the package.
  • .txg file - file containing textures for high zoom levels.

After all components are downloaded, the addOfflinePackage(final String dirPath, final String packageName) method will install the package into the library’s file system.

public void downloadMapsXMLForVersion(int version) {
      String url = SKPackageManager.getInstance().getMapsXMLPathForVersion(version);
      // … Download the xml from the URL … //
      // … When download is finished call [downloadPackages] … //
}

public void downloadPackages() {
      SKPackageURLInfo urlInfo =    SKPackageManager.getInstance().getURLInfoForPackageWithCode("DE");
      // … Download the files from the URLs … //
      // … The package contains an .skm, a .txg and .zip file (which has to be unarchived before adding the package) … //
      // … When download is finished call addOfflinePackage … //
      SKPackageManager.getInstance().addOfflinePackage(packagePath, “DE”);
}

Managing Downloaded Maps Package

The SKPackageManager also provides a way to retrieve information about the offline packages already installed. Based on that information, any package can be uninstalled. The uninstallation of a package implies also removing it physically from the device storage.

SKPackage[] packages = SKPackageManager.getInstance().getInstalledPackages();
SKPackage firstPackage = packages[0];
// Uninstall and delete the package from the disk
SKPackageManager.getInstance().deleteOfflinePackage(firstPackage.getName());

Managing Prebundled Map Packages

Any prebundled map package must be store in the SKMaps.zip file PreinstalledMaps\v1 folder.

The v1 folder should contain the version file and the map version folder (e.g. 20140320). That folder should contain the package folder and optionally the meta folder in case the SDK is used in offline mode only. In package folder are stored .skm files (e.g PreinstalledMaps\v1\20140320\package\RO.skm).



Meta Files

The SDK requires some meta files to be present in order to be able to render the map (in offline).

You may choose to distribute the meta files prebundled in your app or you can download them, on-demand, from our servers.

In case the SDK is used in offline mode only, the meta files must be pre-bundled as well. The meta files are used for rendering shapes, roads, POIs on the map (e.g PreinstalledMaps\v1\20140320\meta\attributes). The meta files are specific to a map version. Meta files are located under assets folder : SKMaps.zip/PreinstallesMaps/v1.



Downloading the Meta Files On Demand

When initialising the SDK, the meta files will also be downloaded (if needed). The SKMetadataListener provides a callback that tells you when these meta files are ready. Only after receiving this callback you may safely switch to offline mode (as now the maps can be successfully rendered).

@Override
 public void onMetaDataDownloadFinished(int versionNumber) {
 }


Preparing a Map Package for Prebundling

The Demo project provides the support to download the map packages for a specific country or city. Once the map package is downloaded you can find it on the phone, in the "MapResourcesPath/Maps/downloads/" folder.

There is a zip file in the assets folder of the demo : SKMaps.zip. This zip file contain the meta files needed in order to have prebundled map. Depending on the map detail that needs to be used (FULL or LIGHT) the content of the zip files needs to be put in PreinstalledMaps/v1 folder.

For example for 20161212 map version copy the .skm file (.ngi and .ngi.dat files) from: MapResourcespath/Maps/v1/20161212/package to the PreinstalledMaps\v1\20161212\package\ in the SKMaps.zip.

Alternative option

Get the download package urls for: map .skm file, name browser zip containing .ngi and .ngi.dat files.

SKPackageURLInfo info =   SKPackageManager.getInstance().getURLInfoForPackageWithCode("DE");
String mapURL = info.getMapURL();
String nameBrowserFilesURL = info.getNameBrowserFilesURL();

From the above location grab the .skm file (optionally the .ngi and .ngi.dat files) and move them into the PreinstalledMaps\v1\20161212\package\ folder in the SKMaps.zip.

In order to use the preinstalled map you have to call setPreinstalledMapsPath when initializing the library:

initMapSettings.setPreinstalledMapsPath(mapResourcesDirPath +"/PreinstalledMaps");

Note: if you use SKPackageManager.getInstance().getMapsXMLPathForVersion(version) you will have the full path to the xml file for the specific version which contains information about the association between city name (e.g. Berlin) and maps file name (DEBE.skm file).

Routing

Calculating and Displaying a Route

The calculation of routes can be made through the SKRoutingManager class and use the SKRouteListener class to receive notifications with routing callbacks. All notifications done through SKRouteListener are called on the UI Thread. These classes provide a simple way to calculate customized routes and to retrieve information about them. The routes can be calculated online or offline, if the required packages are downloaded. Any route can be calculated independent of a map, but if needed, a map instance can be set (to render the route on it).

public class MapActivity extends Activity implements SKRouteListener{
…
private void launchRouteCalculation() {
       //Clear cached routes
       SKRouteManager.getInstance().clearAllRoutesFromCache();
       // Get a route settings object and populate it with the desired properties
       SKRouteSettings route = new SKRouteSettings();
       // Set start and destination points
       route.setStartCoordinate(new SKCoordinate(-122.397674, 37.761278));
       route.setDestinationCoordinate(new SKCoordinate(-122.448270, 37.738761));
       // Set the number of routes to be calculated
       route.setMaximumReturnedRoutes(1);
       // Set the route mode
       route.setRouteMode(SKRouteMode.CAR_FASTEST);
       // Set whether the route should be shown on the map after it's computed
       route.setRouteExposed(true);
       // Set the route listener to be notified of route calculation
       // events
       SKRouteManager.getInstance().setRouteListener(this);
       // Pass the route to the calculation routine
       SKRouteManager.getInstance().calculateRoute(route);
   }

@Override
public void onRouteCalculationCompleted(final SKRouteInfo routeInfo) {
   // routeInfo contains information about the calculated route
}

@Override
public void onRouteCalculationFailed(final SKRoutingErrorCode statusCode){}

@Override
public void onAllRoutesCompleted() { }
}

@Override
public void onOnlineRouteComputationHanging(int timeSinceTheHangingBehaviorStarted) {
}

@Override
public void onServerLikeRouteCalculationCompleted(SKRouteJsonAnswer json) { }

Dealing with Alternative Routes

The SKRoutingManager class provides an easy way to calculate alternative routes. The settings are similar to a normal route just the numberOfRoutes from the SKRouteSettings has to be modified to enable alternatives. If the alternative routes are required to be of different types, the alternativeRoutesModes array can be used.

public class MapActivity extends Activity implements SKRouteListener{
…
/**
 * Launches the calculation of two alternative routes
*/
private void launchAlternativeRouteCalculation() {
       SKRouteSettings route = new SKRouteSettings();
       route.setStartCoordinate(new SKCoordinate(-122.392284, 37.787189));
       route.setDestinationCoordinate(new SKCoordinate(-122.484378, 37.856300));
       // selecting the total number of routes, with 2 alternatives
       route.setNoOfRoutes(3);
       route.setRouteMode(SKRouteMode.CAR_FASTEST);
       route.setRouteExposed(true);
       SKRouteManager.getInstance().setRouteListener(this);
       SKRouteManager.getInstance().calculateRoute(route);
}

@Override
public void onRouteCalculationCompleted(final SKRouteInfo routeInfo) {
   // routeInfo contains information about the calculated route
}

@Override
public void onRouteCalculationFailed(final SKRoutingErrorCode statusCode){}

@Override
public void onAllRoutesCompleted() { }

@Override
public void onOnlineRouteComputationHanging(int timeSinceTheHangingBehaviorStarted) {
}

@Override
public void onServerLikeRouteCalculationCompleted(SKRouteJsonAnswer json) { }

Getting Route Directions

After a route was calculated, if the route is intended for navigation or guiding, route advices can be provided from the SKRoutingManager class. Call getAdviceList and it will return all advices on the route.

public class MapActivity extends Activity implements SKRouteListener{
…

private void launchRouteCalculation() {
    // Get a route object and populate it with the desired properties
    SKRouteSettings route = new SKRouteSettings();
     // Set start and destination points
     route.setStartCoordinate(new SKCoordinate(-122.397674, 37.761278));
     route.setDestinationCoordinate(new SKCoordinate(-122.448270, 37.738761));
     // Set the number of routes to be calculated
     route.setNoOfRoutes(1);
     // Set the route mode
     route.setRouteMode(SKRouteMode.CAR_FASTEST);
     // Set whether the route should be shown on the map after it's computed
     route.setRouteExposed(true);
     // Set the route listener to be notified of route calculation
     // events
     SKRouteManager.getInstance().setRouteListener(this);
     // Pass the route to the calculation routine
     SKRouteManager.getInstance().calculateRoute(route);
   }

@Override
 public void onRouteCalculationCompleted(final SKRouteInfo routeInfo) {
   // routeInfo contains information about the calculated route
   final List<SKRouteAdvice> advices = SKRouteManager.getInstance().getAdviceListForRouteByUniqueId(routeInfo.getRouteID(),            SKMaps.SKDistanceUnitType.DISTANCE_UNIT_KILOMETER_METERS);
 }

@Override
 public void onRouteCalculationFailed(SKRoutingErrorCode statusCode){}

@Override
 public void onAllRoutesCompleted() { }

@Override
 public void onOnlineRouteComputationHanging(int timeSinceTheHangingBehaviorStarted) { }

@Override
 public void onServerLikeRouteCalculationCompleted(SKRouteJsonAnswer json) { }
}

Calculating Routes using a List of Points or a GPX File

In-depth details regarding GPX track navigation are provided on our blog.

Using a List of Points
A route can be calculated based on a list of points (coordinates). Before attempting to calculate the route, a listener should be set in SKRouteManager to ensure that routing notifications are received. Also, in the route settings the route mode should be provided as a parameter. If the points list is not valid or the route mode is not set, the method call will fail. After the route calculation completes you may start navigation on the route.

// List of points
List<SKPosition> pointsList = new ArrayList<SKPosition>();
pointsList.add(new SKPosition(23.609239, 46.767936));
pointsList.add(new SKPosition(23.609149, 46.769281));
pointsList.add(new SKPosition(23.605704, 46.768879));
// Set the route listener
SKRouteManager.getInstance().setRouteListener(this);
SKRouteSettings routeSettings = new SKRouteSettings();
//set route mode
routeSettings.setRouteMode(SKRouteMode.CAR_FASTEST);
SKRouteManager.getInstance().calculateRouteWithPoints(pointsList, routeSettings);

Using a GPX File
A route can be calculated starting with a GPX file as input. Before attempting to calculate the route based on a SKTrackElement (received once you have loaded the GPX track) a listener should be set in SKRouteManager to ensure that routing notifications are received. After the route calculation completes you may start navigation on the route.

// Load the content of a GPX file from a given path on your device
SKTracksFile gpxFile = SKTracksFile.loadAtPath("/storage/sdcard0/Download/my_track.gpx");
// Get the root track element in the previously loaded GPX file
SKTrackElement trackElement = gpxFile.getRootTrackElement();
// Set the route listener to receive routing notifications
SKRouteManager.getInstance().setRouteListener(this);
// Launch the route calculation based on the track element
SKRouteManager.getInstance().createRouteFromTrackElement(trackElement, SKRouteMode.BICYCLE_FASTEST, true, true, true, true);

Route Caching

You can cache a calculated route on the map by its id but only outside the navigation mode. This is useful for situations when you have to draw other routes and you want to cache some of them for quick ulterior restoration.

How it works:

  • in the onRouteCalculationCompleted(final SKRouteInfo routeInfo) callback save the id of the route that was calculated and you'd like to cache by calling the getRouteID() method from the method's SKRouteInfo routeInfo param;
  • only after onAllRoutesCompleted() callback is called you can cache the calculated route by calling SKRouteManager.getInstance().saveRouteToCache(routeId); and passing the saved routeId in the onRouteCalculationCompleted(). Please make sure not to try to cache the route before the onAllRoutesCompleted() is called;
  • you can then load the cached route by calling SKRouteManager.getInstance().loadRouteFromCache(routeId); and the cached route (if any) will be drawn on the map without recalculating it even if it was previously cleared from the map;
  • finally if you want to clear all the routes from cache you can call the SKRouteManager.getInstance().clearAllRoutesFromCache() method.

In the demo project, you can check a dummy implementation of the route caching in the "Interaction between maps" flow from the menu. The route that is drawn when accessing this flow is cached so that in the next activity where another map instance is created you can draw another route and when coming back to the first map the initial route is loaded from cache. If the first route had not been cached, then when creating the second route the first one would be removed because you cannot have two or more routes drawn on any map at the same time.

Please note that route caching can be used in any situations when you have to calculate several routes and not only when having multiple map instances (this situation was used only for illustrating the route caching in the demo project).

Navigation

Starting Navigation

To start a turn - by - turn navigation on an already calculated route or to start a free drive session, use the startNavigation method from the SKNavigationManager class. This method receives the SKNavigationSettings object as an input parameter for further settings.

public class MapActivity extends Activity implements SKRouteListener{
…

private void launchRouteCalculation() {
       // Get a route object and populate it with the desired properties
       SKRouteSettings route = new SKRouteSettings();
       // Set start and destination points
       route.setStartCoordinate(new SKCoordinate(-122.397674, 37.761278));
       route.setDestinationCoordinate(new SKCoordinate(-122.448270, 37.738761));
       // Set the number of routes to be calculated
       route.setMaximumReturnedRoutes(1);
       // Set the route mode
       route.setRouteMode(SKRouteMode.CAR_FASTEST);
       // Set whether the route should be shown on the map after it's computed
       route.setRouteExposed(true);
       // Set the route listener to be notified of route calculation
       // events
       SKRouteManager.getInstance().setRouteListener(this);
       // Pass the route to the calculation routine
       SKRouteManager.getInstance().calculateRoute(route);
   }

@Override
public void onRouteCalculationCompleted(final SKRouteInfo routeInfo) {
   // routeInfo contains information about the calculated route
}

@Override
public void onAllRoutesCompleted() {
    SKNavigationSettings navigationSettings = new SKNavigationSettings();
    navigationSettings.setNavigationType(SKNavigationType.SIMULATION);

    SKNavigationManager navigationManager = SKNavigationManager.getInstance();
    navigationManager.setMapView(mapView);
    navigationManager.setNavigationListener(this);
    navigationManager.startNavigation(navigationSettings);
}

@Override
 public void onOnlineRouteComputationHanging(int status) { }

 @Override
public void onServerLikeRouteCalculationCompleted(int status) { }

@Override
 public void onRouteCalculationFailed(SKRoutingErrorCode statusCode){ }

}

Handling Navigation Events

For an ongoing turn by turn navigation implement the SKNavigationListener interface to receive callbacks about:

  • navigation state: distance to destination, estimated time to destination, current street name, next street name, street type and other useful information;
  • first & secondary visual advise image and distance;
  • audio advices;
  • speed exceeded warnings;
  • re-routings;
  • destination reached;
  • free drive updates.

All notifications done through SKNavigationListener are called on the UI Thread.

public class MapActivity extends Activity implements SKRouteListener, SKNavigationListener{
…

private void launchRouteCalculation() {
       // Get a route object and populate it with the desired properties
       SKRouteSettings route = new SKRouteSettings();
       // Set start and destination points
       route.setStartCoordinate(new SKCoordinate(-122.397674, 37.761278));
       route.setDestinationCoordinate(new SKCoordinate(-122.448270, 37.738761));
       // Set the number of routes to be calculated
       route.setMaximumReturnedRoutes(1);
       // Set the route mode
       route.setRouteMode(SKRouteMode.CAR_FASTEST);
       // Set whether the route should be shown on the map after it's computed
       route.setRouteExposed(true);
       // Set the route listener to be notified of route calculation
       // events
       SKRouteManager.getInstance().setRouteListener(this);
       // Pass the route to the calculation routine
       SKRouteManager.getInstance().calculateRoute(route);
}

@Override
public void onRouteCalculationCompleted(final SKRouteInfo routeInfo) { }

@Override
public void onOnlineRouteComputationHanging(int status) { }

@Override
public void onServerLikeRouteCalculationCompleted(int status) { }

@Override
public void onAllRoutesCompleted() {
    SKNavigationSettings navigationSettings = new SKNavigationSettings();
    navigationSettings.setNavigationType(SKNavigationType.SIMULATION);
    SKNavigationManager navigationManager = SKNavigationManager.getInstance();
    navigationManager.setMapView(mapView);
    navigationManager.setNavigationListener(this);
    navigationManager.startNavigation(navigationSettings);
}

@Override
public void onDestinationReached() {
     // Three scenarios can be found in here
     // 1. Default behavior: the navigation continues in free drive mode.
     // 2. To delete the previous route from the map call
     //SKRouteManager.getInstance().clearCurrentRoute()
     // 3. To stop entering free drive navigation call
     // SKNavigationManager.getInstance().stopNavigation()
}

@Override
public void onSignalNewAdvice(String[] audioFiles, boolean specialSoundFile) { }

@Override
public void onSpeedExceeded(String[] adviceList, boolean speedExceeded) { }

@Override
public void onUpdateNavigationState(SKNavigationState navigationState) { }

@Override
public void onReRoutingStarted() { }

@Override
public void onFreeDriveUpdated(String countryCode, String streetName, SKStreetType streetType, double currentSpeed, double speedLimit) { }

@Override
public void onVisualAdviceChanged(boolean firstVisualAdviceChanged, boolean secondVisualAdviceChanged, SKNavigationState navigationState) { }

@Override
public void onTunnelEvent(boolean tunnelEntered) { }
}

Configuring the Audio Advisor

For audio advises you have two options: use pre-recorded mp3 audio files or using the phone’s text-to-speech engine. In the SKAdvisorSettings class you have the SKAdvisorType enum which allows you to choose which one of them you would like to use. For changing the audio advisor settings used by the navigation component of the SDK, call setAudioAdvisorSettings method from the SKRouteManager class.

  • Using prerecorded audio files
final SKAdvisorSettings advisorSettings = new SKAdvisorSettings();
   advisorSettings.setLanguage(SKAdvisorSettings.SKAdvisorLanguage.LANGUAGE_EN);
   advisorSettings.setAdvisorConfigPath(app.getMapResourcesDirPath() +"/Advisor");
   advisorSettings.setResourcePath(app.getMapResourcesDirPath()
  +"/Advisor/Languages");
   advisorSettings.setAdvisorVoice("en");
   advisorSettings.setAdvisorType(SKAdvisorSettings.SKAdvisorType.AUDIO_FILES);
   SKRouteManager.getInstance().setAudioAdvisorSettings(advisorSettings);
  • Using the text-to-speech engine
final SKAdvisorSettings advisorSettings = new SKAdvisorSettings();
advisorSettings.setLanguage(SKAdvisorSettings.SKAdvisorLanguage.LANGUAGE_EN);
advisorSettings.setAdvisorConfigPath(app.getMapResourcesDirPath() +"/Advisor");
advisorSettings.setResourcePath(app.getMapResourcesDirPath()
+"/Advisor/Languages");
advisorSettings.setAdvisorVoice("en");
advisorSettings.setAdvisorType(SKAdvisorSettings.SKAdvisorType.TEXT_TO_SPEECH);

Simulating a Navigation Experience

Navigation simulation can be used for testing your app and its logic without actually going on the field. The SKNavigationType from SKNavigationSettings gives the possibility of two types of simulation:

  • Normal navigation simulation which starts from the route’s start point and simulates the navigation to the destination.
SKNavigationSettings configuration = new SKNavigationSettings();
configuration.setNavigationType(SKNavigationSettings.SKNavigationType.SIMULATION);
  • Navigation simulation from a log file which simulates a navigation starting from the first location in the file to the last location in the file. The log file could be a recording of an actual drive or could be a computer generated log. By replaying position updates, you can also emulate a "free drive/free walk" experience.
SKNavigationSettings configuration = new SKNavigationSettings();
configuration.setNavigationType(SKNavigationSettings.SKNavigationType.FILE);
// setting the path to the file
configuration.setFileNavigationPath("/storage/sdcard0/files/my_log_file.log");

Trafic

Traffic Map Rendering

Traffic is used to provide visual information about incidents and traffic flow. Also when calculating routes, this information is took into account. The SKMapSettings class contains APIs for rendering the Traffic layer on the map. The traffic mode can be disabled, incidents only, flow only, flow and incidents, external sources.

// Incidents-On traffic-ON
mapView.getMapSettings().setTrafficMode(SKMapSettings.SKTrafficMode.FLOW_AND_INCIDENTS);
// Incidents-OFF traffic-ON
mapView.getMapSettings().setTrafficMode(SKMapSettings.SKTrafficMode.FLOW_ONLY);
// Incidents-ON traffic-OFF
mapView.getMapSettings().setTrafficMode(SKMapSettings.SKTrafficMode.INCIDENTS_ONLY);
// Incidents-off traffic-OFF
mapView.getMapSettings().setTrafficMode(SKMapSettings.SKTrafficMode.DISABLED);
For modifying the minimum zoom level from which to display all incidents use the API from SKMapsSurfaceView. For enabling the incidents to be cluster use
mapView.showTrafficIncidentsFromZoomThreshold(true, 16);

For enabling the incidents to be cluster use:

mapView.setTrafficIncidentsClustering(true);

Traffic Routing

The route calculations are managed by the SKRouteManager singleton class. The SKRouteListener provides information about the result of a route calculation through its delegate methods.

/**
 * Launches the calculation of three alternative routes with traffic
 */
private void launchAlternativeRouteCalculationWithTraffic() {
    SKRouteSettings route = new SKRouteSettings();
    route.setStartCoordinate(startPoint);
    route.setDestinationCoordinate(destinationPoint);
    // Number of alernative routes specified here
    route.setMaximumReturnedRoutes(3);
    route.setRouteMode(SKRouteMode.CAR_FASTEST);
    route.setRouteExposed(true);
    route.setUseLiveTraffic(true);
    route.setUseLiveTrafficETA(true);
    route.setTrafficRoutingMode(SKMapSettings.SKTrafficMode.FLOW_AND_INCIDENTS);
    SKRouteManager.getInstance().setRouteListener(this);
    SKRouteManager.getInstance().calculateRoute(route);
}

To display the traffic for the respective route id received on the callback onRouteCalculationCompleted.

@Override
public void onRouteCalculationCompleted(final SKRouteInfo skRouteInfo) {
    List<SKTrafficIncident> incidents = new ArrayList<SKTrafficIncident>();
    incidents = SKRouteManager.getInstance().getIncidentsOnRouteWithId(skRouteInfo.getRouteID(), true);
    int incidentsTotalDelay = 0;
    int totalDelay = skRouteInfo.getTrafficDelay();

    if (!incidents.isEmpty()) {
        for (SKTrafficIncident incident : incidents) {
                String incidentDescription = incident.getDescription();
            incidentsTotalDelay = incidentsTotalDelay + incident.getDelay();
        }
    } else {
        //no incidentes
    }

    int flowDelay = totalDelay - incidentsTotalDelay;
}

Traffic Updates during Navigation

To start a turn by turn navigation on a previously calculated route with traffic, use the SKNavigationManager.getInstance().startNavigation(navigationSettings). The navigation session can be configured using the SKNavigationSettings class. The traffic changes can be followed by implementing SKTrafficListener and setting it for the navigation manager SKNavigationManager.getInstance().setTrafficListener(this); and these will be received on onTrafficUpdated(SKTrafficUpdateData skTrafficUpdateData).

// Select the current route (on which navigation will run)
SKRouteManager.getInstance().setCurrentRouteByUniqueId(routeInfo.getRouteID());
SKNavigationManager.getInstance().setTrafficListener(this);
// Get navigation settings object
SKNavigationSettings navigationSettings = new SKNavigationSettings();
// Set the desired navigation settings
navigationSettings.setNavigationType(SKNavigationType.SIMULATION);
navigationSettings.setPositionerVerticalAlignment(-0.25f);
navigationSettings.setShowRealGPSPositions(false);
// Get the navigation manager object
SKNavigationManager navigationManager = SKNavigationManager.getInstance();
navigationManager.setMapView(mapView);
// Set listener for navigation events
navigationManager.setNavigationListener(this);
// Start navigating using the settings
navigationManager.startNavigation(navigationSettings);
@Override
public void onTrafficUpdated(SKTrafficUpdateData skTrafficUpdateData) {
    // A rerouting can be made here after traffic changed
    SKNavigationManager.getInstance().rerouteWithTraffic();
}

Search

Tracking POIs

This feature is used for detecting POIs during a navigation session in an area around the current location. After a navigation is launched the POI tracker should be configured with the desired detection rules and initiated. At the same time different types of POIs can be detected and different types of detection rules can be used.

The onUpdatePOIsInRadius method is required to be implemented from SKPOITrackerListener. All notifications done through SKPOITrackerListener are done on the UI Thread. This is the place where the client application should periodically provide POIs. This will be called the first time in the application when the poi tracker is used and after we cross the radiusInMeters * refreshMargin area, values that are set using the startPOITrackerWithRadius(int radiusInMeters, double refreshMargin) method from SKPOITrackerManager.

Whenever a POI is detected the onReceivedPOIs method will be called, passing in its detectedPOIs parameter all the detected POIs in the current area. For further details check the documentation of the SKPOITrackerManager.

public class MapActivity extends Activity implements SKPOITrackerListener {
...

private void startPOITracking() {
       SKTrackablePOIRule rule1 = new SKTrackablePOIRule();
       rule1.setRouteDistance(1500);
       rule1.setAerialDistance(3000);

       SKTrackablePOIRule rule2 = new SKTrackablePOIRule();
       rule2.setRouteDistance(1500);
       rule2.setAerialDistance(3000);

       SKPOITrackerManager poiTrackerManager = new SKPOITrackerManager(this);
       poiTrackerManager.startPOITrackerWithRadius(10000, 0.5);
       poiTrackerManager.setRuleForPOIType(SKTrackablePOIType.SPEEDCAM, new SKTrackablePOIRule());
}

@Override
public void onUpdatePOIsInRadius(double latitude, double longitude, int radius) {
       // set the POIs to be tracked by the POI tracker
       poiTrackingManager.setTrackedPOIs(new ArrayList<SKTrackablePOI>(trackablePOIs.values()));
}

@Override
public void onReceivedPOIs(SKTrackablePOIType type, List<SKDetectedPOI> detectedPois) { }
}

Offline Geocoding

The multi step offline geocoding can be used to search for a certain place and retrieve different map information related to it. The SKMultiStepSearchSettings will be used as an input for a top-down hierarchical search in an offline package. This search can be used for any offline package (country or city). The listLevel indicates the level at which the geocoding will be done (country, state, city, street, house number) and has to be used incrementally.

public class MapActivity extends Activity implements SKSearchListener {
…
// current list level at which to search
private SKListLevel listLevel = SKListLevel.SK_LIST_LEVEL_COUNTRY;

private void searchAtCurrentLevel(long parentId, SKListLevel level)
    // Get a search manager object
    SKSearchManager mgr = new SKSearchManager(this);
    // Get a multi-step search object
    SKMultiStepSearchSettings searchSettings = new SKMultiStepSearchSettings();
    // Set the offline package in which to search
    //Tthe France package in this case needs to be installed
    searchSettings.setOfflinePackageCode("FR");
    // Set list level of the search
    searchSettings.setListLevel(level );
    // Set maximum number of results to be received
    searchSettings.setMaxSearchResultsNumber(20);
    // Set the id of the parent in which to search
    searchSettings.setParentIndex(parentId);
    // Set a filter for the results
    searchSettings.setSearchTerm("P");
    // Initiate the search
    mgr.multistepSearch(searchSettings);
}

private void startSearch() {
    // This will start a search at country level (for city results) in France
    searchAtCurrentLevel(-1, listLevel );
}

@Override
 public void onReceivedSearchResults(List<SKSearchResult> results, boolean succeded) {
     // Results have become available (no more than 20)
     if (listLevel ==  SKListLevel.SK_LIST_LEVEL_COUNTRY) {
     // if the results came from a search at country level (i.e. results are cities)
     // start a search for streets at city level in the first city that was received as result
     listLevel = SKListLevel.SK_LIST_LEVEL_CITY;
     // level was incremented to city level
     // the id of the first city result will be used in the search
     searchAtCurrentLevel(results.get(0).getId(), listLevel);
     }
   }
}

Offline/Online POI Search

The SKSearchManager provides a way to search for points of interest around a certain position. SKNearbySearchSettings will be used as an input for customizing the search parameters and filtering the results. This functionality can be used both online or offline.

public class MapActivity extends Activity implements SKSearchListener {
…
private runNearbySearch() {
    // Get the search manager and set the search result listener
    SKSearchManager searchManager = new SKSearchManager(this);
    // Get a nearby search object
    SKNearbySearchSettings nearbySearchObject = new SKNearbySearchSettings();
    // Set the position around which to do the search and the search radius
    SKCoordinate pos = new SKCoordinate(37.756479, -122.432871);
    nearbySearchObject.setLocation(pos);
    nearbySearchObject.setRadius(3000);
    // Set the search topic
    nearbySearchObject.setSearchTerm("");
    // Set the POI main categories in which to search
    nearbySearchObject.setSearchCategories(new int[] {
    SKPOIMainCategory.SKPOI_MAIN_CATEGORY_NIGHTLIFE.getValue() });
    // Set the maximum number of results
    nearbySearchObject.setSearchResultsNumber(100);
    // Set the search in offline mode
    nearbySearchObject.setSearchMode(SKSearchManager.SKSearchMode.OFFLINE);
    // Initiate the nearby search
    SKSearchStatus status =  searchManager.nearbySearch(nearbySearchObject);
    // The status value indicates if the search could be performed
   }

@Override
public void onReceivedSearchResults(List<SKSearchResult> results, boolean succeded) {
    // The search results are now available in the list
}
}

One Box Search

OneBox search provides a cleaner, easier way of searching for cities, addresses or POIs inside one search field, instead of using one for country, city, street and number. To find out more about the internals of the OneBox search tool (the OpenStreetMap based implementation), see the dedicated page.

public class OneBoxSearchActivity extends Activity implements SKSearchListener {

    public void runOneLineSearch() {
        // Get the search manager and set the search result listener
        SKSearchManager searchManager = new SKSearchManager(this);
        // Set the search topic
        String searchTerm = "Restaurant";
        SKOnelineSearchSettings onelineSearchSettings = new SKOnelineSearchSettings(searchTerm, SKSearchManager.SKSearchMode.ONLINE);
        // Set the position around which to do the search
        SKCoordinate pos = new SKCoordinate(37.756479, -122.432871);
        onelineSearchSettings.setGpsCoordinates(pos);
        // Set the geocoder
        onelineSearchSettings.setOnlineGeocoder(SKOnelineSearchSettings.SKGeocoderType.MAP_SEARCH_OSM);
        // Set the maximum number of results
        onelineSearchSettings.setSearchResultsNumber(20);
        // The status value indicates if the search could be performed
        SKSearchStatus searchStatus = searchManager.onelineSearch(onelineSearchSettings);
    }

    @Override
    public void onReceivedSearchResults(List<SKSearchResult> list, boolean succeded) {
        // The search results are now available in the list

    }
}

Extending the OneBox (OneLine) Search Component/Integrating 3rd Party Providers

A custom search provider can be added following two steps:

  1. Define your own search object SKToolsSearchObject.
  2. /**
     * Stores input parameters for a search.
     */
    public class SKToolsSearchObject {
    
     /**
     * Search radius in meters.
     */
     private short radius;
    
     /**
     * Specifies the search text (e.g. bar, hotel, restaurant).
     */
     private String searchTerm;
    
     /**
     * The location where the search takes place.
     */
     private SKCoordinate location = new SKCoordinate();
    
     /**
     * The code for the country. This is optional; if no country code is
     * specified the search is made on all the available packages, when
     * searching on device, and on entire world on server. Setting a value will
     * help for better results
     */
     private String countryCode;
    
     /**
     * Poi types
     */
     private int[] searchCategories;
    
     /**
     * Maximum search results number.
     */
     private int itemsPerPage;
    }
  3. Define your own SKToolsSearchServiceManager class; you must implement a search functionality.
    /**
     * Class which handles search methods.
     */
    public class SKToolsSearchServiceManager {
        /**
        * Makes a request to the name browser
         *
         */
        public void nbCategorySearch(SKToolsSearchObject searchObject, SKSearchListener listener) { }
        /**
         * Cancels the previously initiated search.
         */
        public void cancelSearch(SKSearchListener listener){ }
    }

NOTE
The OneBox component is implemented in SDKTools module and for using the UI component the developer should add the OneBoxFragment to the current activity.

Reverse Geocoding

The functionality for reverse geocoding is included in SKReverseGeocoderManager. In order for the reverseGeocodePosition call to work properly, map tiles for that position should be already available (downloaded) or a map package containing that position should already be installed.

// Get a GPS position to be reverse geocoded
SKCoordinate position= new SKCoordinate(37.756479, -122.432871);
// Call reverseGeocodePosition
SKSearchResult result =
SKReverseGeocoderManager.getInstance().reverseGeocodePosition(position);
// The address information for the reverse geocoded position should
// now be available in the result object

SDK Resources documentation

Besides the framework binary and the SKMaps.jar the SDK contains one additional package:

SKMaps.zip

Inside you will find the following folders:

  • .Common - let it be hidden (mandatory package)
  • .Routing - let it be hidden (mandatory package)
  • .Shaders - let it be hidden (mandatory package)
  • attributions (mandatory package)
  • cert.pem - contains the HTTPS certificate (mandatory package)
  • daystyle
  • nightstyle
  • outdoorstyle
  • grayscalestyle
  • Advisor - contains advisor config files for different languages
  • PreinstalledMaps
  • v1 - version3_android_3_1_https.txt

As long as a style is not used by the application, the corresponding folder can be deleted, in order to keep the application size lighter.

The Advisor folder is used by the audio advisor component during a turn by turn navigation session.

It contains:

  • 4 files which are general configuration file for the advisor (advice_places.adv, angle_intervals.adv, POI_config.csv, reference_street_names.csv )
  • Languages folders. Each supported language contains a sound files folder (sound_files folder) and one for grammar rules configuration (advisor_configfiles folder).

Any unnecessary language can be deleted (the sound_files folder), if it won’t be used by the application.

If audio advises are not required by the client application, it’s ok to delete all the Advisor/Languages/../sound_files folders from the SKMaps.zip (the advisor_configfiles will still be required as it will be used for generating text to speech prompts and/or text instructions) .

SDK Update Procedure (2.X to 3.Y)

Prerequisite for the update procedure: The path where map resources will be copied on the device must be the same as the previous one.

IMPORTANT
Please note that the Map initialization process has been changed starting with version 3.0.

As the user might have set a custom path to the map resources , here is an example to use the desired map resources path:

SKMapsInitSettings mapsInitSettings = new SKMapsInitSettings();
mapsInitSettings.setMapResourcesPath(previousMapResourcesPath);

Otherwise, the path that is suitable for installing the library is determined on the framework side. In this case, old data will be lost.

First steps:

  1. Replace SKMaps.zip;
  2. Replace SKMaps.jar and SKMaps-docs.jar;
  3. Replace all so libs from arm64-v8a, armeabi, armeabi-v7a, x86;
  4. Go to the AndroidManifest.xml file and increment the versionCode's value;
  5. If there will be a new update set SKMaps.getInstance().updateToLatestSDKVersion = true; before initializing the map.

Here is an example that checks for an SDK update and overwrites the SKMaps.zis package. This method is called every time the application starts and map resources were not copied.

public void checkForSDKUpdate() {
   DemoApplication appContext = (DemoApplication) getApplication();
   int currentVersionCode = appContext.getAppPrefs().getIntPreference(ApplicationPreferences.CURRENT_VERSION_CODE);
   int versionCode = getVersionCode();
   if (currentVersionCode == 0) {
       appContext.getAppPrefs().setCurrentVersionCode(versionCode);
   }
   if (0 < currentVersionCode && currentVersionCode < versionCode) {
      SKMaps.getInstance().updateToLatestSDKVersion = true;
       appContext.getAppPrefs().setCurrentVersionCode(versionCode);
      }
   }

IMPORTANT

  1. In case the SDK is used in offline mode, starting with this version the meta files are located under PreinstalledMaps/v1 folder.
  2. The meta files are specific to the map version. An update to the latest map version is needed in case the SDK is used in offline mode only.

Known Limitations

Multiple Map support: see the Working with the Map chapter.

Settings/Developer options/ Don't keep activities is checked - When sending the application in the background the activity gets destroyed and recreated when resuming. Because the SKMapSurfaceView is recreated, this means the map will have the default SKMapSettings ( previous settings are reset). The only exception is when putting the application in background and a navigation is running. When resuming, the navigation will be still running with the exception that the map follower will be NONE ( default setting in SKMapSettings). This will be a client logic to set again the follower mode to Navigation.

Startup crash on certain Android devices due to OpenGL 2.0 limitations

On certain devices (reported so far Motorola Xoom) when initialized, the SDK throws an OpenGL error, "The device is not supported due to internal GPU limitations" (see this thread for details).

Tracking down this issue we've created a new set of map resources that should fix this error. You can download the modified SKMaps.zip from here (the following files were modified within the archive: /shaders/line.vert and /shaders/lineCap.vert).

If you are redeploying the SKMaps resources on an existing app see the SDK Update Procedure (2.X to 3.Y) chapter that provides details on how to force a map resources rewrite.

Map surface view lifecycle:

A GLSurfaceView must be notified when the activity is paused and resumed. GLSurfaceView clients are required to call onPause() when the activity pauses and onResume() when the activity resumes. These calls allow GLSurfaceView to pause and resume the rendering thread, and also allow GLSurfaceView to release and recreate the OpenGL display.

Logging and Debugging

The SKLogging class handles the logs used for debugging the app. The method enableLogs() is used to enable the logging. After the library is initialized, writeLog() method can be called to log. To save the logs to a file, call writeLogsToFile().

  • enableLogs();
// To enable logs
SKLogging.enableLogs(true);
// To disable logs
SKLogging.enableLogs(false);
  • writeLog();
// The name of the class where the log is written
final String Tag=”YourClass”;
SKLogging.writeLog(Tag,”Your message”,SKLogging.LOG_DEBUG);
  • writeLogsToFile();
SKLogging.writeLogToFile(”/Documents/My_file”);

Bug Reporting
When reporting a bug please make sure you send us all the logs generated by writeLog() and the file (if exists) generated using writeLogsToFile(), in addition to the stack traces from LogCat.

FCD

// Turn FCD on:
SKMaps.getInstance().enableFcd(true)
// Turn FCD off:
SKMaps.getInstance().enableFcd(false)