Integrate Indoor Maps

HERE Indoor Map provides functionality to load, show, and interact with private venues on a map. For more information on HERE Indoor Map, see the HERE Indoor Map Guide.

Note

If you are a venue owner and are interested in leveraging HERE Indoor Map with the HERE SDK, contact us at

venues.support@here.com.

An airport venue with a customized floor switcher.

Currently, the HERE SDK only supports private venues, therefore your venue data will only be shown in your app. By default, no venues are visible on the map. Each of your venues receives a unique venue ID that is tied to your HERE SDK credentials.

Initialize the VenueEngine

Before you can begin using the HERE Indoor Map API, the VenueEngine instance must be created and started. This can be done after a map initialization, however The best time to create the VenueEngine is after the map loads a scene:

private void loadMapScene() {
    // Completion handler when loading a map scene.
    private func onLoadScene(mapError: MapError?) {
        guard mapError == nil else {
            print("Error: Map scene not loaded, \(String(describing: mapError))")
            return
        }

        // Hide extruded buildings layer, so it will not overlaps with venues.
        mapView.mapScene.disableFeatures([MapFeatures.extrudedBuildings])

        // Create a venue engine object. Once the initialization is done, a completion handler
        // will be called.
        do {
            try venueEngine = VenueEngine { [weak self] in self?.onVenueEngineInit() }
        } catch {
            print("SDK Engine not instantiated: \(error)")
        }
    }
}

Once the VenueEngine has been initialized, a completion handler is called. From this point on, there is access to both the VenueService and the VenueMap. A VenueService is used to load venues, and a VenueMap controls the venues on the map. Inside the completion handler, all required delegates can be added, and then the VenueEngine must be started. The platform map catalog HRN must be set once the VenueEngine is started.

Note

Setting HRN is optional. If a user does not set HRN, default collection HRN is automatically selected. If a user want to use any other collection, respective HRN can be set. An error log is generated that indicates missing or invalid HRN values. For more information on how you can receive a valid HRN string for your project, see the HERE Indoor Map Guide.

private func onVenueEngineInit() {
    // Get VenueService and VenueMap objects.
    let venueMap = venueEngine.venueMap
    let venueService = venueEngine.venueService

    // Add needed delegates.
    venueService.addServiceDelegate(self)
    venueService.addVenueDelegate(self)
    venueMap.addVenueSelectionDelegate(self)

    // Start VenueEngine. Once authentication is done, the authentication completion handler
    // will be triggered. Afterwards VenueEngine will start VenueService. Once VenueService
    // is initialized, VenueServiceListener.onInitializationCompleted method will be called.
    venueEngine.start(callback: {
        error, data in if let error = error {
            print("Failed to authenticate, reason: " + error.localizedDescription)
        }
    })

    if (hrn != "") {
        // Set platform catalog HRN
        venueService.setHrn(hrn: hrn)
    }
}

Once the VenueEngine is started, it authenticates using the current credentials, and then starts the VenueService. Once the VenueService is initialized, the VenueServiceDelegate.onInitializationCompleted() method is called:

// Delegate for the VenueService event.
extension ViewController: VenueServiceDelegate {
    func onInitializationCompleted(result: VenueServiceInitStatus) {
        if (result == .onlineSuccess) {
            print("Venue Service initialize successfully.")
        } else {
            print("Venue Service failed to initialize!")
        }
    }

    func onVenueServiceStopped() {
        print("Venue Service has stopped.")
    }
}

List All Indoor Maps

The HERE Indoor Maps API allows you to list all private venues that are accessible for your account and the selected collection. VenueMap contains a list which holds VenueInfo elements containing venue Identifier, venue ID and venue Name.

let venueInfo:[VenueInfo]? = venueEngine?.venueMap.getVenueInfoList()
if let venueInfo = venueInfo {
  for venueInfo in venueInfo {
      print("Venue Identifier: \(venueInfo.venueIdentifier)." + " Venue Id: \(venueInfo.venueId)." + " Venue Name: \(venueInfo.venueName).")
  }
}

Load and Show a Venue

The HERE Indoor Map API allows you to load and visualize venues by ID. You must know the venue's ID for the current set of credentials. There are several ways to load and visualize the venues. In the VenueService, there is a method to start a new venues loading queue:

venueEngine.venueService.startLoading(venueIds: /*VENUE_ID_LIST*/)

You can also add a venue ID to the existing loading queue:

venueEngine.venueService.addVenueToLoad(venueId: /*VENUE_ID*/);

A VenueMap has two methods to add a venue to the map: selectVenueAsync() and addVenueAsync(). Both methods use getVenueService().addVenueToLoad() to load the venue by ID and then add it to the map. The method selectVenueAsync() also selects the venue:

venueEngine.venueMap.selectVenueAsync(venueId:/*VENUE_ID*/);
venueEngine.venueMap.addVenueAsync(venueId:/*VENUE_ID*/);

Once the venue is loaded, the VenueService calls the VenueDelegate.onGetVenueCompleted() method:

// Delegate for the venue loading event.
extension ViewController: VenueDelegate {
    func onGetVenueCompleted(venueId: Int32, venueModel: VenueModel?, online: Bool, venueStyle: VenueStyle?) {
        if venueModel == nil {
            print("Loading of venue \(venueId) failed!")
        }
    }
}

Once the venue is loaded successfully, if you are using the addVenueAsync() method, only the VenueLifecycleDelegate.onVenueAdded() method will be triggered. If you are using the selectVenueAsync()method, the VenueSelectionDelegate.onSelectedVenueChanged() method will also be triggered.

// Delegate for the venue selection event.
extension ViewController: VenueSelectionDelegate {
    func onSelectedVenueChanged(deselectedVenue: Venue?, selectedVenue: Venue?) {
        if let venueModel = selectedVenue?.venueModel {
            if moveToVenue {
                // Move camera to the selected venue.
                let center = GeoCoordinates(latitude: venueModel.center.latitude,
                                            longitude: venueModel.center.longitude,
                                            altitude: 500.0)
                mapView.camera.lookAt(point: center)
            }
        }
    }
}

A Venue can also be removed from the VenueMap, which triggers the VenueLifecycleDelegate.onVenueRemoved() method:

venueEngine.venueMap.removeVenue(venue: venue)

Label Text Preference

You can override the default label text preference for a venue.

Once the VenueEngine is initialized, a callback is called. From this point on, there is access to the VenueService. The optional method setLabeltextPreference() can be called to set the label text preference during rendering. Overriding the default style label text preference provides an opportunity to set the following options as a list where the order defines the preference:

  • "OCCUPANT_NAMES"
  • "SPACE_NAME"
  • "INTERNAL_ADDRESS"
  • "SPACE_TYPE_NAME"
  • "SPACE_CATEGORY_NAME"

These can be set in any desired order. For example, if the label text preference does not contain "OCCUPANT_NAMES" then it will switch to "SPACE_NAME" and so on, based on the order of the list. Nothing is displayed if no preference is found.

private func onVenueEngineInit() {
    // Get VenueService and VenueMap objects.
    let venueMap = venueEngine.venueMap
    let venueService = venueEngine.venueService

    // Add needed delegates.
    venueService.addServiceDelegate(self)
    venueService.addVenueDelegate(self)
    venueMap.addVenueSelectionDelegate(self)

    // Start VenueEngine. Once authentication is done, the authentication completion handler
    // will be triggered. Afterwards VenueEngine will start VenueService. Once VenueService
    // is initialized, VenueServiceListener.onInitializationCompleted method will be called.
    venueEngine.start(callback: {
        error, data in if let error = error {
            print("Failed to authenticate, reason: " + error.localizedDescription)
        }
    })

    if (hrn != "") {
        // Set platform catalog HRN
        venueService.setHrn(hrn: hrn)
    }

    // Set label text preference
    venueService.setLabeltextPreference(labelTextPref: LabelPref)
}

Select Venue Drawings and Levels

A Venue object allows you to control the state of the venue.

The property Venue.selectedDrawing allows you to get and set a drawing which is visible on the map. When a new drawing is selected, the VenueDrawingSelectionDelegate.onDrawingSelected() method is triggered.

The following provides an example of how to select a drawing when an item is clicked in a UITableView:

extension DrawingSwitcher: UITableViewDelegate {
    public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let drawingIndex: Int = indexPath.row
        if let venue = venueMap?.selectedVenue {
            // Set the selected drawing when a user clicks on the item in the table view.
            let drawing: VenueDrawing = venue.venueModel.drawings[drawingIndex]
            venue.selectedDrawing = drawing
            ...
        }
    }
}

The properties Venue.selectedLevel, Venue.selectedLevelIndex and Venue.selectedLevelZIndex allow you to get and set a level which will be visible on the map. If a new level is selected, the VenueLevelSelectionDelegate.onLevelSelected() method is triggered.

The following provides an example of how to select a level based on a reversed levels list from UITableView:

extension LevelSwitcher: UITableViewDelegate {
    public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // Rows in the LevelSwitcher's table view are presented in the reversed way
        currentLevelIndex = Int32(levels.count - indexPath.row - 1)
        updateLevel(currentLevelIndex)
    }
}
func updateLevel(_ levelIndex: Int32) {
    if let venue = venueMap?.selectedVenue {
        venue.selectedLevelIndex = currentLevelIndex
    }
}

A full example of the UI switchers to control drawings and levels is available in the IndoorMap example app, available on GitHub.

Customize the Style of a Venue

You can change the visual style of VenueGeometry objects. Geometry style and/or label style objects must be created and provided to the Venue.setCustomStyle() method:

// Create geometry and label styles for the selected geometry.
geometryStyle = VenueGeometryStyle(
    mainColor: selectedColor, outlineColor: selectedOutlineColor, outlineWidth: 1)
labelStyle = VenueLabelStyle(
    fillColor: selectedTextColor, outlineColor: selectedTextOutlineColor, outlineWidth: 1, maxFont: 28)
venue.setCustomStyle(geometries: [geometry], style: geometryStyle, labelStyle: labelStyle)

Select Space by Identifier

The ID of spaces, levels and drawings can be extracted using getIdentifier(), e.g. for spaces call: spaces.getIdentifier(). Then, for using those id values, a specific space can be searched in a level or a drawing with getGeometryById(id:).

var geometriesID : [String] = [];
var geometries : [VenueGeometry] = [];
for id in geometriesID
{
    VenueGeometry geometry = selectedVenue?.getSelectedDrawing().getGeometryById(id);
    geometries.append(geometry);
}
geometryStyle = VenueGeometryStyle(
    mainColor: selectedColor, outlineColor: selectedOutlineColor, outlineWidth: 1)
labelStyle = VenueLabelStyle(
    fillColor: selectedTextColor, outlineColor: selectedTextOutlineColor, outlineWidth: 1, maxFont: 28)
selectedVenue.setCustomStyle(geometries: geometries, style: geometryStyle, labelStyle: labelStyle)

Handle Tap Gestures on a Venue

You can select a venue object by tapping it. First, set the tap delegate:

// Create a venue tap handler and set it as default tap delegate.
mapView.gestures.tapDelegate = VenueTapHandler(venueEngine: venueEngine,
                                               mapView: mapView,
                                               geometryLabel: geometryNameLabel)

Inside the tap delegate, you can use the tapped geographic coordinates as parameter for the VenueMap.getGeometry() and VenueMap.getVenue() methods:

public func onTap(origin: Point2D) {
    deselectGeometry()

    let venueMap = venueEngine.venueMap
    // Get geo coordinates of the tapped point.
    if let position = mapView.viewToGeoCoordinates(viewCoordinates: origin) {
        // If the tap point was inside a selected venue, try to pick a geometry inside.
        // Otherwise try to select an another venue, if the tap point was on top of one of them.
        if let selectedVenue = venueMap.selectedVenue, let geometry = venueMap.getGeometry(position: position) {
            onGeometryPicked(venue: selectedVenue, geometry: geometry)
        } else if let venue = venueMap.getVenue(position: position) {
            venueMap.selectedVenue = venue
        }
    }
}

func deselectGeometry() {
    // If a map marker is already on the screen, remove it.
    if let currentMarker = marker {
        mapView.mapScene.removeMapMarker(currentMarker)
    }
}

func onGeometryPicked(venue: Venue,
                      geometry: VenueGeometry) {
    // If the geomtry has an icon, add a map marker on top of the geometry.
    if geometry.lookupType == .icon {
        if let image = getMarkerImage() {
            marker = MapMarker(at: geometry.center,
                               image: image,
                               anchor: Anchor2D(horizontal: 0.5, vertical: 1.0))
            if let marker = marker {
                mapView.mapScene.addMapMarker(marker)
            }
        }
    }
}

HERE recommends that you deselect the tapped geometry when the selected venue, drawing, or level has changed:

public init(venueEngine: VenueEngine, mapView: MapView, geometryLabel: UILabel) {
    ...
    let venueMap = venueEngine.venueMap
    venueMap.addVenueSelectionDelegate(self)
    venueMap.addDrawingSelectionDelegate(self)
    venueMap.addLevelSelectionDelegate(self)
}

deinit {
    let venueMap = venueEngine.venueMap
    venueMap.removeVenueSelectionDelegate(self)
    venueMap.removeDrawingSelectionDelegate(self)
    venueMap.removeLevelSelectionDelegate(self)
}
extension VenueTapHandler: VenueSelectionDelegate {
    public func onSelectedVenueChanged(deselectedVenue: Venue?, selectedVenue: Venue?) {
        self.deselectGeometry()
    }
}

extension VenueTapHandler: VenueDrawingSelectionDelegate {
    public func onDrawingSelected(venue: Venue, deselectedDrawing: VenueDrawing?, selectedDrawing: VenueDrawing) {
        self.deselectGeometry()
    }
}

extension VenueTapHandler: VenueLevelSelectionDelegate {
    public func onLevelSelected(venue: Venue, drawing: VenueDrawing, deselectedLevel: VenueLevel?, selectedLevel: VenueLevel) {
        self.deselectGeometry()
    }
}

A full example showing usage of the map tap event with venues is available in the IndoorMap example app, available on GitHub.

results matching ""

    No results matching ""