NAV
swift kotlin java

Introduction


Welcome to Mappy.

We build geospatial platforms connecting venues to their guests. Our turnkey solutions (developer tools, including the Mappy API and Mappy SDK, for mobile apps), improve the guest experience using interactive and social custom maps. Our data analytics platform and operations dashboards are designed to inform data driven decisions.

Mappy SDKs provide an elegant and composable interface for mapping, geocoding, and routing.

The Mappy SDKs leverages the power of best in class technology, including ESRI using the ArcGIS Runtime SDKs as dependencies.

This Implementation Documentation describes how to use the latest version of the Mappy SDK for iOS and Android for ski resorts. Terms of Service for our developer tools, data analytics platform, and mapping services portal are found here.

We also have Technical Documentation for Swift and Technical Documentation for Kotlin which describes individual components of the SDK.

Register your App with Mappy

Your app needs to be registered with Mappy to use the Mappy SDK in your app. For a quote, please email info@beMappy.io. Mappy will provide customers an Id and Secret. Be sure to store them securely.

Prepare Development Requirements

iOS Development Requirements

If you have registered and received your Client ID and Secret Key, we have a quick-start sample you can download or clone in case you want to see some code examples in action before starting a new Xcode project.

Android Development Requirements

If you have registered and received your Client ID and Secret Key, we have a quick-start sample you can download or clone in case you want to see some code examples in action before starting a new Android project.

Add Mappy SDK to your App

iOS

Include Mappy in your project using either Swift Package Manager or CocoaPods as follows:

Use Swift Package Manager to include Mappy in your project

  1. Open your project in Xcode
  2. From the File menu choose Add Packages...
  3. Search for https://github.com/beMappy/Mappy-Swift
  4. Select Mappy-Swift
  5. Select Dependency Rule Up to Next Major Version
  6. Verify that the version range is from at least version 0.8.11
  7. Click Add Package
  8. Verify the Mappy SDK version: in the Project navigator under Package Dependencies, find Mappy and verify the grey version text is 0.8.4 or greater.

Use CocoaPods to include Mappy in your project

  1. Close your project
  2. From a command line go to your project's root directory i.e. cd <MappyProject>.
  3. Create a Podfile for your project with the command pod init.
  4. Edit your project's newly created Podfile file, and add pod 'Mappy' after the # Pods for ... line.
  5. Set the platform to iOS 14 by adding or uncommenting the line platform :ios, '14.0' at the top of the Podfile.
  6. Install Mappy into your project with the command pod install.
  7. During the install process the versions of the dependencies will be listed to the terminal, they will also be listed in the Podfile.lock file. Verify that the Mappy version is 0.8.3 or later.
  8. After the installation completes, open the new .xcworkspace file (not the ".xcodeproj" file), and verify that the Mappy dependancy is in the Pods project. It will be in Pods -> Pods -> Mappy.
  9. Later, to update to the latest Mappy SDK version available for the specified platform, from the project directory run the command pod update.

Android

maven

dependencyResolutionManagement {
    /*
    ...
    */
    repositories {
        /*
        ...
        */
        mavenCentral()
        maven {
            url 'https://esri.jfrog.io/artifactory/arcgis'
        }
    }
}

In the settings.gradle (Project Settings) file add the Jfrog and Maven Central to dependencyResolutionManagement { repositories {} } list.

dependencies

dependencies {
    implement("io.github.bemappy:mappy:0.10.14")
}

In the build.gradle (Module :app) file, for the project where you want the SDK installed, add the io.github.bemappy:mappy to dependencies {} list. You can find the latest version from here: Maven Central

Exclude Duplicated Packages

android {
    packagingOptions {
        resources.excludes.add("META-INF/INDEX.LIST")
        resources.excludes.add("META-INF/DEPENDENCIES")
        resources.excludes.add("META-INF/io.netty.versions.properties")
    }
}

In the build.gradle (Module :app) file, in android {} add these settings to exclude duplicated packages. Otherwise duplicated packages may prevent compilation.

Initialize Mappy in Your App

Before using Mappy in your app, it needs to be initialized.

iOS

To initialize use this code

import UIKit
import Mappy
import Combine

...

    let mappyUserIdKey: String = "mappyUserId"
    var mappyInitCancellable: AnyCancellable?
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // UserDefaults used for demonstration purpose only.
        mappyInitCancellable = MappyCore.initialize(
                                                    clientId: <#String#>,
                                                    secret: <#String#> /* ClientSecret */,
                                                    mappyUserId: UserDefaults.standard.string(forKey: self.mappyUserIdKey)
                                                    deferTracking: false, // true - if the application doesn't want to track the user's location. Note: User statistics and visual tracks depend on the tracking.
                                                    appTrackingInfo: <AppTrackingInfo> // App specific information tracked along with the user location.
                                                    )
        .sink(receiveCompletion: {
            if case .failure(let error) = $0 {
                // TODO: handle error
                print("initialize: error: \(error)")
            }
        }, receiveValue: { mappyMeta in
            // !!!: sometimes the Mappy SDK can successfully return a MappyMeta object even though there was an error. In that case the MappyMeta object will contain error data, and no error will be passed to the receiveCompletion block.
            guard let mappyUserId = mappyMeta.mappyUserId else {
                // TODO: handle error
                print("errorDescription: \(mappyMeta.error?.errorDescription ?? "")")
                return
            }
            UserDefaults.standard.setValue(mappyUserId, forKey: self.mappyUserIdKey)
            // Override point for customization after MappyCore Initialized.
        })

        // Override point for customization after application launch.

        return true
    }

Make sure to replace `ClientId` and `ClientSecret` with your credentials.

  1. Import Mappy, UIKit, and Combine modules in your UIApplicationDelegate.
  2. Configure a Mappy shared instance, by calling MappyCore.initialize typically in your app's initializer or app delegate's application(_:didFinishLaunchingWithOptions:) method:
  3. Make sure that you app stores the mappyUserId String on it's first launch, and passes that value to the initialize method on subsequent launches.

This function instantiates the Mappy SDK, so the SDK APIs can be used across the project. This static method requires a Client ID, Client Secret, an a returning user's Mappy User ID. In the first invocation the User ID is not required, and will be generated and passed to the app in the receiveValue: block.

It is the application's responsibility to store the Mappy User ID accros user sessions, and to use it to initialize Mappy in subsequent sessions.

The Mappy User ID is associated with the Client ID and Client Secret credentials, and cannot be used with another set of Client credentials. When switching between development credentials and production credentials care should be taken to use a Mappy User ID which is associated with the credentials used to initialize Mappy.

Android

Using Kotlin Coroutines

val mappy = Mappy.createInstance(applicationContext)
CoroutineScope(Dispatchers.IO).launch {
    val userId = mappy.initialize(clientId: String, clientSecret: String, mappyUserId: String?)
}

Using Callback

mappy.initialize(clientId: String, clientSecret: String, mappyUserId: String?, object: CompletionCallback<String> {
    override fun onSuccess(result: String) {
        //Initialization Success
    }

    override fun onError(throwable: Throwable) {
        //Initialization Error
    }
})
Mappy mappy = Mappy.createInstance(getApplicationContext());

mappy.initialize(String clientId, String clientSecret, @Nullable String mappyUserId, new CompletionCallback<String>() {
    @Override
    public void onSuccess(String result) {
        //Initialization Success
    }

    @Override
    public void onError(@NonNull Throwable throwable) {
        //Initialization Error
    }
});
  1. Import the Mappy module.
  2. Create a Mappy instance with Mappy.createInstance(context: Context). Be sure to pass a valid context.
  3. And initialize your Mappy instance with either:

This function helps to instantiate the Mappy SDK. Post initialization, SDK APIs can be used internally across the project. This method requires a Client Id, a secret and the user Id. The user Id is generated by Mappy. In the first invocation, you can pass nil as the User Id. The SDK will then generate a new user Id and send it back to the app in the completion block. It is the application's responsibility to store and pass that user Id across user sessions.

Venues

Get All Venues

let venuesService = VenuesService()
venuesService.getVenues()
  .receive(on: DispatchQueue.main)
  .sink(receiveCompletion: { completion in
      switch completion {
      case .finished: break
      case .failure(let error):
          print("error: \(error)")
      }
  }, receiveValue: { [weak self] venues in
        // Received venues
  })
  .store(in: &cancellables)

Using Kotlin Coroutines

val venueService = VenueService.createInstance(applicationContext)
CoroutineScope(Dispatchers.IO).launch {
    val venues = venueService.getVenues()
}

Using Callback

venueService.getVenues(object: CompletionCallback<List<Venue>> {
    override fun onSuccess(result: List<Venue>) {
      //Get Venues Successful
    }

    override fun onError(throwable: Throwable) {
      //Get Venues Error
    }
})
VenueService venueService = VenueService.createInstance(getApplicationContext());
venueService.getVenues(new CompletionCallback<List<Venue>>() {
    @Override
    public void onSuccess(List<Venue> result) {
        //Get Venues Successful
    }

    @Override
    public void onError(@NonNull Throwable throwable) {
        //Get Venues Error
    }
});

This function provides a list of venues associated with a particular Client Id. The data returned includes all details of available venues. You can get these IDs from a venue returned by VenueService().getVenues().

Get a Specific Venue

let venuesService = VenuesService()
venuesService.getVenue(venueId: <#String#> /* from the venueId property of a Venue */)
  .receive(on: DispatchQueue.main)
  .sink(receiveCompletion: { completion in
      switch completion {
      case .finished: break
      case .failure(let error):
          print("error: \(error)")
      }
  }, receiveValue: { [weak self] venue in
        // Received Venue
  })
  .store(in: &cancellables)

Using Kotlin Coroutines

val venueService = VenueService.createInstance(applicationContext);
CoroutineScope(Dispatchers.IO).launch {
    venueService.getVenue(<ID>)
}

Using Callback

venueService.getVenue(id: String, object: CompletionCallback<Venue> {
    override fun onSuccess(result: Venue) {
        //Get Venue Successful
    }

    override fun onError(throwable: Throwable) {
        //Get Venue Error
    }
})
VenueService venueService = VenueService.createInstance(getApplicationContext());
venueService.getVenue(String id, new CompletionCallback<Venue>() {
    @Override
    public void onSuccess(Venue venue) {
        //Get Venue Successful
    }

    @Override
    public void onError(@NonNull Throwable throwable) {
        //Get Venue Error
    }
});

This function helps to get the details for a specific venue using its identifier.

Parameters

Parameter DataType Description
VenueId String The ID of the venue to retrieve

User Statistics

Get User's Statistics

StatsService - GetUserStats

self.statsService = StatsService()
self.statsService.getUserStats(userId: <userId>, venueId: <venueId>, startTime: <startTime>, endTime: <endTime>)
  .receive(on: DispatchQueue.main)
  .sink(receiveCompletion: { completion in
      switch completion {
      case .finished: break
      case .failure(let error):
          print("error: \(error)")
      }
  }, receiveValue: { userStats in
        print("maximumAltitudeInMetres: \(userStats.maximumAltitudeInMetres)")
  })
  .store(in: &cancellables)

Using Kotlin Coroutines

val statsService = StatsService.createInstance(applicationContext);
CoroutineScope(Dispatchers.IO).launch {
    statsService.getUserStats()
}

Using Callback

statsService.getUserStats(object: CompletionCallback<Stats> {
    override fun onSuccess(result: Stats) {
        //Get Stats Successful
    }

    override fun onError(throwable: Throwable) {
        //Get Stats Error
    }
})
StatsService statsService = StatsService.createInstance(getApplicationContext());
statsService.getUserStats(new CompletionCallback<Stats>() {
    @Override
    public void onSuccess(Stats stats) {
        //Get Stats Successful
    }

    @Override
    public void onError(@NonNull Throwable throwable) {
        //Get Stats Error
    }
});

This function retrieves the statistics for a user. Optionally the results can be restricted to stats from specific venu, and within a specified date range.

The list of stats availible can be found in the technical documentation at iOS - UserStats, and Android - Stats

Parameters

Parameter DataType Description
userId String The ID of any particular venue in which the user Skied, or null to use the current user ID.
venueId String The ID of any particular venue in which the user Skied, or null to retrieve statistics for all venues.
startTime Long The starting date of the date range to retrieve statistics for, in Unix timestamp format. Defaults to null, indicating that there is no start date filter.
endTime Long The ending date of the date range to retrieve statistics for, in Unix timestamp format. Defaults to null, indicating that there is no end date filter.

Rendering

Map

Map - Load

map = Map(mapInfo: <#Venue#>)
map.load()
  .receive(on: DispatchQueue.main)
  .sink(receiveCompletion: { completion in
      switch completion {
      case .finished: break
      case .failure(let error):
          print("error: \(error)")
      }
  }, receiveValue: { [weak self, weak map, weak mapView] _ in
        // Map has been loaded
        if let map = map {
            self?.mapView?.map = map
        }
  })
  .store(in: &cancellables)
val map = Map(venue: Venue)

map.load(context: Context, object : CompletionCallback<Map>() {
    fun onSuccess(map: Map) {
        binding.mapView.setMap(map)
    }

    fun onError(throwable: Throwable) {
        // Error Handling
    }
})

Map - Using Jetpack Compose

// must execute load method successfully first on the map object
MapView(
    map = map,
    modifier = Modifier,
    mapController = MapController()
)
Map map = new Map(venue: Venue);
map.load(this, new CompletionCallback<Map>() {
    @Override
    public void onSuccess(@NonNull Map map) {
        binding.mapView.setMap(map);
    }

    @Override
    public void onError(@NonNull Throwable throwable) {
        // Error Handling
    }
});

A Map is a class that represents the 2D map. You need to inject all the required data associated with a venue to instantiate and load a Map.

Get Features

Map - Get Features

map.getFeatures()
  .receive(on: DispatchQueue.main)
  .sink(receiveCompletion: { completion in
      switch completion {
      case .finished: break
      case .failure(let error):
          print("error: \(error)")
      }
  }, receiveValue: { [weak self] features in
        // Received features
  })
  .store(in: &cancellables)

Get Features - Using Kotlin Coroutines

val map = Map(venue)
CoroutineScope(Dispatchers.IO).launch {
    val loadedMap = map.load()
    loadedMap.getFeatures()
}

Get Features - Using Callback

// must execute load method successfully first on the map object
loadedMap.getFeatures(object: CompletionCallback<List<Feature>> {
    override fun onSuccess(result: List<Feature>) {
        //Get Features Successful
    }

    override fun onError(throwable: Throwable) {
        //Get Features Error
    }
})
// must execute load method successfully first on the map object
loadedMap.getFeatures(new CompletionCallback<List<Feature>>() {
    @Override
    public void onSuccess(List<Feature> result) {
        //Get Features Successful
    }

    @Override
    public void onError(@NonNull Throwable throwable) {
        //Get Features Error
    }
});

Get all the available features in the map like Ski run, lift, buidling, amenity, bookmarks, etc.

Search features by keyword

Map - Search Features

map.getFeatures(text: <#String#> /* search text */)
  .receive(on: DispatchQueue.main)
  .sink(receiveCompletion: { completion in
      switch completion {
      case .finished: break
      case .failure(let error):
          print("error: \(error)")
      }
  }, receiveValue: { [weak self] features in
        // Received features
  })
  .store(in: &cancellables)

Search features - Using Kotlin Coroutines

val map = Map(venue)
CoroutineScope(Dispatchers.IO).launch {
    val loadedMap = map.load()
    loadedMap.getFeatures(keyword: String)
}

Search features - Using Callback

// must execute load method successfully first on the map object
loadedMap.getFeatures(keyword: String, object: CompletionCallback<List<Feature>> {
    override fun onSuccess(result: List<Feature>) {
        //Get Features Successful
    }

    override fun onError(throwable: Throwable) {
        //Get Features Error
    }
})
// must execute load method successfully first on the map object
loadedMap.getFeatures(String keyword, new CompletionCallback<List<Feature>>() {
    @Override
    public void onSuccess(List<Feature> result) {
        //Get Features Successful
    }

    @Override
    public void onError(@NonNull Throwable throwable) {
        //Get Features Error
    }
});

Get all the available features in the map like Ski run, lift, buidling, amenity, bookmarks, etc. that contains a given keyword.

Filter features by type

Map - Filter Features

map.getFeatures(category: .<#FeatureCategory#>(.any))
  .receive(on: DispatchQueue.main)
  .sink(receiveCompletion: { completion in
      switch completion {
      case .finished: break
      case .failure(let error):
          print("error: \(error)")
      }
  }, receiveValue: { [weak self] features in
        // Received features
  })
  .store(in: &cancellables)

Filter features - Using Kotlin Coroutines

val map = Map(venue)
CoroutineScope(Dispatchers.IO).launch {
    val loadedMap = map.load()
    loadedMap.getFeatures(classType: Class)
}

Filter features - Using Callback

// must execute load method successfully first on the map object
loadedMap.getFeatures(classType: Class, object: CompletionCallback<List<Feature>> {
    override fun onSuccess(result: List<Feature>) {
        //Get Features Successful
    }

    override fun onError(throwable: Throwable) {
        //Get Features Error
    }
})

/**
* Possible classTypes:
*   - SkiLift::class.java
*   - SkiSlope::class.java
*   - Building::class.java
*   - BuildingAmenity::class.java
*/
// must execute load method successfully first on the map object
loadedMap.getFeatures(Class classType, new CompletionCallback<List<Feature>>() {
    @Override
    public void onSuccess(List<Feature> result) {
        //Get Features Successful
    }

    @Override
    public void onError(@NonNull Throwable throwable) {
        //Get Features Error
    }
});

/**
* Possible classTypes:
*   - SkiLift.class
*   - SkiSlope.class
*   - Building.class
*   - BuildingAmenity.class
*/

Get all the available features in the map like Ski run, lift, buidling, amenity, bookmarks, etc. that matches a given type.

Map Features

  • Attribute Name: objectid
  • Data Type: ObjectID
  • Recommended Uses: Unique Identifier of feature
  • Attribute Name: name
  • Data Type: String
  • Length: 60
  • Recommended Uses: Search, list, badge name, selection, network routing, sort by alphabetical
  • Attribute Name: length
  • Data Type: Single
  • Recommended Uses: In the badge and selection
  • Attribute Name: trav_time
  • Data Type: single
  • Attribute Name: Length_feet
  • Data Type: integer
  • Attribute Name: pass_pos
  • Data Type: string
  • Length: 255
  • Recommended Uses: Stand / Sit
  • Attribute Name: type
  • Data Type: string
  • Length: 255
  • Recommended Uses: Notates the type of lift e.g. 4 person chairlift that signifies which graphic to show. Included in the list, badge, selection, network routing
  • Value List: lift_type
  • Coded Values: Magic Carpet, Tow Rope, 2 Chairlift, 3 Chair Lift, 4 Chair Lift, 5 Chair Lift, 6 Chair Lift, Gondola, Tram
  • Attribute Name: lift_number
  • Data Type: Small integer
  • Recommended Uses: search, list, badge name, in addition to a name the lifts have numbers, only used at some large resorts, not applicable for smaller resorts
  • Attribute Name: objectid
  • Data Type: ObjectID
  • Recommended Uses: Unique Identifier of feature
  • Attribute Name: name
  • Data Type: string
  • Length: 100
  • Recommended Uses: Search, badge name, selection, network routing, sort by alphabetical
  • Attribute Name: skill_level
  • Data Type: intiger
  • Recommended Uses: Notates the level of run that signifies which graphic to show per run e.g. Advanced
  • Value List: skill_level
  • Coded Values: 1 (Beginner), 2 (Intermediate), 3 (Advanced), 4 (Expert), 5 (Beginner Intermediate), 6 (Intermediate Advanced), 7 (Advanced Expert)
  • Attribute Name: slope
  • Data Type: Single
  • Recommended Uses: Search, badge name, selection
  • Attribute Name: avg_spd
  • Data Type: Single
  • Attribute Name: length
  • Data Type: single
  • Attribute Name: run_type
  • Data Type: string
  • Length: 255
  • Recommended Uses: Turn by turn navigation
  • Value List: run_type
  • Coded Values: Main Run, Connector, Walking Path, Nordic Trail, Hike to Terrain, Tunnel, Private Skiway, Catwalk
  • Attribute Name: trav_time
  • Data Type: single
  • Attribute Name: generalized_level
  • Data Type: intiger
  • Recommended Uses: Notates the level for user login and network routing (turn by turn directions by skill level)
  • Value List: skill_level_gen
  • Coded Values: 1 (Beginner), 2 (Intermediate), 3 (Advanced), 4 (Expert)
  • Attribute Name: complexity
  • Data Type: string
  • Length: 255
  • Value List: complexity
  • Coded Values: Glades, Moguls
  • Attribute Name: extreme_conditions
  • Data Type: string
  • Length: 255
  • Value List: y_n
  • Coded Values: Yes, No
  • Attribute Name: objectid
  • Data Type: ObjectID
  • Recommended Uses: Unique Identifier of feature
  • Attribute Name: objectid
  • Data Type: ObjectID
  • Recommended Uses: Unique Identifier of feature
  • Attribute Name: building_name
  • Data Type: string
  • Length: 255
  • Recommended Uses: In the badge name as a subtitle to the amenity in the list view and selection, amenities will be within a building and show the building name, or if it is an amenity like terrain park or kid zone (point features temporarily in this feature class) it will be 'On Mountain'
  • Attribute Name: amenity_class
  • Data Type: string
  • Length: 255
  • Recommended Uses: Search filter
  • Value List: amenity_class
  • Coded Values: Food and Beverage, Guest Services, Retail, Ski Features, Service, First Aid, Other, Accommodation, Transportation, Event, Label
  • Attribute Name: amenity_type
  • Data Type: string
  • Length: 255
  • Recommended Uses: Search filter
  • Value List: amenity_type
  • Coded Values: Bar, Casual Dining, Full Service Restaurant, Childcare, Information, Lockers, Other, Restroom, Ski School, Ski Valet, Ticket Office, Art Gallery, Bank, Grocery, Rental Shop, Repair Shop, Retail Store, Bowl, Kid Zone, Racing Zone, Slow Zone, Terrain Park, Tubing Area, Unspecified Ski Feature, ATM, Health and Wellness, Postal, Recreation, Construction, Corporate, Miscellaneous, Postal, Real Estate, Recreation, Medical Clinic, Ski Patrol, Elevator, Fountain, Ice Rink, Playground, Statue, Condo, Hotel, Bus Stop, Parking, Entertainment, Festival, Wellness, Meeting, Sporting Event, Base Area, Peak Elevation, Plaza
  • Attribute Name: name
  • Data Type: string
  • Length: 255
  • Recommended Uses: Name of the amenity on the list, search, badge name, selection, network routing, sort alphabetical
  • Attribute Name: phone
  • Data Type: string
  • Length: 255
  • Recommended Uses: In the badge name during selection
  • Attribute Name: website
  • Data Type: string
  • Length: 10000
  • Recommended Uses: In the badge name, selection
  • Attribute Name: foreign_id
  • Data Type: string
  • Length: 255
  • Attribute Name: foreign_source
  • Data Type: string
  • Length: 255
  • Attribute Name: unique_id
  • Data Type: string
  • Length: 255
  • Attribute Name: objectid
  • Data Type: ObjectID
  • Recommended Uses: Unique Identifier of feature
  • Attribute Name: name
  • Data Type: string
  • Length: 100
  • Recommended Uses: Search, badge name, selection, network routing, sort by alphabetical

MapView

MapView - Initialize

MapView()

MapView - Using XML UI

import io.bemappy.sdk.ui.MapView
MapView(context: Context)

MapView - Using Jetpack Compose

import io.bemappy.sdk.ui.compose.MapView
MapView(map: Map, modifier: Modifier, mapController: MapController)

// A recomended practice is to use Modifier.fillMaxSize() for the map view
import io.bemappy.sdk.ui.MapView;
new MapView(Context context)

MapView is an UI class that helps in rendering the 2D map. Once the map is loaded, add the map to MapView and then you can add the same to your UI hierarchy.

Rotate To North

MapView - Rotate To North

mapView.rotateToNorth()

Rotate - Using XML UI (with View Binding or Data Binding)

binding.mapView.rotateToNorth()

Rotate - Using Jetpack Compose

// must execute load method successfully first on the map object
val mapController = remember { MapController() }

MapView(
    map = loadedMap,
    modifier = Modifier,
    mapController = mapController
)

mapController.rotateToNorth()
binding.mapView.rotateToNorth();

Rotates the map to the default orientation.

Set Initial ViewPoint

MapView - Set Initial ViewPoint

mapView.setInitialViewpoint()

Initial ViewPoint - Using XML UI (with View Binding or Data Binding)

binding.mapView.setInitialViewpoint()

Initial ViewPoint - Using Jetpack Compose

mapController.setInitialViewpoint()
binding.mapView.setInitialViewpoint();

Restores the map to its initial state where the venue is entirely visible

Tap on a Feature

Tap on a Feature

mapView.event
    .receive(on: DispatchQueue.main)
    .sink(receiveValue: {
        switch $0 {
        case .onTap(let feature):
            // Handle the tap event
        default:
            break
        }
    })
    .store(in: &cancellables)

Tap Listener - Using XML UI (with View Binding or Data Binding)

binding.mapView.setTapListener(object: TapListener {
        override fun onTouch(feature: Feature?) {
             // handle tap
        }
    }
)

Tap Listener - Using Jetpack Compose

MapView(
    map = loadedMap,
    modifier = Modifier,
    mapController = mapController,
    onTap = { feature -> // handle tap }
)
binding.mapView.setTapListener((feature) -> {
    //handle tap
});

A callback that will be invoked with the feature the user taps on.

View Point Change

MapView - View Point Change

mapView.event
    .receive(on: DispatchQueue.main)
    .sink(receiveValue: {
        switch $0 {
        case .onViewpointChange:
            // Handle the event
        default:
            break
        }
    })
    .store(in: &cancellables)

View Point Change Listener - Using XML UI (with View Binding or Data Binding)

binding.mapView.setViewPointChangeListener(object: ViewpointChangeListener {
    override fun onChanged() {
        //handle viewpoint change
    }
})

View Point Change Listener - Using Jetpack Compose

MapView(
    map = loadedMap,
    modifier = Modifier.fillMaxSize(),
    mapController = mapController,
    onViewPointChanged = {
        //handle viewpoint change
    }
)
binding.mapView.setViewPointChangeListener(() -> {
    //handle viewpoint change
});

A callback that will be invoked every time when the user interacts with the Map except in the case of a tap.

Select a Feature

Select a Feature

mapView.select(feature: <#Feature#>)

Select a Feature - Using XML UI (with View Binding or Data Binding)

binding.mapView.selectFeature(feature: Feature)

Select a Feature - Using Jetpack Compose

MapView(
    map = loadedMap,
    modifier = Modifier,
    mapController = mapController
)

mapController.selectFeature(feature: Feature)

Select a Feature

binding.mapView.selectFeature(Feature feature)

A function that highlights and zooms to the feature provided

Unselect a Feature

Unselect a Feature

mapView.select(feature: nil)

Unselect a Feature - Using XML UI (with View Binding or Data Binding)

binding.mapView.unselectFeature()

Unselect a Feature - Using Jetpack Compose

MapView(
    map = loadedMap,
    modifier = Modifier.fillMaxSize(),
    mapController = mapController
)

mapController.unselectFeature()

Unselect a Feature

binding.mapView.unselectFeature()

A function that removes the highlight from the selected feature

Display User Pins

MapView - Display User Pins

mapView.displayUserPins(using: <#[UserPinLocation]#>)

MapView - Display User Pins - Using Jetpack Compose

MapView(
    map = loadedMap,
    modifier = Modifier.fillMaxSize(),
    mapController = mapController
)

mapController.displayUserPin(userLocations: List<UserLocation>)

MapView - Display User Pins - Using View Binding

binding.mapView.displayUserPin(userLocations: List<UserLocation>)

MapView - Display User Pins - Using View Binding

binding.mapView.displayUserPin(<UserLocation>, <String>, <BitmapDrawable>)

A function that plots the pins on the map.

Remove User Pins

MapView - Remove User Pins

mapView.displayUserPins(using: [])

MapView - Remove User Pins - Using Jetpack Compose

MapView(
    map = loadedMap,
    modifier = Modifier.fillMaxSize(),
    mapController = mapController
)

mapController.removeUserPins()

MapView - Remove User Pins - Using View Binding

binding.mapView.removeUserPins()

MapView - Remove User Pins - Using View Binding

binding.mapView.removeUserPins()

To remove the pins from the map.

Tracks

Render User's Tracks

MapView - Render user's tracks

// Current day range
let startTime = Calendar.current.startOfDay(for: Date()).timeIntervalSince1970
let endTime = Date(timeIntervalSince1970: Calendar.current.startOfDay(for: Date()).timeIntervalSince1970 + (86400 - 1)).timeIntervalSince1970 // 86400 -> 1 day in seconds
self.mapView.renderUserTrack(startTime: startTime, endTime: endTime)
  .receive(on: DispatchQueue.main)
  .sink(receiveCompletion: { completion in
      switch completion {
      case .finished: break
      case .failure(let error):
          print("error: \(error)")
      }
  }, receiveValue: { status in
        print("status: \(status)")
  })
  .store(in: &cancellables)

MapView - Render user's tracks - Using Jetpack Compose

MapView(
    map = loadedMap,
    modifier = Modifier.fillMaxSize(),
    mapController = mapController
)

// Start time
val beg = Calendar.getInstance()
beg.timeInMillis = 0

// End time
val end = Calendar.getInstance()

// without callback and optional parameters
mapController.renderUserTracks(
    startTimeMilli = beg.timeInMillis,
    endTimeMilli = end.timeInMillis,
)

// using callback
mapController.renderUserTracks(
        userId = "optional_user_id",
        venueId = "optional_venue_id",
        startTimeMilli = beg.timeInMillis,
        endTimeMilli = end.timeInMillis,
        completionCallback = object : CompletionCallback<Boolean> {
            override fun onSuccess(result: Boolean) {
                if (result) {
                    // Track rendered
                } else {
                    // No track available to render for the provided time duration
                }
            }

            override fun onError(throwable: Throwable) {
                // Error while getting user's tracks
            }
        }
    )

MapView - Render user's tracks - Using View Binding

binding.mapView.renderUserTracks(
    startTimeMilli = beg.timeInMillis,
    endTimeMilli = end.timeInMillis,
)

MapView - Render user's tracks - Using View Binding

Calendar beg = Calendar.getInstance();
beg.setTimeInMillis(0);

Calendar end = Calendar.getInstance();

mapController.renderUserTracks(null,
    null,
    beg.getTimeInMillis(),
    end.getTimeInMillis(),
    new CompletionCallback<Boolean>() {
        @Override
        public void onSuccess(Boolean result) {
            if (result) {
                // Track rendered
            } else {
                // No track available to render for the provided time duration
            }
        }

        @Override
        public void onError(@NonNull Throwable throwable) {
            // Error while getting user's tracks
        }
    });

Renders the user's tracks on the map.

Remove user's tracks

MapView - Remove user's tracks

self.mapView.removeUserTrack()

MapView - Remove user's tracks - Using Jetpack Compose

MapView(
    map = loadedMap,
    modifier = Modifier.fillMaxSize(),
    mapController = mapController
)

// without callback
mapController.removeUserTrack()

// using callback
mapController.removeUserTrack(completionCallback = object : CompletionCallback<Boolean> {
    override fun onSuccess(result: Boolean) {
    // Track removed
    }

    override fun onError(throwable: Throwable) {
    // Error while removing user's tracks
    }
})

MapView - Remove user's tracks - Using View Binding

binding.mapView.removeUserTrack()

MapView - Render user's tracks - Using View Binding

mapController.removeUserTrack(
    new CompletionCallback<Boolean>() {
        @Override
        public void onSuccess(Boolean result) {
           // Track removed
        }

        @Override
        public void onError(@NonNull Throwable throwable) {
            // Error while removing user's tracks
        }
    });

Removes the user's tracks from the map.

Hide user's tracks

MapView - Hide user's tracks

self.mapView.hideUserTrack()

MapView - Hide user's tracks - Using Jetpack Compose

MapView(
    map = loadedMap,
    modifier = Modifier.fillMaxSize(),
    mapController = mapController
)

mapController.hideUserTracks()

MapView - Hide user's tracks - Using View Binding

binding.mapView.hideUserTracks()

MapView - Hide user's tracks - Using View Binding

binding.mapView.hideUserTracks()

Make the user's tracks on the map invisible if already rendered.

To check user's tracks rendering status

MapView - Track's Redering Status

self.mapView.isUserTrackRendered

MapView - Check if user's tracks has been rendered - Using Jetpack Compose

MapView(
    map = loadedMap,
    modifier = Modifier.fillMaxSize(),
    mapController = mapController
)

mapController.isUserTrackRendered()

MapView - Check if user's tracks has been rendered - Using View Binding

binding.mapView.isUserTrackRendered()

MapView - Check if user's tracks has been rendered - Using View Binding

binding.mapView.isUserTrackRendered()

Determines whether the user's tracks has been rendered on the map.

Check user's tracks visibility

MapView - Check if user's tracks is currently visible - Using Jetpack Compose

MapView(
    map = loadedMap,
    modifier = Modifier.fillMaxSize(),
    mapController = mapController
)

mapController.isUserTrackVisible()

MapView - Check if user's tracks is currently visible - Using View Binding

binding.mapView.isUserTrackVisible()

MapView - Check if user's tracks is currently visible - Using View Binding

binding.mapView.isUserTrackVisible()

Determines whether the user's tracks is currently visible on the map.

Scene

Scene - Load

scene = Scene(sceneInfo: <#Venue#>)
scene.load()
  .receive(on: DispatchQueue.main)
  .sink(receiveCompletion: { completion in
      switch completion {
      case .finished: break
      case .failure(let error):
          print("error: \(error)")
      }
  }, receiveValue: { [weak self, weak scene, weak sceneView] _ in
        // Scene has been loaded
        if let scene = scene {
            self?.sceneView?.scene = scene
        }
  })
  .store(in: &cancellables)

Scene - Using XML UI (with View Binding or Data Binding)

val scene = Scene(venue: Venue)
scene.load(context: Context, object : CompletionCallback<Scene>() {
    fun onSuccess(scene: Scene) {
        binding.sceneView.setScene(scene)
    }

    fun onError(throwable: Throwable) {
        // Error Handling
    }
})

Scene - Using Jetpack Compose

// must execute load method successfully first on the scene object
SceneView(
    scene = scene,
    modifier = Modifier,
    sceneController = SceneController()
)
Scene scene = new Scene(Venue venue);
scene.load(Context context, new CompletionCallback<Scene>() {
    @Override
    public void onSuccess(@NonNull Scene scene) {
        binding.sceneView.setScene(scene);
    }

    @Override
    public void onError(@NonNull Throwable throwable) {
        // Error Handling
    }
});

A scene is a class that represents the 3D map. You need to inject in all the required data associated with a venue to instantiate and load a Scene.

Get Features

Scene - Get Features

scene.getFeatures()
  .receive(on: DispatchQueue.main)
  .sink(receiveCompletion: { completion in
      switch completion {
      case .finished: break
      case .failure(let error):
          print("error: \(error)")
      }
  }, receiveValue: { [weak self] features in
        // Received features
  })
  .store(in: &cancellables)

Get Features - Using Kotlin Coroutines

val scene = Scene(venue: Venue)
CoroutineScope(Dispatchers.IO).launch {
    val loadedScene = scene.load()
    loadedScene.getFeatures()
}

Get Features - Using Callback

// must execute load method successfully first on the scene object
loadedScene.getFeatures(object: CompletionCallback<List<Feature>> {
    override fun onSuccess(result: List<Feature>) {
        //Get Features Successful
    }

    override fun onError(throwable: Throwable) {
        //Get Features Error
    }
})
// must execute load method successfully first on the map object
loadedScene.getFeatures(new CompletionCallback<List<Feature>>() {
    @Override
    public void onSuccess(List<Feature> result) {
        //Get Features Successful
    }

    @Override
    public void onError(@NonNull Throwable throwable) {
        //Get Features Error
    }
});

Get all the available features in the scene like Ski run, lift, buidling, amenity, bookmarks, etc.

Search features by keyword

Scene - Search Features

scene.getFeatures(text: <#String#>)
  .receive(on: DispatchQueue.main)
  .sink(receiveCompletion: { completion in
      switch completion {
      case .finished: break
      case .failure(let error):
          print("error: \(error)")
      }
  }, receiveValue: { [weak self] features in
        // Received features
  })
  .store(in: &cancellables)

Search features - Using Kotlin Coroutines

val scene = Scene(venue: Venue)
CoroutineScope(Dispatchers.IO).launch {
    val loadedScene = scene.load()
    loadedScene.getFeatures(keyword: String)
}

Search features - Using Callback

// must execute load method successfully first on the scene object
loadedScene.getFeatures(keyword: String, object: CompletionCallback<List<Feature>> {
    override fun onSuccess(result: List<Feature>) {
        //Get Features Successful
    }

    override fun onError(throwable: Throwable) {
        //Get Features Error
    }
})
// must execute load method successfully first on the map object
loadedScene.getFeatures(String keyword, new CompletionCallback<List<Feature>>() {
    @Override
    public void onSuccess(List<Feature> result) {
        //Get Features Successful
    }

    @Override
    public void onError(@NonNull Throwable throwable) {
        //Get Features Error
    }
});

Get all the available features in the scene like Ski run, lift, buidling, amenity, bookmarks, etc. that contains a given keyword.

Filter features by type

Scene - Filter Features

scene.getFeatures(category: <#FeatureCategory#>)
  .receive(on: DispatchQueue.main)
  .sink(receiveCompletion: { completion in
      switch completion {
      case .finished: break
      case .failure(let error):
          print("error: \(error)")
      }
  }, receiveValue: { [weak self] features in
        // Received features
  })
  .store(in: &cancellables)

Filter features - Using Kotlin Coroutines

val scene = Scene(venue: Venue)
CoroutineScope(Dispatchers.IO).launch {
    val loadedScene = scene.load()
    loadedScene.getFeatures(classType: Class)
}

Filter features - Using Callback

// must execute load method successfully first on the map object
loadedScene.getFeatures(classType: Class, object: CompletionCallback<List<Feature>> {
    override fun onSuccess(result: List<Feature>) {
        //Get Features Successful
    }

    override fun onError(throwable: Throwable) {
        //Get Features Error
    }
})

/**
* Possible classTypes:
*   - SkiLift::class.java
*   - SkiSlope::class.java
*   - Building::class.java
*   - BuildingAmenity::class.java
*/
// must execute load method successfully first on the map object
loadedScene.getFeatures(Class classType, new CompletionCallback<List<Feature>>() {
    @Override
    public void onSuccess(List<Feature> result) {
        //Get Features Successful
    }

    @Override
    public void onError(@NonNull Throwable throwable) {
        //Get Features Error
    }
});

/**
* Possible classTypes:
*   - SkiLift.class
*   - SkiSlope.class
*   - Building.class
*   - BuildingAmenity.class
*/

Get all the available features in the scene like Ski run, lift, buidling, amenity, bookmarks, etc. that matches a given type.

SceneView

SceneView - Initialize

SceneView()

SceneView - Using XML UI

import io.bemappy.sdk.ui.SceneView
SceneView(context: Context)

SceneView - Using Jetpack Compose

val sceneController = remember { SceneController() }

SceneView(
    scene = loadedScene,
    modifier = Modifier,
    sceneController = sceneController
)
import io.bemappy.sdk.ui.SceneView;
new SceneView(Context context)

SceneView is an UI class that helps in rendering the 3D map. Once the scene is loaded, add the scene to SceneView and then you can add the same to your UI hierarchy.

Rotate To North

SceneView - Rotate To North

sceneView.rotateToNorth()

Rotate To North - Using XML UI (with View Binding or Data Binding)

binding.sceneView.rotateToNorth()

Rotate To North - Using Jetpack Compose

// must execute load method successfully first on the map object
val sceneController = remember { SceneController() }

SceneView(
    scene = loadedScene,
    modifier = Modifier,
    sceneController = sceneController
)

sceneController.rotateToNorth()
binding.sceneView.rotateToNorth();

Rotates the scene to the default orientation

Set Initial ViewPoint

SceneView - Set Initial ViewPoint

sceneView.setInitialViewpoint()

Set Initial ViewPoint - Using XML UI (with View Binding or Data Binding)

binding.sceneView.setInitialViewpoint()

Set Initial ViewPoint - Using Jetpack Compose

sceneController.setInitialViewpoint()
binding.sceneView.setInitialViewpoint();

Restores the scene to its initial state where the venue is entirely visible.

Tap on a Feature

Tap on a Feature

sceneView.event
    .receive(on: DispatchQueue.main)
    .sink(receiveValue: {
        switch $0 {
        case .onTap(let feature):
            // Handle the tap event
        default:
            break
        }
    })
    .store(in: &cancellables)

Tap Listener - Using XML UI (with View Binding or Data Binding)

binding.sceneView.setTapListener(object: TapListener {
        override fun onTouch(feature: Feature?) {
             // handle tap
        }
    }
)

Tap Listener - Using Jetpack Compose

SceneView(
    scene = loadedScene,
    modifier = Modifier,
    sceneController = sceneController,
    onTap = { feature -> // handle tap }
)
binding.sceneView.setTapListener((feature) -> {
    //handle tap
});

A callback that will be invoked with the feature the user taps on.

View Point Change

SceneView - View Point Change

sceneView.event
    .receive(on: DispatchQueue.main)
    .sink(receiveValue: {
        switch $0 {
        case .onViewpointChange:
            // Handle the event
        default:
            break
        }
    })
    .store(in: &cancellables)

View Point Change Listener - Using XML UI (with View Binding or Data Binding)

binding.sceneView.setViewPointChangeListener(object: ViewpointChangeListener {
    override fun onChanged() {
        //handle viewpoint change
    }
})

View Point Change Listener - Using Jetpack Compose

SceneView(
    scene = loadedScene,
    modifier = Modifier.fillMaxSize(),
    sceneController = sceneController,
    onViewPointChanged = {
        //handle viewpoint change
    }
)
// lambda example
binding.sceneView.setViewPointChangeListener(() -> {
    // handle viewpoint change
});

A callback that will be invoked every time when the user interacts with the Scene except in the case of a tap.

Select a Feature

Select a Feature

sceneView.select(feature: <#Feature?#>)

Select a Feature - Using XML UI (with View Binding or Data Binding)

binding.sceneView.selectFeature(feature: Feature)

Select a Feature - Using Jetpack Compose

SceneView(
    scene = loadedScene,
    modifier = Modifier,
    sceneController = sceneController
)

sceneController.selectFeature(feature: Feature)

Select a Feature - Using Jetpack Compose

binding.sceneView.selectFeature(Feature feature)

A function that highlights and zooms to the feature provided

Unselect a Feature

Unselect a Feature

sceneView.select(feature: nil)

Unselect a Feature - Using XML UI (with View Binding or Data Binding)

binding.sceneView.unselectFeature()

Unselect a Feature - Using Jetpack Compose

SceneView(
    scene = loadedScene,
    modifier = Modifier.fillMaxSize(),
    sceneController = sceneController
)

sceneController.unselectFeature()

Unselect a Feature

binding.sceneView.unselectFeature()

A function that removes the highlight from the selected feature

Display User Pins

SceneView - Display User Pins

sceneView.displayUserPins(using: <#[UserPinLocation]#>)

SceneView - Display User Pins - Using Jetpack Compose

SceneView(
    scene = loadedScene,
    modifier = Modifier.fillMaxSize(),
    sceneController = sceneController
)

sceneController.displayUserPin(userLocations: List<UserLocation>)

SceneView - Display User Pins - Using View Binding

binding.sceneView.displayUserPin(userLocations: List<UserLocation>)

SceneView - Display User Pins - Using View Binding

binding.sceneView.displayUserPin(userLocations: List<UserLocation>)

A function that plots the pins on the scene.

Remove User Pins

SceneView - Remove User Pins

sceneView.displayUserPins(using: [])

SceneView - Remove User Pins - Using Jetpack Compose

SceneView(
    scene = loadedScene,
    modifier = Modifier.fillMaxSize(),
    sceneController = sceneController
)

sceneController.removeUserPins()

SceneView - Remove User Pins - Using View Binding

binding.sceneView.removeUserPins()

SceneView - Remove User Pins - Using View Binding

binding.sceneView.removeUserPins()

To remove the pins from the scene.

Tracks

Render user's tracks

SceneView - Render user's tracks

// Current day range
let startTime = Calendar.current.startOfDay(for: Date()).timeIntervalSince1970
let endTime = Date(timeIntervalSince1970: Calendar.current.startOfDay(for: Date()).timeIntervalSince1970 + (86400 - 1)).timeIntervalSince1970 // 86400 -> 1 day in seconds
self.sceneView.renderUserTrack(startTime: startTime, endTime: endTime)
  .receive(on: DispatchQueue.main)
  .sink(receiveCompletion: { completion in
      switch completion {
      case .finished: break
      case .failure(let error):
          print("error: \(error)")
      }
  }, receiveValue: { status in
        print("status: \(status)")
  })
  .store(in: &cancellables)

SceneView - Render user's tracks - Using Jetpack Compose

SceneView(
    scene = loadedScene,
    modifier = Modifier.fillMaxSize(),
    sceneController = sceneController
)

// Start time
val beg = Calendar.getInstance()
beg.timeInMillis = 0

// End time
val end = Calendar.getInstance()

// without callback and optional parameters
sceneController.renderUserTracks(
    startTimeMilli = beg.timeInMillis,
    endTimeMilli = end.timeInMillis,
)

// using callback
sceneController.renderUserTracks(
        userId = "optional_user_id",
        venueId = "optional_venue_id",
        startTimeMilli = beg.timeInMillis,
        endTimeMilli = end.timeInMillis,
        completionCallback = object : CompletionCallback<Boolean> {
            override fun onSuccess(result: Boolean) {
                if (result) {
                    // Track rendered
                } else {
                    // No tracks are available
                }
            }

            override fun onError(throwable: Throwable) {
                // Error while getting user's tracks
            }
        }
    )

SceneView - Render user's tracks - Using View Binding

binding.sceneView.renderUserTracks(
    startTimeMilli = beg.timeInMillis,
    endTimeMilli = end.timeInMillis,
)

SceneView - Render user's tracks - Using View Binding

Calendar beg = Calendar.getInstance();
beg.setTimeInMillis(0);

Calendar end = Calendar.getInstance();

sceneController.renderUserTracks(null,
    null,
    beg.getTimeInMillis(),
    end.getTimeInMillis(),
    new CompletionCallback<Boolean>() {
        @Override
        public void onSuccess(Boolean result) {
            if (result) {
                // Track rendered
            } else {
                // No track available to render for the provided time duration
            }
        }

        @Override
        public void onError(@NonNull Throwable throwable) {
            // Error while getting user's tracks
        }
    });

Renders the user's tracks on the scene.

Remove user's tracks

SceneView - Remove user's tracks - Using Jetpack Compose

SceneView - Remove user's tracks

self.sceneView.removeUserTrack()
SceneView(
    scene = loadedScene,
    modifier = Modifier.fillMaxSize(),
    sceneController = sceneController
)

// without callback
sceneController.removeUserTrack()

// using callback
sceneController.removeUserTrack(completionCallback = object : CompletionCallback<Boolean> {
    override fun onSuccess(result: Boolean) {
    // Track removed
    }

    override fun onError(throwable: Throwable) {
    // Error while removing user's tracks
    }
})

SceneView - Remove user's tracks - Using View Binding

binding.sceneView.removeUserTrack()

SceneView - Render user's tracks - Using View Binding

sceneController.removeUserTrack(
    new CompletionCallback<Boolean>() {
        @Override
        public void onSuccess(Boolean result) {
           // Track removed
        }

        @Override
        public void onError(@NonNull Throwable throwable) {
            // Error while removing user's tracks
        }
    });

Removes the user's tracks from the scene.

Hide user's tracks

SceneView - Hide user's tracks

self.sceneView.hideUserTrack()

SceneView - Hide user's tracks - Using Jetpack Compose

SceneView(
    scene = loadedScene,
    modifier = Modifier.fillMaxSize(),
    sceneController = sceneController
)

sceneController.hideUserTracks()

SceneView - Hide user's tracks - Using View Binding

binding.sceneView.hideUserTracks()

SceneView - Hide user's tracks - Using View Binding

binding.sceneView.hideUserTracks()

Make the user's tracks on the scene invisible if already rendered.

To check user's tracks rendering status

SceneView - Track's Redering Status

self.sceneView.isUserTrackRendered

SceneView - Check if user's tracks have been rendered - Using Jetpack Compose

SceneView(
    scene = loadedScene,
    modifier = Modifier.fillMaxSize(),
    sceneController = sceneController
)

sceneController.isUserTrackRendered()

SceneView - Check if user's tracks have been rendered - Using View Binding

binding.sceneView.isUserTrackRendered()

SceneView - Check if user's tracks have been rendered - Using View Binding

binding.sceneView.isUserTrackRendered()

Determines whether the user's tracks have been rendered on the scene.

Check user's tracks visibility

SceneView - Check if user's tracks are currently visible - Using Jetpack Compose

SceneView(
    scene = loadedScene,
    modifier = Modifier.fillMaxSize(),
    sceneController = sceneController
)

sceneController.isUserTrackVisible()

SceneView - Check if user's tracks are currently visible - Using View Binding

binding.sceneView.isUserTrackVisible()

SceneView - Check if user's tracks are currently visible - Using View Binding

binding.sceneView.isUserTrackVisible()

Determines whether the user's tracks are currently visible on the scene.

Location Tracking

Start location tracking

Location Tracking - Start

TrackingService.current.resume(appTrackingInfo: <AppTrackingInfo>)
val trackingService: TrackingService.createInstance(applicationContext)

// Using Kotlin Coroutines
CoroutineScope(Dispatchers.IO).launch {
    trackingService.resume(appTrackingInfo: AppTrackingInfo)
}
TrackingService trackingService = TrackingService.createInstance(applicationContext);

trackingService.resume(AppTrackingInfo appTrackingInfo);

To start/resume the user's location tracking. NOTE: By default the Tracking service starts tracking when the SDK is initialized. This invocation would be useful when we want to toggle the tracking process.

Suspend tracking

Location Tracking - Stop

TrackingService.current.suspend()
val trackingService: TrackingService.createInstance(applicationContext)

trackingService.suspend()
TrackingService trackingService = TrackingService.createInstance(applicationContext);

trackingService.suspend();

To suspend the user's location tracking.

Update tracking payload

Location Tracking - Update App Info

TrackingService.current.appTrackingInfo = <AppTrackingInfo>
val trackingService: TrackingService.createInstance(applicationContext)

// Using Kotlin Coroutines
CoroutineScope(Dispatchers.IO).launch {
    trackingService.changeTrackingPayload(appTrackingInfo: AppTrackingInfo)
}
TrackingService trackingService = TrackingService.createInstance(applicationContext);

trackingService.changeTrackingPayload(AppTrackingInfo appTrackingInfo);

To update the current tracking payload. It is necessary that the application updates the tracking service with necessary app specific information at least once. Please refer to the code snippets on how to update the current tracking payload.

To check tracking status

Location Tracking - Status

TrackingService.current.isTracking
val trackingService: TrackingService.createInstance(applicationContext)

trackingService.isTracking()
TrackingService trackingService = TrackingService.createInstance(applicationContext);

trackingService.isTracking();

Check if tracking is currently active. Returns true if tracking is active, false otherwise.

Location Sharing

Group CRUD

The GroupsService helps in creating, reading, updating and deleting groups. It can also add or remove users to and from the groups. It is mandatory to initialize the SDK before creating the instance and invoking the functions of this service.

Create a Group

Group CRUD - Create Group

groupsService.createGroup(name: <#String#>)
    .sink(receiveCompletion: {
        if case .failure(let error) = $0 {
            // Error
        }
    }, receiveValue: { [weak self] in
        // $0 - Group Data
    })
    .store(in: &cancellables)

Group CRUD - Create Group

val groupService = GroupService.createInstance(applicationContext)

//Using Kotlin Coroutines
CoroutineScope(Dispatchers.IO).launch {
    val group = groupService.createGroup(groupName: String, userRole: String)
}

//using Callback
groupService.createGroup(groupName: String, userRole: String, object : CompletionCallback<Group> {
    override fun onError(throwable: Throwable) {
        //On Error
    }

    override fun onSuccess(result: Group) {
        //On Success
    }
})

Group CRUD - Create Group

GroupService groupService = GroupService.createInstance(getApplicationContext());

groupService.createGroup(String groupName, String userRole, new CompletionCallback<List<Group>>() {
    @Override
    public void onSuccess(Group result) {
        //Create group Successful
    }

    @Override
    public void onError(@NonNull Throwable throwable) {
        //Create group Error
    }
});

To create a group.

Fetch Groups

Group CRUD - Fetch Groups

groupsService.fetchGroups()
    .sink(receiveCompletion: {
        if case .failure(let error) = $0 {
            // Error
        }
    }, receiveValue: { [weak self] in
        // $0 - Groups
    })
    .store(in: &cancellables)

Group CRUD - Fetch Groups

val groupService = GroupService.createInstance(applicationContext)

//Using Kotlin Coroutines
CoroutineScope(Dispatchers.IO).launch {
    val groups = groupService.getGroups()
}

//Using callback
groupService.getGroups(object : CompletionCallback<List<Group>> {
    override fun onError(throwable: Throwable) {
        //On Error
    }

    override fun onSuccess(result: List<Group>) {
        //On Success
    }
})

Group CRUD - Fetch Groups

GroupService groupService = GroupService.createInstance(getApplicationContext());

groupService.getGroups(new CompletionCallback<List<Group>>() {
    @Override
    public void onSuccess(List<Group> result) {
        //Get groups Successful
    }

    @Override
    public void onError(@NonNull Throwable throwable) {
        //Get groups Error
    }
});

To fetch all the user groups.

Fetch Group Info

Group CRUD - Fetch Groups Info

groupsService.fetchGroupInfo(groupId: <#String#>)
    .sink(receiveCompletion: {
        if case .failure(let error) = $0 {
            // Error
        }
    }, receiveValue: { [weak self] in
        // $0 - Group Data
    })
    .store(in: &cancellables)

Group CRUD - Fetch Groups Info

val groupService = GroupService.createInstance(applicationContext)

//Using Kotlin Coroutines
CoroutineScope(Dispatchers.IO).launch {
    val group = groupService.getGroup(groupId: String)
}

//Using callback
groupService.getGroup(groupId: String, object: CompletionCallback<Group> {
    override fun onError(throwable: Throwable) {
        TODO("Not yet implemented")
    }

    override fun onSuccess(result: Group) {
        TODO("Not yet implemented")
    }
})

Group CRUD - Fetch Groups Info

GroupService groupService = GroupService.createInstance(getApplicationContext());

groupService.getGroup(String groupId, new CompletionCallback<List<Group>>() {
    @Override
    public void onSuccess(Group result) {
        //Get group Successful
    }

    @Override
    public void onError(@NonNull Throwable throwable) {
        //Get group Error
    }
});

To fetch the groups data using its identifier.

Update a Group

Group CRUD - Update Group

groupsService.updateGroup(groupId: <#String#>, name: <#String#> /* new name */)
    .sink(receiveCompletion: {
        if case .failure(let error) = $0 {
            // Error
        }
    }, receiveValue: { [weak self] in
        // $0 - Group Data
    })
    .store(in: &cancellables)

Group CRUD - Update Group

val groupService = GroupService.createInstance(applicationContext)

//Using Kotlin Coroutines
CoroutineScope(Dispatchers.IO).launch {
    val group = groupService.updateGroup(groupId: String, name: String)
}

//Using callback
groupService.updateGroup(groupId: String, name: String, object: CompletionCallback<Group> {
    override fun onSuccess(result: Group) {
        //On Success
    }

    override fun onError(throwable: Throwable) {
        //On Error
    }
})

Group CRUD - Update Group

GroupService groupService = GroupService.createInstance(getApplicationContext());

groupService.updateGroup(String groupId, String name, new CompletionCallback<List<Group>>() {
    @Override
    public void onSuccess(Group result) {
        //Update group Successful
    }

    @Override
    public void onError(@NonNull Throwable throwable) {
        //Update group Error
    }
});

To update a group.

Delete a Group

Group CRUD - Delete Group

groupsService.deleteGroup(groupId: <#String#>)
    .sink(receiveCompletion: {
        if case .failure(let error) = $0 {
            // Error
        }
    }, receiveValue: { [weak self]
        // $0 - Group Data
    })
    .store(in: &cancellables)

Group CRUD - Delete Group

val groupService = GroupService.createInstance(applicationContext)

//Using Kotlin Coroutines
CoroutineScope(Dispatchers.IO).launch {
    val group = groupService.deleteGroup(groupId: String)
}

//Using callback
groupService.deleteGroup(groupId: String, object: CompletionCallback<Group> {
    override fun onSuccess(result: Group) {
        //On Success
    }

    override fun onError(throwable: Throwable) {
        //On Error
    }
})

Group CRUD - Delete Group

GroupService groupService = GroupService.createInstance(getApplicationContext());
groupService.deleteGroup(String groupId, new CompletionCallback<List<Group>>() {
    @Override
    public void onSuccess(Group result) {
        //Delete group Successful
    }

    @Override
    public void onError(@NonNull Throwable throwable) {
        //Delete group Error
    }
});

To delete a group.

Add a user

Group CRUD - Add User

groupsService.addUser(userId: <#String#>, groupCode: <#String#>)
    .sink(receiveCompletion: {
        if case .failure(let error) = $0 {
            // Error
        }
    }, receiveValue: { [weak self] in
        // $0 - Group data
    })
    .store(in: &cancellables)

Group CRUD - Add User

val groupService = GroupService.createInstance(applicationContext)

//Using Kotlin Coroutines
CoroutineScope(Dispatchers.IO).launch {
    val group = groupService.addUserToGroup(groupCode: String, userId: String, userRole: String)
}

//Using callback
groupService.addUserToGroup(groupCode: String, userId: String, userRole: String, object: CompletionCallback<Group> {
    override fun onSuccess(result: Group) {
        //On Success
    }

    override fun onError(throwable: Throwable) {
        //On Error
    }
})

Group CRUD - Add User

GroupService groupService = GroupService.createInstance(getApplicationContext());
groupService.addUserToGroup(String groupCode, String userId, new CompletionCallback<List<Group>>() {
    @Override
    public void onSuccess(Group result) {
        //Add user to group Successful
    }

    @Override
    public void onError(@NonNull Throwable throwable) {
        //Add user to group Error
    }
});

To add a user.

The current limit is 50 users per group. Once a group reaches its users limit, the error message 'groupUserLimitExceeded' will be returned. Please contact support@bemappy.io to increase the limit for number of users per group.

Remove a user

Group CRUD - Remove User

groupsService.removeUser(userId: user.userId, groupId: groupID)
    .sink(receiveCompletion: {
        if case .failure(let error) = $0 {
            // Error
        }
    }, receiveValue: { [weak self] in
        // $0 - Group data
    })
    .store(in: &cancellables)

Group CRUD - Remove User

val groupService = GroupService.createInstance(applicationContext)

//Using Kotlin Coroutines
CoroutineScope(Dispatchers.IO).launch {
    val group = groupService.deleteUserInGroup(groupId: String, userId: String)
}

//Using callback
groupService.deleteUserInGroup(groupId: String, userId: String, object: CompletionCallback<Group> {
    override fun onSuccess(result: Group) {
        //On Success
    }

    override fun onError(throwable: Throwable) {
        //On Error
    }
})

Group CRUD - Remove User

GroupService groupService = GroupService.createInstance(getApplicationContext());
groupService.deleteUserInGroup(String groupId, String userId, new CompletionCallback<List<Group>>() {
    @Override
    public void onSuccess(Group result) {
        //Delete user in group Successful
    }

    @Override
    public void onError(@NonNull Throwable throwable) {
        //Delete user in group Error
    }
});

To remove a user

Group Location

Receive group members' locations and location updates, and share a user's location with group members

To use group location features an instance of GroupLocationService must be initialized and kept. There must only be one instance of GroupLocationService for the lifetime of the app.

On Event

Group Location - On Event

groupLocationService.onEvent
    .sink(receiveCompletion: {
        if case .failure(let error) = $0 {
            // handle error
            print(error.localizedDescription)
        }
    }, receiveValue: { [weak self] groupEvent in
        switch groupEvent {
        case .userLocation(let userLocation) :
            print(userLocation);
        case .userAddedToGroup(groupId: let groupId, userId: let userId)
            print("\(groupId) \(userId)")
        case .userRemovedFromGroup(groupId: let groupId, userId: let userId)
            print("\(groupId) \(userId)")
        }
    })
    .store(in: &cancellables)

Group Location - On Event

val groupLocationService = GroupLocationService.createInstance(context)

groupLocationService.onEvent(object : EventCallback<GroupEvent> {
    override fun onFailure(throwable: Throwable) {
        //On Failure
    }

    override fun onMessage(event: GroupEvent) {
        //On Message
    }
})

Group Location - On Event

GroupLocationService groupLocationService = GroupLocationService.createInstance(context)

groupLocationService.onEvent(new EventCallback<GroupEvent>() {
    @Override
    public void onMessage(GroupEvent event) {
        //On Message
    }

    @Override
    public void onFailure(Throwable throwable) {
        //On Failure
    }
})

After inializing a GroupLocationService instance, the next thing that needs to be set up is an onEvent callback to receive group location service notifications. If listenToGroups has been run, the receiveValue block of onEvent will be called when any group member's location updates, or a user is added or removed from the group.

When a group member's location update is received it can be updated or added to an array/list of user pins and displayed in a mapView or displayed in a sceneView

Share User Location

Group Location - Share User Location

groupLocationService.shareUserLocation()
.sink(receiveCompletion: {
    if case .failure(let error) = $0 {
        // Error
    }
}, receiveValue: {
    // Share invocation Success
})
.store(in: &self.cancellables)

Group Location - Share User Location

val groupLocationService = GroupLocationService.createInstance(context)

groupLocationService.startSharingLocation(object: EventCallback<Location> {
    override fun onMessage(event: Location) {
        // Note: This is not guaranteed to be called immediately upon success. It is only called each time a user in this user's groups moves. E.g. if there are several users in the group, but none of them have moved since startSharingLocation() was called, this will not be called; or if there are no users in the group this will not be called; etc.
    }

    override fun onFailure(throwable: Throwable) {
        // Handle Failure
    }
})

Group Location - Share User Location

GroupLocationService groupLocationService = GroupLocationService.createInstance(context)

groupLocationService.startSharingLocation(new EventCallback<Void>() {
    @Override
    public void onMessage(Location event) {
        //On Message
    }

    @Override
    public void onFailure(Throwable throwable) {
        //On Failure
    }
})

To start sharing the user's location with the user's groups. Other users in the group will receive updates for this user's location in the receiveValue block of onEvent.

Stop Sharing User Location

Group Location - Stop Sharing

groupLocationService.stopSharingUserLocation()

Group Location - Stop Sharing

val groupLocationService = GroupLocationService.createInstance(context)

groupLocationService.stopSharingLocation()

Group Location - Stop Sharing

GroupLocationService groupLocationService = GroupLocationService.createInstance(context)

groupLocationService.stopSharingLocation()

To stop sharing the user's location with the user's groups.

Listen To Groups

Group Location - Listen To Groups

groupLocationService.listenToGroups()
.sink(receiveCompletion: {
    if case .failure(let error) = $0 {
        // Error
    }
}, receiveValue: {
    // Started listening
})
.store(in: &self.cancellables)

Group Location - Listen To Groups

//using kotlin flow
groupLocationService.startListeningToGroups().catch {
    //Location Flow Error
}.collectLatest { result: UserLocation ->
    //Location Flow Collect Result
}

//using callback
groupLocationService.startListeningToGroups(object: EventCallback<List<GroupUser>> {
    override fun onMessage(event: List<GroupUser>) {
        //On Message
    }

    override fun onFailure(throwable: Throwable) {
        //On Failure
    }
})

Group Location - Listen To Groups

groupLocationService.startListeningToGroups(new EventCallback<UserLocation>() {
    @Override
    public void onMessage(List<GroupUser> event) {
        //On Message
    }

    @Override
    public void onFailure(@NonNull Throwable throwable) {
        //On Failure
    }
})

To start listening to other users' locations who are part of the user's groups.

Stop Listening To Groups

Group Location - Stop Listening

groupLocationService.stopListeningToGroups()

Group Location - Stop Listening

groupLocationService.stopListeningToGroups()

Group Location - Stop Listening

groupLocationService.stopListeningToGroups()

To stop listening to other users' locations who are part of the user's groups.

Navigation

Map

Get Routes

Navigation - Map - Routes

map.getRoutes(from: <#Feature#>, to: <#Feature#>, skiLevel: <#SkiSlope.Level#>)
    .sink(receiveCompletion: {
        if case .failure(let error) = $0 {
            // Error
        }
    }, receiveValue: { [weak self] in
        // $0 - Routes
    }).store(in: &cancellables)

Navigation - Map - Routes

map.getRoutes(origin: Feature, destination: Feature, skiLevel: Level, callback: CompletionCallback<List<Route>>)

Navigation - Map - Routes

map.getRoutes(Feature origin, Feature destination, Level skiLevel, CompletionCallback<List<Route>> callback)

To find possible routes from one feature(origin) to another(destination) using the difficulty level.

MapView

Display a Route

Navigation - MapView - Display a Route

mapView.display(route: <#RouteResponse?#>)

Navigation - MapView - Display a Route - XML Based UI

mapView.display(route: Route)

Navigation - MapView - Display a Route - Jetpack Compose

mapController.display(route: Route)

Navigation - MapView - Display a Route

mapView.display(Route route)

To display the route on the map.

Start Navigation

Navigation - MapView - Start Navigation

let routeTracker = mapView.startNavigation(route: <#RouteResponse#>)

Navigation - MapView - Start Navigation - XML Based UI

val routeTracker = mapView.startNavigation(route: Route, destinationImage: BitmapDrawable, traversedRouteColor: Int, remainingRouteColor: Int)

Navigation - MapView - Start Navigation - Jetpack Compose

val routeTracker = mapController.startNavigation(route: Route, destinationImage: BitmapDrawable, traversedRouteColor: Int, remainingRouteColor: Int)

Note there is not RouteTracker object in android because everything is handled in the callback

Navigation - MapView - Start Navigation

RouteTracker routeTracker = mapView.startNavigation(Route route, BitmapDrawable destinationImage, Int traversedRouteColor, Int remainingRouteColor)

To start the turn by turn navigation using a route. This function returns a RouteTracker using which the application user interface can be updated during the navigation.

End Navigation

Navigation - MapView - End Navigation

mapView.stopNavigation()

Navigation - MapView - End Navigation - XML Based UI

mapView.stopNavigation()

Navigation - MapView - End Navigation - Jetpack Compose

mapController.stopNavigation()

Navigation - MapView - End Navigation

mapView.stopNavigation()

To end the navigation

Scene

Get Routes

Navigation - Scene - Routes

scene.getRoutes(from: <#Feature#>, to: <#Feature#>, skiLevel: <#SkiSlope.Level#>)
    .sink(receiveCompletion: {
        if case .failure(let error) = $0 {
            // Error
        }
    }, receiveValue: { [weak self] in
        // $0 - Routes
    }).store(in: &cancellables)

Navigation - Scene - Routes

scene.getRoutes(origin: Feature, destination: Feature, skiLevel: Level, callback: CompletionCallback<List<Route>>)

Navigation - Scene - Routes

scene.getRoutes(Feature origin, Feature destination, Level skiLevel, CompletionCallback<List<Route>> callback)

To find possible routes from one feature(origin) to another(destination) using the difficulty level.

SceneView

Display a Route

Navigation - SceneView - Display a Route

sceneView.display(route: <#RouteResponse?#>)

Navigation - SceneView - Display a Route - XML Based UI

sceneView.display(route: Route)

Navigation - SceneView - Display a Route - Jetpack Compose

sceneController.display(route: Route)

Navigation - SceneView - Display a Route

sceneView.display(Route route)

To display the route on the scene.

Start Navigation

Navigation - SceneView - Start Navigation

let routeTracker = sceneView.startNavigation(route: <#RouteResponse#>)

Navigation - SceneView - Start Navigation - XML Based UI

val routeTracker = sceneView.startNavigation(route: Route, destinationImage: BitmapDrawable, traversedRouteColor: Int, remainingRouteColor: Int)

Navigation - SceneView - Start Navigation - Jetpack Compose

val routeTracker = sceneController.startNavigation(route: Route, destinationImage: BitmapDrawable, traversedRouteColor: Int, remainingRouteColor: Int)

Navigation - SceneView - Start Navigation

sceneView.startNavigation(Route route, BitmapDrawable destinationImage, Int traversedRouteColor, Int remainingRouteColor)

Note there is not RouteTracker object in android because everything is handled in the callback

To start the turn by turn navigation using a route. This function returns a RouteTracker using which the application user interface can be updated during the navigation.

End Navigation

Navigation - SceneView - End Navigation

sceneView.stopNavigation()

Navigation - SceneView - End Navigation - XML Based UI

sceneView.stopNavigation()

Navigation - SceneView - End Navigation - Jetpack Compose

sceneController.stopNavigation()

Navigation - SceneView - End Navigation

sceneView.stopNavigation()

To end the navigation

RouteTracker

Uses a location to provide status and progress updates as a route is traversed (by a moving vehicle, for example).

RouteTracker can give the time or distance to the next maneuver, notify if the location is off-route, and regenerate a new route if necessary.

Routing Events

Navigation - RouteTracker - Routing Events

routeTracker.onEvent
    .sink(receiveValue: {
        switch $0 {
        case .onError(let error):
            // Error
        case .onTurnByTurnNavigationResponse(let response):
            // Navigation status update
        case .didReroute(let routeResponse):
            // New route
        case .didReachDestination:
            // Reached destination - Navigation Ended
        }
    }).store(in: &cancellables)

Navigation - RouteTracker - Routing Events

routeTracker.onEvent(object: NavigationCallback<NavigationTurn> {
    override fun onUpdate(result: NavigationTurn) {
        //on Update
    }

    override fun onError(throwable: Throwable) {
        //On Error
    }
})

Navigation - RouteTracker - Routing Events

routeTracker.onEvent(new NavigationCallback<NavigationTurn>() {
    @Override
    public void onError(@NonNull Throwable throwable) {
        //On Error
    }

    @Override
    public void onUpdate(NavigationTurn result) {
        //On Update
    }
})

To listen to the events related to the route that is being navigated.

Audio Navigation

Navigation - RouteTracker - Enable / Disable voice directions

routeTracker.voiceDirectionsActive = <true / false>

Navigation - RouteTracker - Enable / Disable voice directions

routeTracker.areVoiceDirectionsActive = <true / false>

Navigation - RouteTracker - Enable / Disable voice directions

routeTracker.areVoiceDirectionsActive = <true / false>

To toggle the audio navigation.

Mappy Assets