Skip to content

Overview

Introduction

What is the Mapsted Maps Mobile Framework?

The Mapsted Maps mobile framework is a cross-platform development toolkit which allows you to quickly integrate Mapsted's core location technology into your own applications. The framework is organized into several modules for easy integration.

Positioning Module: This module contains Mapsted's location positioning technology, analytics data collection, and various data retrieval and wayfinding logic functionality.

Map Module: Use the Map Module to integrate our interactive map and corresponding customizable map UI/UX into your application. The interface includes views for the map, wayfinding, itinerary management, and more.

Prebuilt UI/UX Components: A collection of beautiful, modern prebuilt UI/UX components, which are optimized for, and include, built-in integration with the Mapsted Maps mobile framework. You can import these components directly for use in your own application.

App Template: The App Template is a fully functional application right out-of-the-box, which includes some basic customization.

Mapsted Key Terms

This section will go over some key terminology that is unique to our products, to make it easier for you to get started with the Mapsted Maps Mobile Framework.

Properties, Buildings, and Floors

Each venue is referred to as a Property, which consists of one or more Building structures. Each of these Building structures contains one or more Floor structures. The Property establishes a relationship between related buildings. For reference, a visualization of the Property, Building, and Floor structures can be seen in the Figure below.

Each Property is identified by a PropertyId, each Building is identified by a BuildingId, and each Floor is identified by a FloorId. The PropertyId, BuildingId, and FloorId are unique across the Mapsted system.

Entities

An Entity is a fundamental element within a Property or Building. An Entity consists of a geometry type (i.e., MapPolygon, MapPolyline, MapPoint), a physical interpretation (e.g., Structure-Building, Structure-Room, or Obstacle-Wall), and at least one Category (e.g., Restaurant). One Entity can be associated with multiple categories, but each Entity will have one main Category.

Please see the figure below for a visualization of the Entity structure. Note that an Entity which is on the property-level (e.g., a building, parking garage, park, pond) is found in the PropertyEntities, and an Entity which is on the building-level (e.g., a room, elevator, wall) is found in the BuildingEntities.

Each Entity is identified by an EntityId, and depending on the geometry type, will have an associated PolygonId, PolylineLineId, or PointId. Note that EntityId and corresponding geometry type id are only unique within their scope. This means that for PropertyEntities, the EntityId will be unique to that particular Property. In the case of BuildingEntities, the EntityId will be unique to that Building.


Interactive Map

Map interactions are a fundamental component of Mapsted's mobile map SDK. This section will demonstrate some sample usages.

Map Interactions

The map view supports two types of map interactions, map gestures and map clicks. Map gestures allow for pan, zoom, tilt, and rotations, while map clicks allow for customizable actions (e.g., selection of entities).

Map Gestures

All common map gestures are supported by default. Learn more about the available map gestures and their effects below.

A Map Event listener can be registered to be notified when the map state changes. There are three types of map event notifications available.

  • Idle Map: A user is notified when the map view is finished rendering
  • Stable Map: A notification is sent out when all map animations have finished, and the user has lifted their fingers off the screen
  • Map Moved: This type of notification occurs when a map gesture is in progress, such as when a map is being panned, rotated, tilted, or zoomed

Below is some sample code to register a listener to be notified of map state changes.

// Handle Map's event types
MapstedMapApi.getMapEvents().observe(this, mapEvent -> {
    switch (mapEvent.getEventType()) {
        case EventType.MAP_IDLE:
            // handle idle event
            break;
        case EventType.MAP_STABLE:
            // handle stable event
            break;
        case EventType.MAP_MOVED:
            // handle map moved event
            break;
    }
});
extension YourViewController: MNMapListenerDelegate {
    func onMapMoved() {
    }

    func onMapStable() {
    }

    func onMapIdle(){
    }
}

Map Clicks

Map click events indicate when the user has clicked on the map. A map click event consists of a click type, a click location, and a click entity. See below for definitions of the various types of clicks, including Single Tap, Double Tap, Long Press, and Dual Tap.

// Register map click listener
MapstedMapApi.getMapClickEvents().observe(this, mapClickEvent -> {
    ClickType clickType = mapClickEvent.getClickType();
    IMercatorZone clickLocation = mapClickEvent.getClickLocation();
    Entity clickEntity = mapClickEvent.getClickEntity();

    // Handle event
});
/*Your view controller hosting the Mapsted map viewcontroller will 
automatically receive map event notifications if it conforms to the 
MNMapListenerDelegate delegate. When user taps on the map outside any 
vector elements, outsideBuildingTapped gets called with the tap position 
and the tap type.*/

extension YourViewController: MNMapListenerDelegate {
    func outsideBuildingTapped(tapPos: MNMercator, tapType: MNMapApi.TapType) {
        DispatchQueue.main.async {
            if tapType == .eSingle {
                //handle single tap
            }
            else if tapType == .eLong {
               //handle long tap
            }
            else if tapType == .eDouble {
                //handle double tap
            }
        }
    }
}

/*To receive map vector element notifications, your viewcontroller 
needs to conform to MNMapVectorElementListenerDelegate. The 
onPolygonTapped gets called when user taps on a vector element 
on map. The onBalloonClicked gets called when user taps a pop up.*/

extension YourViewController: MNMapVectorElementListenerDelegate {
    func onPolygonTapped(polygon: MNMapPolygon, tapType: MNMapApi.TapType, tapPos: MNMercator) {
        //From polygon clicked, get info such as name, centroid etc.

        let centroid = polygon.centroid()
        let name = polygon.name()
    }

    func onBalloonClicked(searchEntity: MNSearchEntity) {
    }
}

Automatic Map Actions

The below code sample demonstrates how to enable or disable automatic pan, zoom, tilt, and rotation functionalities. These actions are controlled automatically in Mapsted’s UI SDK.

// Disable auto-follow
MapParams.cameraFollowUser.set(false);

// Enable auto-follow
MapParams.cameraFollowUser.set(true);
// Now this is swift!!

Programmatic Map Actions

You can also programmatically adjust the map to control zoom, tilt, and rotation actions.

/*
Add custom parameters for map view events like zoom, tilt and rotate. 
Create a list of UpdateMapEvent. 
Add individual map view events with the necessary values. 
Set the map view events on UpdateMapEvent object.
Call onUpdateMapEvent passing updateMapEvent object
*/

ArrayList<UpdateMapViewEvent> updateMapViewEvents = new ArrayList<>();
// Add the required map view events
// Specify the type of event, value for that event and the speed at which you want the event to occur
updateMapViewEvents.add(new UpdateMapViewEvent(UpdateMapViewEvent.TILT, 45.0f, 1.0f));
updateMapViewEvents.add(new UpdateMapViewEvent(UpdateMapViewEvent.ZOOM, 20.0f, 0.5f));
updateMapViewEvents.add(new UpdateMapViewEvent(UpdateMapViewEvent.RECENTER, 0f, 0.5f));
updateMapViewEvents.add(new UpdateMapViewEvent(UpdateMapViewEvent.ROTATE, 0f, 0.5f));

UpdateMapEvent updateMapEvent = new UpdateMapEvent();
updateMapEvent.setUpdateMapViewEvents(updateMapViewEvents);
MapstedMapApi.onUpdateMapEvent(updateMapEvent);
//To simply center the map at a specific location at existing zoom, use:
MNMapApi.shared.mapView()?.moveToLocation(mercator: MNMercator)

//To center and update zoom use:
MNMapApi.shared.mapView()?.moveToLocation(mercator: MNMercator, zoom: Float, duration: Float)

//To rotate map, use:
MNMapApi.shared.mapView()?.setRotation(angle: Float, durationSeconds: Float)

//To tilt map, use:
MNMapApi.shared.mapView()?.tiltMap(angle: Float, duration: Float)

Offline Maps

All downloaded map data is automatically stored in a local database and available for use offline. The SDK will automatically download map data, as needed, based on what is nearby. Map data downloads can also be managed programmatically, triggered by UI interactions. See below for sample code.

// start downloading map data for a given propertyId
MapstedPositioningApi.getInstance().startPropertyDownload(propertyId, new OnMapDataDownloadedListener() {
            @Override
            public void onMapDownloaded(boolean isSuccessful, Common.MapDataType mapDataType, int mapDataId) {
                // property download completed or failed
            }

            @Override
            public void onDownloadProgressUpdate(int currentProgressCount, int totalProgressCount) {
                // property download progress update
            }
        }, new OfflineBaseMapDownloadListener() {
            @Override
            public void downloadOfflineBaseMap(PropertyInfo propertyInfo) {
                // notified that offline basemap has been downloaded
            }
        });


// delete downloaded property data
MapstedPositioningApi.getInstance().deletePropertyData(propertyId);

// When using the Map SDK, you can select and draw the property on the map
// Note that this will internally trigger a map download if it has not previously been downloaded 
MapstedMapApi.selectPropertyAndDrawIfNeeded(propertyId, new OnSetSelectedPropertyListener() {
                            @Override
                            public void onSetSelectedProperty(boolean isSuccessful) {
                                // Whether or not it successfully was drawn on the map
                            }
                        });

// Properties can also be undrawn from the map as follows:
MapstedMapApi.removePropertyFromMap(propertyId);
// To download property data
MNPositioningApi.shared.downloadPropertyDataSync(propertyId)

// To check if property is downloaded 
MNPositioningApi.shared.isPropertyDownloaded(propertyId)

// To get progress on the download, you register for notifications as an observer 
// You will need to provide the name of a method which will handle the notifications
MNCoreNotificationManager.main.addObserver(type: .dataDownloadProgress, observer: self, selector: #selector(methodToDoSomethingWithProgress))

// You can retrieve property data using the property identifier
let propertyData = MNPositioningApi.shared.propertyData(propertyId)

// When using the Map SDK, you can select and draw the property on the map
MNMapApi.shared.drawProperty(isSelected, propertyData)

// To delete downloaded property data
MNPositioningApi.shared.removePropertyData(propertyId);

// To undraw a property from the map
MNMapApi.shared.removeProperty(propertyId)

Prebuilt Map View

You have the option to customize the Prebuilt Map View in a variety of different ways. A few examples are detailed below.

// Set custom params prior to initializing the Mapsted SDK
CustomParams.newBuilder()
                .setBaseMapStyle(BaseMapStyle.DARK) // Set theme to dark
                .setMapPanType(MapPanType.RESTRICT_TO_SELECTED_PROPERTY) // restrict map panning to property
                .setMapZoomRange(new MapstedMapRange(6.0f, 24.0f)) // restrict map zoom range
                .build();

// ...

// Note: For runtime map parameter adjustments, use the LiveData in MapParams
MapParams.cameraFollowUser.set(false);
//Set the map style to light or dark
MNMapApi.shared.setBaseMapStyle(style: .DARK)

//Set map panning to restricted which limits panning to a region around the property
MNMapApi.shared.setMapPanType(type: MNMapMeta.MapPanType.eRestrictToProperty)

Custom Map View

You can power your application using our position location technology, while using your own MapView. The Location Positioning Technology guide shows you how to access user location information. Mapsted uses Open Street Maps as the georefence for user locations, buildings, and other map elements. If your system uses a basemap such as Google maps or Apple maps, please contact us, so we can make sure that our user locations, buildings, and other map elements are appropriately georeferenced for your basemap


Mapsted's prebuilt UI/UX includes intelligent search. This type of search finds matches based on names, and keywords or categories, and offers suggestions which can handle various typos or spelling errors.

Prebuilt UI/UX

Prebuilt UI/UX automatically uses the intelligent search functionality.

Search via Names/Auto Suggestions

As the user types, auto suggestions are also provided, as shown below.

Search via Categories/Keywords

Search is supported by category and keyword.

Search via Reverse Geocoding (Coming Soon)

Reverse geocoding is supported within properties and buildings. Using reverse geocoding, a MercatorZone object, like a ClickLocation from a ClickEvent, can be passed in and an Entity object will be returned. If the specified MercatorZone corresponds to a specific Entity, that entity will be returned, otherwise it will return null.

Customizable UI/UX

If Mapsted's prebuilt UI/UX is not used, the intelligent search functionality can also be programmatically accessed, as shown in the sample code below.

// Any class that inherits from ISearchable can be used
List<ISearchable> mySearchableList = new ArrayList<>();

// For example, populate ISearchable list with BuildingSearchEntities
SelectedLocation selectedLocation = MapstedMapApi.getSelectedLocation();
BuildingDataUI buildingData = selectedLocation.getBuildingData();

if (buildingData != null) { // null if no building is selected
    BuildingSearchEntities buildingSearchEntities = getBuildingSearchEntities();
    if (buildingSearchEntities != null) {
        SearchEntityMapIterator buildingEntityIt = buildingSearchEntities.getSearchEntityMap().getIterator();
        while (buildingEntityIt.hasNext()) {
            buildingEntityIt.next();
            SearchEntity searchEntity = buildingEntityIt.getValue();

            if (searchEntity == null || searchEntity.getName().trim().equals("")) {
                continue; // skip empty names
            }

            mySearchableList.add(searchEntity);
        }
    }
}

// Apply filter and sort based on input string query (e.g., query = "ga" would provide a good match for "Gap")
// filterAndSortSearchables method will filter and re-order mySearchableList
// When complete, UI can now be updated
MapstedPositioningApi.getInstance().filterAndSortSearchables(query, mySearchableList);
// Get a list of searchables for the property
let searchables = MNPositioningApi.shared.getSearchables(propertyId: 512)

//Get ordered list of indices into list of searchables based on intelligent search
let indices = MNPositioningApi.shared.filterAndSortSearchables(searchables: searchables, propertyInfo: MNPropertyInfo(propertyId: 512), searchString: "foo")
let searchResults = [Searchable]()
for index in indices {
    searchResults.append(searchables[index])
}

Location Positioning Technology

Our Location positioning technology allows for user position updates in location-enabled indoor venues and outdoor spaces. Mapsted’s technology relies on innovative, adaptive, data-fusion, and self-learning algorithms to deliver accurate, scalable indoor and outdoor positioning technology using any off-the-shelf smart-phone. The technology is forward-thinking in nature. It uses disturbances (e.g., magnetic, wireless) in the environment to learn and determine location. Basically, it approaches the problem by converting the interference, or noise, in the environment into useful information. The Figure below will give you a high-level overview of how this location positioning technology works.

User Positioning

A user's position is defined by a Position object. The Position object includes the user's IZone, which includes a propertyId, buildingId, and floorId, representing where the user is located. If any of these Ids show a value of -1, then it means they are not available. Some examples are detailed below.

Outdoor Positioning

The location positioning technology engine is seamless. It uses the same mechanism to return positions whether the user is indoors or outdoors. Based on the returned Position object, you can determine whether a user is outdoors, for example.

  • propertyId = -1 means the user is not on any premises,
  • propertyId > 0 and buildingId = -1 means the user is on the premises of the specified propertyId, but not inside any of its buildings.

Indoor Positioning

Similarly to outdoor positioning, when the user is indoors, this can be identified based on the returned Position object, as follows.

  • propertyId > 0, buildingId > 0, and floorId > 0 means that the user is on the propertyId within the buildingId, and on the floorId.

Programmatically Handling User Positions

There are two types of Position events that you can register as a listener for. The first type is a standard Position event. Position events are continuously updated by the positioning SDK, depending on the user's current activity and the type of data sources available. The second event type is PositionAnimation, which occurs when the user is in motion. PositionAnimation events dynamically update the user position at 30 frames per second, creating a smooth UI/UX experience when the user marker is in motion.

The Position event listener should be used for business logic processing based on a position update, and the PositionAnimation event listener should be used for displaying the user marker on a map UI to ensure the best user experience. The Position event listener is typically updated roughly every second, when the user is in motion. The PositionAnimation event listener is updated approximately 30 times per second when the user is in motion, to ensure smooth animation for enhanced UI/UX.

// Observe Position change events
// Updated roughly every 1 second
LiveData<Position> positionLiveData = MapstedPositioningApi.getInstance().getPositionLiveData();
positionLiveData.observe(this, position -> {
    int propertyId = position.getPropertyId();
    int buildingId = position.getBuildingId();
    int floorId = position.getFloorId();
    double x = position.getX();
    double y = position.getY();
});


// Observe Position Animation change events
// Updated roughly 30 fps when user is in motion
LiveData<Position> positionAnimationLiveData = MapstedPositioningApi.getInstance().getPositionAnimationLiveData();
positionAnimationLiveData.observe(this, position -> {
    int propertyId = position.getPropertyId();
    int buildingId = position.getBuildingId();
    int floorId = position.getFloorId();
    double x = position.getX();
    double y = position.getY();
});
//Your viewcontroller needs to register for position updates as follows:
MNPositioningApi.shared.registerPositionDelegate(delegate: self)

extension YourViewController: MNPositionDelegate {

    // Updated roughly every 1 second
    public func onPositionChange(encrypted: MNPosition) {
        let propertyId = encrypted.zone().propertyId()
        let buildingId = encrypted.zone().buildingId()
        let floorId = encrypted.zone().floorId()
        let x = position.loc.x
        let y = position.loc.y
    }

    // Updated roughly 30 fps when user is in motion
    public func onPositionChangeWithAnimation(encrypted: MNPosition, begin: Bool) {
    }
}

User Bearing

The Bearing is a float which represents the direction that the user is currently facing. Bearing is specified in degrees clockwise from North. This listener is only called when the user is on the premises of a property.

LiveData<Float> bearingLiveData = MapstedPositioningApi.getInstance().getBearingLiveData();
bearingLiveData.observe(this, bearing -> {
    // bearing is specified in degrees clockwise from North
});
//Your viewcontroller needs to register for bearing updates as follows:
MNPositioningApi.shared.registerBearingDelegate(delegate: self)

extension YourViewController : MNBearingDelegate {
    func onBearingChange(bearingDeg: CGFloat) {
         // bearing is specified in degrees clockwise from North
    }
}

Coordinate Systems

Mapsted uses the Universal Transverse Mercator (UTM, EPSG:3857) coordinate system to repesent the global (x,y) position in meters. Support is also provided for latitude and longitude (WGS84) in degrees, as shown below.

LatLng latLng_1 = new LatLng(someLatitude, someLongitude);
Mercator mercator_1 = MapCalc.toMercator(latLng); // From WGS84 to EPSG:3857

Mercator mercator_2 = new Mercator(someX, someY);
LatLng latLng_2 = MapCalc.toLatLng(mercator.getX(), mercator.getY());  // From EPSG:3857 to Lat, Lng
let pos1 = MNMercator(lat: someLatitude, lng: someLongitude)
print("X: \(pos1.x) Y: \(pos1.y)") // From WGS84 to EPSG:3857

let pos2 = MNMercator(x: someX, y: someY, z: someZ)
let latLng:CLLocationCoordinate2D = MNMercator.latLong(from: pos2)  // From EPSG:3857 to Lat, Lng
print("Lat: \(latLng.latitude) Long: \(latLng.longitude)") 

Wayfinding

Wayfinding uses Mapsted's advanced routing technology to generate optimal routes to help users get around in selected properties and buildings. The wayfinding engine can handle property-wide multi-destination routing. Each RouteRequest consists of a start location, such as the user's current location or a point of interest, and a list of destination locations or points of interest. The RouteRequest also consists of several RouteOptions that determine which accessibility measures are necessary, the order the user would like to navigate to the destinations in, and allow for the use of optimized routes.

The RouteResponse consists of a list of Route objects, where each Route consists of a list of RouteSegment objects. Each RouteSegment corresponds to a portion of the route on a specific floor of a building, or property level, for example outdoors, and is linked via a TransitionType enum. A figure showing an illustrative example of a RouteResponse is shown below. In the illustrative example above, the user is navigating from a location on Level 2 of Building A to a location on Level 1 of Building B. As shown, the route from point A to point B involves four RouteSegments and includes three transitions - between floors, to outdoors, and to indoors.

Prebuilt UI/UX

When using the Prebuilt UI/UX, the wayfinding functionality is automatically in use.

Customizable UI/UX

There are a number of customizable routing parameters available, which can be set programmatically, as outlined below.

Programmatic Wayfinding

// Setup RoutingCallback

MapstedPositioningApi.getInstance().setRoutingCallback(new RoutingCallback() {
            @Override
            public void requestRoute(RouteRequest routeRequest) {
                // Not used
            }

            @Override
            public void onRouteResponse(RouteResponse routingResponse) {
                // Called after a route request is processed
                boolean isSuccessful = routingResponse.isSuccessful();
                Route route = routingResponse.getCurRoute();

                // Note: UI may be updated to show route preview to user
                // ...

                // If user desires to begin navigation, can be started by using:            
                MapstedPositioningApi.getInstance().startNavigation();

                // Navigation can be cancelled midway through (e.g., user UI interactions) by calling:
                MapstedPositioningApi.getInstance().stopNavigation();
            }

            @Override
            public void onRoutingStatus(boolean isRoutingModeOn, RouteResponse latestRouteResponse) {
                // Indicates a status change for the latest route response
                // For example, if isRoutingModeOn is false, UI can adjust accordingly
            }

            @Override
            public void onRouteInstruction(RouteUserStatus routeUserStatus) {
                // Called when a new instruction is available for the real-time navigating user
            }

            @Override
            public void onRouteRecalculation(RouteResponse newRouteResponse) {
                // Called when the user deviates from the route provided
                // and a new route is automatically recalculated
            }

            @Override
            public void onDestinationReached(int destinationEntityId) {
                // Called when the user reaches the destination (specified by the destinationEntityId)
                // Note that stopNavigation is internally called
            }
        });



// Setup desired routing options
// OptimizedRoute will re-order destinations optimally, if set to false will navigate in order
// If Accessibility is true, it may override the behaviour of Stairs/Elevator/Escalator
RouteOptions routingOptions = new RouteOptions();
routingOptions.setOptimizeRoute(true);
routingOptions.setFromCurrentLocation(false); // true if routing from user's position
routingOptions.setAccessibility(false);
routingOptions.setIncludeElevators(true);
routingOptions.setIncludeEscalators(true);
routingOptions.setIncludeStairs(true);

RouteRequestBuilder builder = new RouteRequestBuilder()
                    .setRouteOptions(routingOptions)
                    .setStartSearchable(myStartSearchable) // only if not from 'MyLocation'
                    .addDestination(myDestination_1) // add possibly multiple destinations
                    .addDestination(myDestination_2)
                    .addDestination(myDestination_3);

RouteRequest request = builder.build();

// Start Async route request
// Response will be passed to the RoutingCallback
// UI should be updated accordingly
MapstedPositioningApi.getInstance().requestRoute(request);
//Register your viewcontroller to receive route updates
MNPositioningApi.shared.registerRoutingDelegate(delegate: self)

extension YourViewController: MNRoutingDelegate {
    func onRouteResponse(_ response: MNRouteResponse) {
         guard response.isSuccessful else {
            // routing failed
            return
        }
        //handle response here...

        // If user desires to begin navigation, can be started by using:
        MNPositioningApi.shared.startNavigation()

        // Navigation can be cancelled midway through (e.g., user UI interactions) by calling:
        MNPositioningApi.shared.stopNavigation()
    }

    func onRoutingStatus(isRoutingModeOn: Bool, latestRouteResponse: MNRouteResponse) {
        // Indicates a status change for the latest route response
        // For example, if isRoutingModeOn is false, UI can adjust accordingly
    }

    //Navigation:

    func onRouteInstruction(nextKeyPoint: MNRouteNode) {
        // Called when a new instruction is available for the real-time navigating user
    }

    func onRouteRecalculation(newRouteResponse: MNRouteResponse) {
         // Called when the user deviates from the route provided
         // and a new route is automatically recalculated
    }

    func onDestinationReached(destinationEntityId: NSInteger) {
        // Called when the user reaches the destination (specified by the destinationEntityId)
    }
}

// Setup desired routing options
// OptimizedRoute will re-order destinations optimally, if set to false will navigate in order

let optimizedRoute = true
let useStairs = false
let useEscalators = true
let useElevators = true
let calculateFromCurrentLocation = true

let routeOptions = MNRouteOptions.init(useStairs, escalators: useEscalators, elevators: useElevators, current: calculateFromCurrentLocation, optimized: optimizedRoute)

// There is also an accessibility option provided - it may override the behaviour of Stairs/Elevator/Escalator
routeOptions.setAccesibility(true)

//Build a route request
var routeRequest: MNRouteRequest?

if calculateFromCurrentLocation { //startEntity is passed as nil
    routeRequest = MNRouteRequest.init(routeOptions: routeOptions, destinations: destinations, startEntity: nil)
}
else { //destinationsMinusStart is an array of destinations excluding the start 
    routeRequest = MNRouteRequest.init(routeOptions: routeOptions, destinations: destinationsMinusStart, startEntity: startEntity)
}

if let routeRequest = routeRequest {
    MNPositioningApi.shared.requestRoute(request: routeRequest)
}

Understanding Route Responses

A RouteResponse contains a boolean which indicates whether or not the RouteRequest was successfully processed. An error may occur for several reasons, such as if an invalid start or data was provided, or a route could not be found.

A requested route may involve single or multiple destinations. A RouteResponse consists of a RouteVector (a list of Route objects), where each Route represents a trip to a specific destination. Each Route consists of a RouteSegmentVector (a list of RouteSegment objects), where each RouteSegment represents a separate segment of the route, such as a different floor inside a building, or an indoor-outdoor segment. Each RouteSegment consists of a RouteNodeVector (a list of RouteNode objects), where each RouteNode represents a specific location, and possibly, a corresponding instruction.

// Note: Do not forget to handle null cases

RouteResponse routingResponse = MapstedMapApi.getRoutingResponse();

/*
    If multiple destinations are requested, there may be multiple routes.
    Each route represents the trip to a specific destination.
*/
RouteVector routeVector = routingResponse.getRoutes();

// The route index that is currently selected (e.g., in route preview mode)
int currentRouteIndex = MapstedMapApi.getCurRouteIndex(); 

// The currently selected route (e.g., in route preview mode)
Route route = routeVector.get(currentRouteIndex);

// All route segments for the specifc route (e.g., multiple floors or indoor-outdoor)
RouteSegmentVector routeSegmentVector = route.getSegments();

// Loop through all route segments
for (int i = 0; i < routeSegmentVector.size(); i++) {
    RouteSegment routeSegment = routeSegmentVector.get(i);

    // Outside property, within property, or within building
    RouteSegmentType segmentType = routeSegment.getSegmentType();

    // Smoothed route for UI plotting
    MercatorVector smoothedRoute = routeSegment.getSmoothedRoute();

    // Can iterate over non-smoothed (discretized) route nodes
    for (int j = 0; j < routeSegment.getRouteNodes().size(); j++) {
        RouteNode routeNode = routeSegment.getRouteNodes().get(j); 

        // key points will have instructions
        boolean isKeyPoint = routeNode.isKeyPoint();

        if(isKeyPoint) {
            // instruction will take into account localization, if supplied
            // default is english
            String instruction = routeNode.getInstruction();

            // You can customize your own behaviour by using the instruction type
            // e.g., TURN_LEFT, TURN_RIGHT, ENTER_BUILDING, ...
            InstructionType instructionType = routeNode.getInstructionType()

            // EntityId of the instruction's landmark (-1 if doesn't exist)
            int landmarkEntityId = routeNode.getLandmarkEntityId();
        }
    }
}

// ...
func routeResponseReceived(response: MNRouteResponse) {
    guard response.isSuccessful else {
            // routing failed
            return
    }

    /*
    If multiple destinations are requested, there may be multiple routes.
    Each route represents the trip to a specific destination.
    */
    let routes = response.routes
    //Index into routes to access a particular route
    let route = routes[0];

    //You can access individual segments of a route as follows
    let routeSegment = route.segments[1] //size checks are skipped for brevity
    let segmentType = routeSegment.segmentType // Outside property, within property, or within building

    // Smoothed route for UI plotting
    let smoothedRoute = routeSegment.smoothedRouteNodes

    //Get instructions
    for routeSeg in route.segments {
        for routeNode in routeSeg.routeNodes {
            if routeNode.isKeyPoint {
                // instruction will take into account localization, if supplied
                // default is english
                let instruction = routeNode.instruction

                // You can customize your own behaviour by using the instruction type
                // e.g., TURN_LEFT, TURN_RIGHT, ENTER_BUILDING, ...
                let instructionType = routeNode.instructionType

                // EntityId of the instruction's landmark (-1 if doesn't exist)
                let landmarkEntityId = routeNode.landmarkEntityId
            }
        }
    }
}

Accessibility

Accessibility Routing

Mapsted SDK supports accessibility routing. When enabled, the provided routes avoid stairs, steps, and escalators, and use ramps or elevators to help the user navigate to their destination instead. For even finer control, the SDK also allows for users to specify specific transitions which they would like to take, for example if the user would prefer to take the escalator or elevator over stairs.

RouteOptions routeOptions = new RouteOptions();

// If full accessibility is desired, it can be enabled
// Note that this will override all other customizations for stairs, escalators, elevators
routeOptions.setAccessibility(true);

// Alternatively, if for example, the user perfers escalators/elevators over stairs
// The following can be used
routeOptions.setAccessibility(false);
routeOptions.setIncludeStairs(false);
routeOptions.setIncludeEscalators(true);
routeOptions.setIncludeElevators(true);

// Create RouteRequest and process. See Wayfinding section for more information.
//Create an instance of MNRouteOptions. You can configure the first three parameters based on whether you prefer 
//stairs/escalators/elevators. The MNRouteOptions object is used to create a route request.

let useStairs = false
let useEscalators = true
let useElevators = true

let routeOptions = MNRouteOptions.init(useStairs, escalators: useEscalators, elevators: useElevators, current: true, optimized: true)

//There is an accessibility mode that overrides previous settings.
routeOptions.setAccessibility(true)

Localization

The Mapsted SDK offers multiple language support, including support for both left-to-right and right-to-left languages. Support is automatically provided for most languages, but appropriate translation and font files would be necessary to support non-mainstream languages. This is handled automatically when using the Map UI SDK or prebuilt UI/UX.

Localization Example

The figures below show the same map view in English and Arabic, respectively. As you can see, the map UI automatically handles the left-to-right and right-to-left localizations.

Programmatically Controlling Localization

The Mapsted mobile SDK will automatically capture the localization based on the device's settings, if the selected language is supported. If the language is not available, it will automatically default back to English. The localization can also be modified programmatically, as shown below.

    // Sample usage for listening for language events, notifying the Mapsted Map
    // Note that when using the Mapsted Map-Ui or prebuilt UI/UX this is handled automatically

     HandlerThread languageHandler = new HandlerThread("LanguageSupport");
        languageHandler.start();

        new Handler(Looper.getMainLooper()).post(() -> {
            LocaleManager.getInstance(mActivity.getApplicationContext()).getLanguageMutableLiveData().observe(mActivity, (languageLiveData) -> {
                new Handler(languageHandler.getLooper()).post(() -> {
                    MapstedMapApi.setLanguageCode(languageLiveData, (language, propertyId, isAvailable) -> {
                        // TODO: Update the rest of the UI
                    });
                });

            });
        });
//iOS automatically relaunches the app in use when the the user changes their 
//preferred language from settings. To change the user default language programmatically, 
//call the set method of the system provided UserDefault class by means of its shared standard object.

UserDefaults.standard.set(["en"], forKey: "AppleLanguages")
UserDefaults.standard.synchronize()

//You will need to provide the two letter language code to the method. 
//Standard language code values include "en" for English, "fr" for French, "ar" for Arabic, etc.

Prebuilt UI/UX

We offer a wide variety of Prebuilt app templates and UI components which have been designed and optimized for use with the complete Mapsted Maps Mobile framework.

Depending on your desired level of customization, you can choose from several Prebuilt UI/UX options. Our Prebuilt app templates provide a fully immersive mobile application that includes basic customizations such as colour changes. For more advanced customization, you can use our Prebuilt UI components to build your own application. These components include a suite of Prebuilt UI views, such as wayfinding UI/UX, itinerary management, and category lists.


Prebuilt App Templates

Our Prebuilt app templates allow you to experience the look and feel of a full mobile application within minutes, while providing basic customization options to give the app your own look and feel. See below for examples of a few app template UI screens.

To learn how to enable a Prebuilt app template, see the sample code below.

// Extending Mapsted App Template enabled Mapsted's Prebuilt app templte
// Note that this could also be achieved using an Intent to launch
// MapstedAppTemplateMainActivity
public class MainActivity extends MapstedAppTemplateMainActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Anything you want...
    }

}
// Now this is swift!!

Prebuilt UI Components

The Prebuilt app templates outlined in the previous section use a variety of Prebuilt UI components. Our UI components are specific UI/UX views which are designed and optimized to interact with the Mapsted Maps Mobile framework. This allows you to custom pick and choose which aspects of our Prebuilt UI/UX you want to use in your application.

The Prebuilt UI components are an excellent choice when you already have your own application and want to easily add some additional functionality based on the Mapsted Maps Mobile framework. For example, one common usage is to trigger a Mapsted Map view and use various Prebuilt UI components, like wayfinding UI/UX or itinerary management, when a maps button is clicked within your application. In other scenarios, various Prebuilt UI components can be reused in other areas of your application, for example category lists


Customizable UI/UX

The customizable UI/UX allows you to use various prebuilt UI/UX components, while also allowing you the option of adding your own customizable UI. As an example, you can use your own UI views on the Mapsted Map. This section explains how you can create and display custom views on the map. The two examples below show how custom UI elements, such as the blue "Explore More" button and the horizontal floating category list, were added to the Mapsted Map component.


Programmatically Add, Update, and Remove Custom Views

Any view can be programmatically added, modified, and removd from the Mapsted Map. Each view that is passed in should be given a custom view tag (a string to uniquely identify the view), which can be used for retrieving/modify, and/or removing the view. The sample code below shows how a view can be added, modified, and removed.

    // Create Custom View (e.g., contains a button named with id custom_button)
    View inflate = LayoutInflater.from(this).inflate(R.layout.custom_layout, null, false);
    Button custom_button = inflate.findViewById(R.id.custom_button);

    // For example, set click listener
    custom_button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // Handle click event
        }
    );

    // Display custom View on the map
    MapstedSdkController.getInstance().addViewToMap("my_custom_tag", myView);

    // Get custom View on the map
    View myView = MapstedSdkController.getInstance().getViewOnMap("my_custom_tag");

    // Can now modify view, as desired (e.g., change visibility)
    // ...

    // Remove custom View from the map
    MapstedSdkController.getInstance().removeViewFromMap("my_custom_tag");
//To access UI components, you need to add the following import
import appuikit

//To facilitate adding UI components to your app, a ContainerViewController class is provided 
//which allows easy swapping in/out of viewcontrollers. To use this, first drag and drop a container 
//view to your viewcontroller and set the embedded viewcontroller class in IB to ContainerViewController. 
//Assuming you have named the embedded segue "containerSegue", you can obtain a reference to the 
//ContainerViewController as follows.

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "containerSegue" {
        if let containerVC = segue.destination as? ContainerViewController {
            self.containerVC = containerVC
        }
    }
}

//Now to add a viewcontroller, simply call addController. Parameter isNew should be 
//set to true in case you want to maintain a stack of the viewcontrollers in memory 
//for fast swapping. If set to false, only one viewcontroller will be retained.

containerVC.addController(controller: categoryListVC, yOffset: 0, isNew: false)

//To display a floating category list, access the CategoryCarouselViewController. 
//Pass categoryUIDs which is list of category uids to display. The alignment can 
//be horizontal/vertical. Your viewcontroller needs to conform to CategorySelectionDelegate
//to get notified when user selects a category.

if let categoryListVC = CategoryCarouselViewController.instantiateViewController(propertyInfo: propertyInfo, categoryUIDs: categoryUIDs, alignmentIsVertical: false, delegate: self) as? CategoryCarouselViewController {
    categoryListVC.setCategoryTextColor(color: .white)
    containerVC.addController(controller: categoryListVC, yOffset: 0, isNew: false)
} 

extension YourViewController : CategorySelectionDelegate {
    func selectedCategory(category: MNCategory) {
        //handle the selection...
    }
}

Location Sharing (Coming Soon)

The Mapsted Maps Mobile framework offers several location-sharing options which use the power of our location positioning technology to help users interact with friends or colleagues.

Using location tags, users can easily share a specific location and/or message to one another for easy coordination. For example, a user could tag the food court on level 3 of a shopping centre, and share the location tag with several friends, along with a message that reads, "Meet me at 1 pm for lunch."

Using location sharing, users can also display their locations on each other's maps. This can be especially useful for couples or family members that wish to visit different places in a large venue, while still being aware of each other's location.

Location Tags

When location tagging is enabled in your application, users can use a long press gesture on the map to set a tag. This tag can be labelled, saved, navigated to, or shared with another user. Users also have the option to attach a message to the tag. Once the location tag is shared and received by another user, they can easily view and navigate to it. This provides a simple and easy-to-use approach for sharing information about a specific location with a friend or colleague.

To learn how to enable or disable a location tag, see the sample code below.

// For example, to enable tag UI when initializing
CustomParams.newBuilder()
                .setEnableTagUI(true) // enable tag UI
                .build();

// Note that this can also be adjusted in runtime, as shown below 

// Enable location tagging
UiParams.enableTagUI.set(true); // for the UI views
MapstedMapApi.setPlacePinOnSelectedLocation(true); // for pin on map

// Disable location tagging
UiParams.enableTagUI.set(false); // for the UI views
MapstedMapApi.setPlacePinOnSelectedLocation(false); // for pin on map
//Enable location tagging
MNMapApi.shared.allowTagsOnMap(enable: true)

//Disable location tagging
MNMapApi.shared.allowTagsOnMap(enable: false)

Programmatic Location Sharing

The location sharing framework was designed for maximum flexibility. You can use the Prebuilt option or choose to customize based on your specific needs. The location sharing framework requires a string key identifier to represent different users. It is important that the application is able to interpret this string identifier to provide meaningful UI to the user to connect and share locations with friends, colleagues, or family.

For example, our Prebuilt location sharing UI/UX offers location sharing based on the user's contact list and uses their phone number as the string key identifier. This allows for our UI to provide the user with a recognizable name or description of the friends or colleagues they can connect to, based on how they labelled them in their own contact list. Depending on your application, a phone number may not always be the best choice of string identifier. If your application maintains its own user ID system, which links to a user profile or friend list, then that option may work better for you as a string identifier. In that case, the customizable location sharing option will still allow you to take advantage of our location sharing functionality.

Prebuilt Location Sharing

Our Prebuilt location sharing UI/UX has been designed to use each user's phone number as the string key identifier. The Prebuilt UI/UX includes views for viewing the contact list, selecting which users you want to share your location with, and managing the location sharing. Once enabled, the map will automatically display the locations of other users and will also provide views for easily locating them on the map.

Customizable Location Sharing

The customizable location sharing option requires that you manage your own string key identifiers. These could still be the user's phone number, or, you can use any type of identifier that works for you. The location sharing map UI is still included in this case, but as the developer, you will be required to specify the properties for each user that will be displayed on the map, for example, string key identifier, name, or image.