Mapwize For Mapbox on iOS

The Mapwize iOS SDK is built as a plugin on top of Mapbox GL native for iOS.

The Mapbox map is used as base map to display the outdoor. It is an amazingly powerful SDK allowing you to do a lot of cool stuff with the map. If you want to move the map, rotate it, overlay your own data or more, you can do it directly by controlling the Mapbox map. Adding Mapwize does not remove any capability from Mapbox, it just adds more. Have a look at the Mapbox documentation to see all features.

This Mapwize plugin is adding the possibility of getting inside buildings. As you zoom on the map, you will automatically enter in buildings, see the different floors and be able to navigate inside. If you want to change floors, see different universes (views) of the building, draw directions inside or display the user's indoor location, then you'll interact with the Mapwize plugin.

The Mapwize plugin is written in Objective-C for maximum compatibility and performance. However, it is working and extensively tested in both Objective-C and Swift apps.

Getting started

A very simple app integrating Mapwize is available on our Github. It contains the minimum code to display the map. It is available in both Objective-C and Swift.

Mapwize simple app Objective-C

Mapwize simple app Swift

Minimum iOS version

Mapwize is compatible with iOS 9 and higher.

Adding Mapwize to your project

Cocoapod

Mapwize is available through CocoaPod. Using pod, all dependencies, including Mapbox, will be added automatically. If you do not want to use pod, you will need to manually download the MapwizeForMapbox framework as we as all dependencies.

Simply add the following line in your Podfile

pod 'MapwizeForMapbox'

Carthage

Mapwize is available through Carthage since 1.7.0.

Add the following lines to you Cartfile

github "IndoorLocation/indoor-location-ios" ~> 1.0.5
binary "https://www.mapbox.com/ios-sdk/Mapbox-iOS-SDK.json" ~> 4.6.0
binary "https://sdk.mapwize.io/mapwize-for-mapbox-ios/carthage/MapwizeForMapbox.json" ~> 1.8.0

Mapwize API Key

You'll need a Mapwize API key to load the plugin and allow API requests. Simply add MWZMapwizeApiKey with the key in your info.plist.

To get your own Mapwize API key, sign up for a free account at mapwize.io. Then within the Mapwize Studio, navigate to "API Keys" on the side menu.

Mapwize demo keys are available for testing in the demo projects. Please note they cannot be used in production.

Configuring the outdoor map

MapwizePlugin is using Mapbox for the outdoor map. There are 2 options regarding the outdoor:

To use the default Mapwize outdoor map:

If you want to use your custom Mapbox style, get your Mapbox API key by sign up at mapbox.com

Custom Server Url

If you are using a custom instance of Mapwize server you have to set the api URL. This is done by adding a "MWZCustomApiUrl" to your info.plist.

The default value is "https://api.mapwize.io/v1/"

Adding your first map

Instanciate a Mapbox map, either through storyboard or through code. See details here

When the map is loaded, you can load the MapwizePlugin and attach it to the Mapbox map. Assuming _mglMapView is a IBOutlet to MGLMapView:

You can instantiate Mapwize as soon as you get a MGLMapView with one of the following constructors:

- (instancetype) initWith:(MGLMapView*) mglMapView options:(MWZOptions*) options;
- (instancetype) initWith:(MGLMapView*) mglMapView options:(MWZOptions*) options uiSettings:(MWZUISettings*) settings;

Like for example

MapwizePlugin* mapwizePlugin;

- (void)viewDidLoad {
[super viewDidLoad];
mapwizePlugin = [[MapwizePlugin alloc] initWith:self.mapView options:options];
mapwizePlugin.delegate = self;
mapwizePlugin.mapboxDelegate = self;
}

Take a look at the simple apps for details and Swift version.

Mapwize plugin options and UISettings

The plugin can be loaded with the following options available in the MWZOptions class:

Options referring to UI component are provided in the MWZUISettings class:

Delegate

MapwizePlugin needs access to the Mapbox map delegate to work properly. At this point, Mapbox only allows a single delegate to be set. If you need access to the Mapbox delegate, you can set your in MapwizePlugin.mapboxDelegate which provides a copy of all events. Setting MGLMapView.delegate will cause Mapwize plugin to fail.

The Mapwize delegate provides callback for the following events:

- (void) mapwizePluginDidLoad:(MapwizePlugin*) mapwizePlugin;

- (void) plugin:(MapwizePlugin*) plugin willEnterVenue:(MWZVenue*) venue;
- (void) plugin:(MapwizePlugin*) plugin didEnterVenue:(MWZVenue*) venue;
- (void) plugin:(MapwizePlugin*) plugin didExitVenue:(MWZVenue*) venue;

- (void) plugin:(MapwizePlugin*) plugin didTap:(MWZClickEvent*) clickEvent;

- (void) plugin:(MapwizePlugin*) plugin didChangeFloor:(NSNumber*) floor;
- (void) plugin:(MapwizePlugin*) plugin didChangeFloors:(NSArray<NSNumber*>*) floors;

- (void) plugin:(MapwizePlugin*) plugin didChangeFollowUserMode:(FollowUserMode) followUserMode;

MWZClickEvent

In order to react to click events on the map, Mapwize provides an object that helps you know on which object the user clicked. It can be a venue, a place or nothing.

It is represented with 3 event types :

typedef enum MWZClickEventType: NSUInteger {
MAP_CLICK,
PLACE_CLICK,
VENUE_CLICK
}

The following attributes can be retrieved :

MWZClickEventType eventType;    // Enum, see above
MWZLatLngFloor* latLngFloor;    // The latLngFloor at which the click was made. The latitude and longitudes are always set, but the LatLngFloor latLngFloor;     floor can be null. Available for all event types.
MWZPlace* place;                // Not null if eventType == PLACE_CLICK
MWZVenue* venue;                // Not null if eventType == VENUE_CLICK

A typical way to handle those event :]

func plugin(_ plugin: MapwizePlugin!, didTap clickEvent: MWZClickEvent!) {
switch clickEvent.eventType {
case VENUE_CLICK:
let venue = clickEvent.venue
// Do something with venue
break
case PLACE_CLICK:
let place = clickEvent.place
// Do something with place
break;
default:
let latLngFloor = clickEvent.latLngFloor
// Do something with coordinates
}
}

Indoor Location

Displaying the user's location on the map is done by connecting an IndoorLocationProvider to Mapwize.

There are a lot of IndoorLocation providers already freely available as part of the IndoorLocation open-source framework.

If you are missing your provider, contact us at support@mapwize.io or build your own starting from the base classes.

To set the IndoorLocationProvider on the MapwizePlugin:
objective-c
- (void) setIndoorLocationProvider:(ILIndoorLocationProvider*) locationProvider;

If you want to manually set the user location, you can use the ManualIndoorLocationProvider

You can retrieve the current user position on the map using ILIndoorLocation* userLocation.

There is no delegate to be notified of any new user location. Subscribe to the IndoorLocationProvider's delegate instead.

The user's heading is also displayed on the map as an arrow from the user position. The heading is retrieved directly from the device. You can read the user heading using NSNumber* userHeading. It is not possible to manually set the user heading at this point.

FollowUserMode

You can use the follow user mode feature to automatically move the map as the user location changes.

3 modes are available on the MWZFollowUserMode class:

You can retrieve the current mode using the FollowUserMode followUserMode property and you can set it using

- (void) setFollowUserMode:(FollowUserMode) followUserMode;

The FollowUserMode is automatically set to NONE when the user manually moves the map or changes floor.

Venues

Indoor maps are defined inside venues. The map will automatically enter in a venue when the zoom is sufficient, and exit the venue when the map is moved away. Only one venue can be open at a time.

The currently displayed venue can be retrieved using the getVenue method

- (MWZVenue*) getVenue;

The method returns null if no venue is displayed on the map. The willEnterVenue, didEnterVenue and didExitVenue events from the delegate can be used.

Universes

Venues can have multiple universes. Universes are like views. In Mapwize Studio, it is possible to define which elements are displayed in each universe and define security policies for each of them.

When a venue is displayed, the method (MWZUniverse*) getUniverse will return the currently displayed universe for that venue, and (void) setUniverse:(MWZUniverse*) universe will change the universe.

You can set and get the universe for each venue using

- (void) setUniverse:(MWZUniverse*) universe forVenue:(MWZVenue*) venue;
- (MWZUniverse*) getUniverseForVenue:(MWZVenue*) venue;

Floors

Venues usually have multiple floors. Floors are identified by a number which can be decimal.

When a venue is displayed, the method (NSNumber*) getFloor will return the currently displayed floor for that venue and (NSArray<NSNumber*>*) getFloors will return the list of active floors for that venue. A floor is considered active if the geometry of one layer of that floor is intersecting with the visible region on the screen.

You can change floor using

- (void) setFloor:(NSNumber*) floor;
- (void) setFloor:(NSNumber*) floor forVenue:(MWZVenue*) venue;

The didChangeFloor event is fired when the floor changes.
The didChangeFloors event is fired when the list of active floors changes, which may or may not happen at venue enter, venue exit, universe change or camera move.

Languages

The title of the places that are displayed on the map can be translated in multiple languages and you can control what language is used.

Firstly, you can set the preferered language of the user. By default, titles will be displayed in the preferred language if available in the venue. Otherwise, the main language of the venue is used.

- (void) setPreferredLanguage:(NSString*) language;
- (NSString*) getPreferredLanguage;

It is also possible to specify what language to use for a specific venue

- (void) setLanguage:(NSString*) language;
- (void) setLanguage:(NSString*) language forVenue:(MWZVenue*) venue;
- (NSString*) getLanguageForVenue:(MWZVenue*) venue;
- (NSString*) getLanguage;

Moving the map

The Mapbox map offers lots of options to move the camera viewing the map. Some specific methods are available on the MapwizePlugin to move the map towards Mapwize objects:

- (void) centerOnVenue:(MWZVenue*) venue forceEntering:(BOOL) force;
- (void) centerOnPlace:(MWZPlace*) place;
- (void) centerOnUser;

Please note that the centerOnVenue takes the forceEntering parameter. If set to false, the bounds of the maps are fit so that the entire venue is visible. For large campuses, this may mean that the zoom level is not high enough to enter the venue. If forceEntering is set to true, the map will be zoomed in to be sure that the venue is displayed, possibly not showing the entire campus.

Adding markers

Markers is a convenient way to add simple pins on the map.

Mapwize Markers are positioned on a specific floor and therefore only displayed if that floor is selected. However, they are not attached to a specific venue and will be displayed even if the venue is not visible.

If you want to add more complex annotations on the map, you have all the power of Mapbox at your disposal. Have a look at their documentation

By default, the image used for the pin is the standard Mapwize pin. However, you can specify your own using an UIImage. The anchor of the marker is always the center of the image, meaning that it's always the center of the image that is on top of the coordinates you specified. If you want to have your marker only on top, simply use an image with transparency.

- (MWZMapwizeAnnotation*) addMarker:(MWZLatLngFloor*) latLngFloor;
- (MWZMapwizeAnnotation*) addMarker:(MWZLatLngFloor*) latLngFloor image:(UIImage*) image;
- (MWZMapwizeAnnotation*) addMarkerOnPlace:(MWZPlace*) place;
- (MWZMapwizeAnnotation*) addMarkerOnPlace:(MWZPlace*) place image:(UIImage*) image;
- (NSArray<MWZMapwizeAnnotation*>*) addMarkersOnPlaceList:(MWZPlaceList*) placeList;
- (NSArray<MWZMapwizeAnnotation*>*) addMarkersOnPlaceList:(MWZPlaceList*) placeList image:(UIImage*) image;
- (void) removeMarker:(MWZMapwizeAnnotation*) marker;
- (void) removeMarkers;

Promoting places

The title of places are displayed based on the zoom level. The objective is to display as many as possible without having collisions. By default, the order specified in Mapwize Studio is used to define which place is displayed first.

However, there are situations where you would like to change dynamically the order. For example, if the user clicks on a place and you display informations about it, you might want to make sure that the title of that place is displayed. Basically, you want to promote that place to the first position.

Promoting places make them come first on the rendering. Of course, the collision mechanism still apply so if 2 promoted places collide, then only the first in the promotion list comes first. Also, promoted places only show on their floor when their venue are visible.

- (NSArray<MWZPlace*>*) getPromotedPlacesForVenue:(MWZVenue*) venue;
- (void) setPromotedPlaces:(NSArray<MWZPlace*>*) places forVenue:(MWZVenue*) venue;
- (void) addPromotedPlace:(MWZPlace*) place;
- (void) addPromotedPlaces:(NSArray<MWZPlace*>*) places;
- (void) removePromotedPlace:(MWZPlace*) place;
- (void) removePromotedPlaces:(NSArray<MWZPlace*>*) places;
- (void) removePromotedPlacesForVenue:(MWZVenue*) venue;

Showing Direction

To display directions on the map, the first step is to compute them. For that, you'll need to use the API methods to get a MWZDirection object as described in the API section.

When you have a MWZDirection object, you can display it on the map using

- (void) setDirection:(MWZDirection*) direction;
- (void) setDirection:(MWZDirection *)direction  options:(MWZDirectionOptions*) options;

The first method will use defaut DirectionOptions.

Here is the list of options with their default value:

@property (nonatomic, strong) UIImage* endMarkerIcon;   // nil
@property (nonatomic, assign) BOOL displayEndMarker;    // YES
@property (nonatomic, assign) BOOL centerOnStart;       // YES
@property (nonatomic, assign) BOOL setToStartingFloor;  // YES

If endMarkerIcon is null and displayEndMarker is true, default Mapwize marker will be used.

Once a direction is set, you can retrieve it or remove it.

- (MWZDirection*) getDirection;
- (void) removeDirection;

Showing Navigation (Dynamic direction)

Navigation is used as direction is. The difference is that navigation show the evolution of the user on the direction line.

The navigation should be used only if the starting point of the direction is the user position and if you have a IndoorLocation system working in your venue.

In order to provide an understandable visualization, Mapwize recompute the IndoorLocation to put the user position dot at the most realistic position on the direction path. Below, you will read how to use this information to recompute direction if needed.

- (void) startNavigation:(MWZDirection*) direction options:(MWZDirectionOptions*) options navigationUpdateHandler:(NavigationInfoDidUpdate) navigationUpdateHandler;
- (void) stopNavigation;

NavigationInfoDidUpdate is a block that have 3 parameters :

Styling places

Do you want to show if a meeting room is available or not, or color the prefered shops of the user? Then dynamic styling of places is what you are looking for.

You can overwrite the style of any place at any time using the setStyle function. Set the style to nil to return to the default style.

- (void) setStyle:(MWZStyle*) style forPlace:(MWZPlace*) place;

The available attributes for the style are

Padding

When developing an app around a map, it is advised not to resize the map at every user interaction. However, it might be interesting to overlay elements on top of the map. Since MapwizePlugin is adding controls on the map like the user position control or the floor control, padding can be used to free the top and bottom of the map to make space for overlay.

- (void) setBottomPadding:(CGFloat) bottomPadding;
- (void) setTopPadding:(CGFloat) topPadding;
- (void) setBottomPadding:(CGFloat) bottomPadding animationDuration:(CGFloat) duration;
- (void) setTopPadding:(CGFloat) topPadding animationDuration:(CGFloat) duration;

API

A complete set of functions are available to query raw Mapwize objects.

Mapwize objects are cached as much as possible in the SDK to reduce network traffic. However, full offline support is not available yet, but will be in a near future.

Parameters

ApiFilter

@property NSString* venueId;
@property NSString* universeId;
@property NSNumber* isVisible;
@property NSString* organizationId;
@property NSString* alias;
@property NSString* name;
@property NSString* floor;
@property NSNumber* latitudeMin;
@property NSNumber* latitudeMax;
@property NSNumber* longitudeMin;
@property NSNumber* longitudeMax;

SearchParams

@property NSString* query;
@property NSString* venueId;
@property NSString* organizationId;
@property NSString* universeId;
@property NSArray<NSString*>* objectClass;

Requests

Access

Access can be granted at runtime by calling the MWZApi directly :
objective-c
+ (NSURLSessionDataTask*)getAccess:(NSString*) accessKey success:(void (^)(void)) success failure:(void (^)(NSError* error)) failure

Using this, you have to refresh the MapwizePlugin manually using
objective-c
- (void) refreshWithCompletionHandler:(void (^)(void)) handler;

You can also call the grantAccess method on the MapwizePlugin. This method handle the Api call and the refresh.
objective-c
- (void) grantAccess:(NSString*) accessKey success:(void (^)(void)) success failure:(void (^)(NSError* error)) failure

Venues

+ (NSURLSessionDataTask*)getVenueWithId:(NSString*) identifier success:(void (^)(MWZVenue* venue)) success failure:(void (^)(NSError* error)) failure;
+ (NSURLSessionDataTask*)getVenuesWithFilter:(MWZApiFilter*) filter success:(void (^)(NSArray<MWZVenue*>* venues))success failure:(void (^)(NSError *error))failure;
+ (NSURLSessionDataTask*)getVenueWithName:(NSString*) name success:(void (^)(MWZVenue* venue)) success failure:(void (^)(NSError* error)) failure;
+ (NSURLSessionDataTask*)getVenueWithAlias:(NSString*) alias success:(void (^)(MWZVenue* venue)) success failure:(void (^)(NSError* error)) failure;

Places

+ (NSURLSessionDataTask*)getPlaceWithId:(NSString*) identifier success:(void (^)(MWZPlace* place)) success failure:(void (^)(NSError* error)) failure;
+ (NSURLSessionDataTask*)getPlacesWithName:(NSString*) name venue:(MWZVenue*) venue success:(void (^)(MWZPlace* place)) success failure:(void (^)(NSError* error)) failure;
+ (NSURLSessionDataTask*)getPlacesWithAlias:(NSString*) alias venue:(MWZVenue*) venue success:(void (^)(MWZPlace* place)) success failure:(void (^)(NSError* error)) failure;
+ (NSURLSessionDataTask*)getPlacesWithFilter:(MWZApiFilter*) filter success:(void (^)(NSArray<MWZPlace*>* places)) success failure:(void (^)(NSError* error)) failure;

ConnectorPlaces

+ (NSURLSessionDataTask*)getConnectorPlaceWithId:(NSString*) identifier success:(void (^)(MWZConnectorPlace* connectorPlace)) success failure:(void (^)(NSError* error)) failure;
+ (NSURLSessionDataTask*)getConnectorPlacesWithName:(NSString*) name venue:(MWZVenue*) venue success:(void (^)(MWZConnectorPlace* connectorPlace)) success failure:(void (^)(NSError* error)) failure;
+ (NSURLSessionDataTask*)getConnectorPlacesWithAlias:(NSString*) alias venue:(MWZVenue*) venue success:(void (^)(MWZConnectorPlace* connectorPlace)) success failure:(void (^)(NSError* error)) failure;
+ (NSURLSessionDataTask*)getConnectorPlacesWithFilter:(MWZApiFilter*) filter success:(void (^)(NSArray<MWZConnectorPlace*>* connectors)) success failure:(void (^)(NSError* error)) failure;

PlaceLists

+ (NSURLSessionDataTask*)getPlaceListWithId:(NSString*) identifier success:(void (^)(MWZPlaceList* placeList)) success failure:(void (^)(NSError* error)) failure;
+ (NSURLSessionDataTask*)getPlaceListsWithName:(NSString*) name venue:(MWZVenue*) venue success:(void (^)(MWZPlaceList* placeList)) success failure:(void (^)(NSError* error)) failure;
+ (NSURLSessionDataTask*)getPlaceListsWithAlias:(NSString*) alias venue:(MWZVenue*) venue success:(void (^)(MWZPlaceList* placeList)) success failure:(void (^)(NSError* error)) failure;
+ (NSURLSessionDataTask*)getPlaceListsWithFilter:(MWZApiFilter*) filter success:(void (^)(NSArray<MWZPlaceList*>* placeLists)) success failure:(void (^)(NSError* error)) failure;

MainFrom & MainSearches

+ (NSURLSessionDataTask*)getMainFromsWithVenue:(MWZVenue*) venue success:(void (^)(NSArray<MWZPlace*>* places)) success failure:(void (^)(NSError* error)) failure;
+ (NSURLSessionDataTask*)getMainSearchesWithVenue:(MWZVenue*) venue success:(void (^)(NSArray<id<MWZObject>>* mainSearches)) success failure:(void (^)(NSError* error)) failure;

Layers

+ (NSURLSessionDataTask*)getLayerWithId:(NSString*) identifier success:(void (^)(MWZLayer* layer)) success failure:(void (^)(NSError* error)) failure;
+ (NSURLSessionDataTask*)getLayerWithName:(NSString*) name venue:(MWZVenue*) venue success:(void (^)(MWZLayer* layer)) success failure:(void (^)(NSError* error)) failure;
+ (NSURLSessionDataTask*)getLayerWithAlias:(NSString*) alias venue:(MWZVenue*) venue success:(void (^)(MWZLayer* layer)) success failure:(void (^)(NSError* error)) failure;
+ (NSURLSessionDataTask*)getLayersWithFilter:(MWZApiFilter*) filter success:(void (^)(NSArray<MWZLayer*>* layers)) success failure:(void (^)(NSError* error)) failure;

Universes

+ (NSURLSessionDataTask*)getUniverseWithId:(NSString*) identifier success:(void (^)(MWZUniverse* universe)) success failure:(void (^)(NSError* error)) failure;
+ (NSURLSessionDataTask*)getUniversesWithFilter:(MWZApiFilter*) filter success:(void (^)(NSArray<MWZUniverse*>* universes)) success failure:(void (^)(NSError* error)) failure;
+ (NSURLSessionDataTask*)getAccessibleUniversesWithVenue:(MWZVenue*) venue success:(void (^)(NSArray<MWZUniverse*>* universes)) success failure:(void (^)(NSError* error)) failure;

Directions

+ (NSURLSessionDataTask*)getDirectionWithFrom:(id<MWZDirectionPoint>) from to:(id<MWZDirectionPoint>) to isAccessible:(BOOL) isAccessible success:(void (^)(MWZDirection* direction))success failure:(void (^)(NSError* error)) failure;
+ (NSURLSessionDataTask*)getDirectionWithFrom:(id<MWZDirectionPoint>) from tos:(NSArray<id<MWZDirectionPoint>>*) tos isAccessible:(BOOL) isAccessible success:(void (^)(MWZDirection* direction))success failure:(void (^)(NSError* error)) failure;
+ (NSURLSessionDataTask*)getDirectionWithFrom:(id<MWZDirectionPoint>) from to:(id<MWZDirectionPoint>) to waypoints:(NSArray<id<MWZDirectionPoint>>*) waypoints isAccessible:(BOOL) isAccessible success:(void (^)(MWZDirection* direction))success failure:(void (^)(NSError* error)) failure;
+ (NSURLSessionDataTask*)getDirectionWithFrom:(id<MWZDirectionPoint>) from tos:(NSArray<id<MWZDirectionPoint>>*) tos waypoints:(NSArray<id<MWZDirectionPoint>>*) waypoints isAccessible:(BOOL) isAccessible success:(void (^)(MWZDirection* direction))success failure:(void (^)(NSError* error)) failure;

Distances

You can get a list of distance between a 'from' point to a list of 'to' points using :

+ (NSURLSessionDataTask*)getDistanceWithFrom:(id<MWZDirectionPoint>) from tos:(NSArray<id<MWZDirectionPoint>>*) tos isAccessible:(BOOL) isAccessible sortByTravelTime:(BOOL) sort success:(void (^)(MWZDistanceResponse* distance))success failure:(void (^)(NSError* error)) failure

Searches

+ (NSURLSessionDataTask*)searchWithParams:(MWZSearchParams*) searchParams success:(void (^)(NSArray<id<MWZObject>>* searchResponse)) success failure:(void (^)(NSError* error)) failure;

Evolution and support

The SDK is evolving quickly. If you wish you had an extra function or if you have any remark, don't hesitate to contact us. Updates can be pushed quickly for critical matters.

For any question, contact us at support@mapwize.io