Integrate Indoor Maps
The HERE Indoor Map feature provides the possibility to load, show and interact with private venues on the map. For more information about Here Indoor Map, refer to the Introduction to HERE Indoor Map.
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.
Screenshot: Showing an airport venue with a customized floor switcher.
Note that as of now the HERE SDK provides only support for private venues: This means, that your venue data will be shown only in your app. By default, no venues are visible on the map. Each of your venues will get a uniquie venue ID that is tied to your HERE SDK credentials.
Initialize the VenueEngine
Before you start using the Indoor Map API, the VenueEngine
instance should be created and started. This can be done after a map initialization. The best point to create the VenueEngine
is after the map loads a scene.
private void loadMapScene() {
private func onLoadScene(mapError: MapError?) {
guard mapError == nil else {
print("Error: Map scene not loaded, \(String(describing: mapError))")
return
}
mapView.mapScene.disableFeatures([MapFeatures.extrudedBuildings])
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 will be called. From this point, there is access to the VenueService
and the VenueMap
. A VenueService
is used for loading venues and a VenueMap
controls the venues on the map. Inside the completion handler all needed delegates can be added. Afterwards, the VenueEngine
needs to be started. The platform map catalog HRN
must be set once VenueEngine
is started.
Note
It is required to set a valid HRN value, otherwise the VenueService
will not work - and an error log indicates the missing or invalid HRN value. This link contains more information on how you can get a valid HRN string for your project.
private func onVenueEngineInit() {
let venueMap = venueEngine.venueMap
let venueService = venueEngine.venueService
venueService.addServiceDelegate(self)
venueService.addVenueDelegate(self)
venueMap.addVenueSelectionDelegate(self)
venueEngine.start(callback: {
error, data in if let error = error {
print("Failed to authenticate, reason: " + error.localizedDescription)
}
})
venueService.setHrn(hrn: hrn)
}
After the VenueEngine
is started, it authenticates using the current credentials. If authentication is successful, the VenueEngine
will start the VenueService
. Once the VenueService
is initialized, the VenueServiceDelegate.onInitializationCompleted()
method will be called.
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.")
}
}
Load and Show a Venue
The Indoor Map API allows to load and visualize venues by ID's. You should know the venue ID's available for the current credentials beforehand. 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: )
Also, there is a method to add a venue ID to the existing loading queue:
venueEngine.venueService.addVenueToLoad(venueId: );
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.
extension ViewController: VenueDelegate {
func onGetVenueCompleted(venueId: Int32, venueModel: VenueModel?, online: Bool, venueStyle: VenueStyle?) {
if venueModel == nil {
print("Loading of venue \(venueId) failed!")
}
}
}
If the venue is loaded successfully, in case of the method addVenueAsync()
, only the VenueLifecycleDelegate.onVenueAdded()
method will be triggered. In case of the method selectVenueAsync()
, VenueSelectionDelegate.onSelectedVenueChanged()
method will be triggered as well.
extension ViewController: VenueSelectionDelegate {
func onSelectedVenueChanged(deselectedVenue: Venue?, selectedVenue: Venue?) {
if let venueModel = selectedVenue?.venueModel {
if moveToVenue {
let center = GeoCoordinates(latitude: venueModel.center.latitude,
longitude: venueModel.center.longitude,
altitude: 500.0)
mapView.camera.lookAt(point: center)
}
}
}
}
A Venue
can be removed from the VenueMap
. In this case, the VenueLifecycleDelegate.onVenueRemoved()
method will be triggered.
venueEngine.venueMap.removeVenue(venue: venue)
Label Text Preference
You can override the default label text preference for the venue.
Once the VenueEngine
is initialized, a callback will be called. From this point, there is access to the VenueService
. The optional method setLabeltextPreference()
can be called for setting the label text prererence for rendering. Overriding the default style label text preference provides the opportunity to set the following options as a list where the order defines the preference: "OCCUPANT_NAMES", "SPACE_NAME", "INTERNAL_ADDRESS", "SPACE_TYPE_NAME" and "SPACE_CATEGORY_NAME". These can be set in any desired order. 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 in the list. If no preference is found then nothing will be shown.
private func onVenueEngineInit() {
let venueMap = venueEngine.venueMap
let venueService = venueEngine.venueService
venueService.addServiceDelegate(self)
venueService.addVenueDelegate(self)
venueMap.addVenueSelectionDelegate(self)
venueEngine.start(callback: {
error, data in if let error = error {
print("Failed to authenticate, reason: " + error.localizedDescription)
}
})
venueService.setHrn(hrn: hrn)
venueService.setLabeltextPreference(labelTextPref: LabelPref)
}
Select Venue Drawings and Levels
A Venue
object allows to control a state of the venue.
The property Venue.selectedDrawing
allows to get and set a drawing which will be visible on the map. If a new drawing is selected, the VenueDrawingSelectionDelegate.onDrawingSelected()
method will be triggered. See an example of how to select a drawing when an item is clicked in an UITableView
:
extension DrawingSwitcher: UITableViewDelegate {
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let drawingIndex: Int = indexPath.row
if let venue = venueMap?.selectedVenue {
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 will be triggered. See 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) {
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 indoor-map example app you can find on GitHub.
Customize the Style of a Venue
It is possible to change the visual style of the VenueGeometry
objects. Geometry style and/or label style objects need to be created and provided to the Venue.setCustomStyle()
method.
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)
Handle Tap Gestures on a Venue
You can select a venue object by performing a tap gesture on it. First, set the 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
if let position = mapView.viewToGeoCoordinates(viewCoordinates: origin) {
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 let currentMarker = marker {
mapView.mapScene.removeMapMarker(currentMarker)
}
}
func onGeometryPicked(venue: Venue,
geometry: VenueGeometry) {
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)
}
}
}
}
It is good practice to 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 of the usage of the map tap event with venues is available in the indoor-map example app you can find on GitHub.