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:

class MyApp extends StatelessWidget {
  final VenueEngineState _venueEngineState = VenueEngineState();

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'HERE SDK for Flutter - Venues',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('HERE SDK for Flutter - Venues'),
        ),
        body: Column(children: [
          Expanded(
            child: Stack(children: <Widget>[
              // Add a HERE map.
              HereMap(onMapCreated: _onMapCreated),
              // Add a venue engine widget, which helps to control venues
              // on the map.
              VenueEngineWidget(state: _venueEngineState)
            ]),
          ),
        ]),
      ),
    );
  }

  void _onMapCreated(HereMapController hereMapController) {
    // Load a scene from the HERE SDK to render the map with a map scheme.
    hereMapController.mapScene.loadSceneForMapScheme(MapScheme.normalDay,
        (MapError error) {
      if (error != null) {
        print('Map scene not loaded. MapError: ${error.toString()}');
        return;
      }
      // Hide the extruded building layer, so that it does not overlap
      // with the venues.
      hereMapController.mapScene.disableFeatures([MapFeatures.extrudedBuildings]);

      // Create a venue engine object. Once the initialization is done,
      // a callback will be called.
      var venueEngine;
      try {
        venueEngine = VenueEngine(_onVenueEngineCreated);
        _venueEngineState.set(hereMapController, venueEngine, _geometryInfoState);
      } on InstantiationException catch(e){
        print('error caught: $e');
      }
    });
  }

  void _onVenueEngineCreated() {
    _venueEngineState.onVenueEngineCreated();
  }

}

Once the VenueEngine is initialized, a callback 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 callback, all needed listeners can be added, and then the VenueEngine must be started. The platform map catalog HRN must be set once 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.

class VenueEngineWidget extends StatefulWidget {
  final VenueEngineState state;

  VenueEngineWidget({required this.state});

  
  VenueEngineState createState() => state;
}

// The VenueEngineState listens to different venue events and helps another
// widgets react on changes.
class VenueEngineState extends State<VenueEngineWidget> {
  late VenueServiceListener _serviceListener;

  void onVenueEngineCreated() {
    var venueMap = venueEngine!.venueMap;
    // Add needed listeners.
    _serviceListener = VenueServiceListenerImpl();
    _venueEngine!.venueService.addServiceListener(_serviceListener);

    // Start VenueEngine. Once authentication is done, the authentication
    // callback will be triggered. Afterwards, VenueEngine will start
    // VenueService. Once VenueService is initialized,
    // VenueServiceListener.onInitializationCompleted method will be called.
    venueEngine!.start(_onAuthCallback);

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

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

// Listener for the VenueService event.
class VenueServiceListenerImpl extends VenueServiceListener {
  
  onInitializationCompleted(VenueServiceInitStatus result) {
    if (result != VenueServiceInitStatus.onlineSuccess) {
      print("VenueService failed to initialize!");
    }
  }

  
  onVenueServiceStopped() {}
}

Start the VenueEngine using a token

The VenueEngine can be started using a valid HERE Indoor Platform project token. For additional information on the HERE platform project management and project workflows refer to HERE Platform Project management

class VenueEngineWidget extends StatefulWidget {
  final VenueEngineState state;

  VenueEngineWidget({required this.state});

  
  VenueEngineState createState() => state;
}

// The VenueEngineState listens to different venue events and helps another
// widgets react on changes.
class VenueEngineState extends State<VenueEngineWidget> {
  late VenueServiceListener _serviceListener;

  void onVenueEngineCreated() {
    var venueMap = venueEngine!.venueMap;
    // Add needed listeners.
    _serviceListener = VenueServiceListenerImpl();
    _venueEngine!.venueService.addServiceListener(_serviceListener);

    // Start VenueEngine by replacing TOKEN_GOES_HERE with token you have in String data type.
    // Afterwards, VenueEngine will start VenueService. Once VenueService is initialized,
    // VenueServiceListener.onInitializationCompleted method will be called.
    venueEngine!.startWithToken("TOKEN_GOES_HERE");
  }
}

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.

List<VenueInfo> venueInfo = _venueEngine!.venueMap.getVenueInfoList();
for (int i = 0; i < venueInfo.length; i++) {
  int venueId = venueInfo[i].venueId;
  print("Venue Identifier: " + venueInfo[i].venueIdentifier + " Venue Id: $venueId" + " Venue Name: "+venueInfo[i].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(/*VENUE_ID_LIST*/);

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

_venueEngine.venueService.addVenueToLoad(/*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(/*VENUE_ID*/);
_venueEngine.venueMap.addVenueAsync(/*VENUE_ID*/);

A venue can also be selected by providing a venue ID:

Container(
padding: EdgeInsets.only(left: 8, right: 8),
// Widget for opening venue by provided ID.
child: TextField(
    decoration: InputDecoration(
        border: InputBorder.none, hintText: 'Enter a venue ID'),
    onSubmitted: (text) {
        try {
        // Try to parse a venue id.
        int venueId = int.parse(text);
        // Select a venue by id.
        _venueEngineState.selectVenue(venueId);
        } on FormatException catch (_) {
        print("Venue ID should be a number!");
        }
    }),
),

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.

// A listener for the venue selection event.
class VenueSelectionListenerImpl extends VenueSelectionListener {
  late VenueEngineState _venueEngineState;

  VenueSelectionListenerImpl(VenueEngineState venueEngineState) {
    _venueEngineState = venueEngineState;
  }

  
  onSelectedVenueChanged(Venue? deselectedVenue, Venue? selectedVenue) {
    _venueEngineState.onVenueSelectionChanged(selectedVenue);
  }
}

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

_venueEngine.venueMap.removeVenue(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.

class VenueEngineWidget extends StatefulWidget {
  final VenueEngineState state;

  VenueEngineWidget({required this.state});

  
  VenueEngineState createState() => state;
}

// The VenueEngineState listens to different venue events and helps another
// widgets react on changes.
class VenueEngineState extends State<VenueEngineWidget> {
  late VenueServiceListener _serviceListener;

  void onVenueEngineCreated() {
    var venueMap = venueEngine!.venueMap;
    // Add needed listeners.
    _serviceListener = VenueServiceListenerImpl();
    _venueEngine!.venueService.addServiceListener(_serviceListener);

    // Start VenueEngine. Once authentication is done, the authentication
    // callback will be triggered. Afterwards, VenueEngine will start
    // VenueService. Once VenueService is initialized,
    // VenueServiceListener.onInitializationCompleted method will be called.
    venueEngine!.start(_onAuthCallback);

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

    // Set label text preference
    venueEngine!.venueService.setLabeltextPreference(LabelPref);
  }
}

Select venue drawings and levels

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

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

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

  // Create a list view item from the drawing.
  Widget _drawingItemBuilder(BuildContext context, VenueDrawing drawing) {
    bool isSelectedDrawing = drawing.identifier == _selectedDrawing!.identifier;
    Property? nameProp = drawing.properties["name"];
    return TextButton(
      style: TextButton.styleFrom(
          foregroundColor: isSelectedDrawing ? Colors.blue : Colors.white,
          padding: EdgeInsets.zero
      ),
      child: Text(
        nameProp != null ? nameProp.asString : "",
        textAlign: TextAlign.center,
        style: TextStyle(
          color: isSelectedDrawing ? Colors.white : Colors.black,
          fontWeight: isSelectedDrawing ? FontWeight.bold : FontWeight.normal,
        ),
      ),
      onPressed: () {
        // Hide the list with drawings.
        _isOpen = false;
        // Select a drawing, if the user clicks on the item.
        _selectedVenue!.selectedDrawing = drawing;
      },
    );
  }

The properties selectedLevel and selectedLevelIndex allow you to get and set a selected level. If a new level is selected, the VenueLevelSelectionListener.onLevelSelected() method is triggered.

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

  // Create a list view item from the level.
  Widget _levelItemBuilder(BuildContext context, VenueLevel level) {
    bool isSelectedLevel = level.identifier == _selectedLevel!.identifier;
    return TextButton(
      style: TextButton.styleFrom(
          foregroundColor: isSelectedLevel ? Colors.blue : Colors.white,
          padding: EdgeInsets.zero
      ),
      child: Text(
        level.shortName,
        textAlign: TextAlign.center,
        style: TextStyle(
          color: isSelectedLevel ? Colors.white : Colors.black,
          fontWeight: isSelectedLevel ? FontWeight.bold : FontWeight.normal,
        ),
      ),
      onPressed: () {
        // Select a level, if the user clicks on the item.
        _selectedVenue!.selectedLevel = level;
      },
    );
  }

A full example of the UI switchers to control drawings and levels is available in the indoor_map_app 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.
final VenueGeometryStyle _geometryStyle =
  VenueGeometryStyle(Color.fromARGB(255, 72, 187, 245), Color.fromARGB(255, 30, 170, 235), 1);
final VenueLabelStyle _labelStyle =
  VenueLabelStyle(Color.fromARGB(255, 255, 255, 255), Color.fromARGB(255, 0, 130, 195), 1, 28);
_selectedVenue.setCustomStyle([geometry], _geometryStyle, _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(String id).

List<String> geometriesID = [];
List<VenueGeometry> geometries = [];
geometriesID.forEach((id) {
VenueGeometry? geometry = venue.selectedDrawing.getGeometryByIdentifier(id);
geometries.add(geometry!);
});
final VenueGeometryStyle _geometryStyle =
VenueGeometryStyle(Color.fromARGB(255, 72, 187, 245), Color.fromARGB(255, 30, 170, 235), 1);
final VenueLabelStyle _labelStyle =
VenueLabelStyle(Color.fromARGB(255, 255, 255, 255), Color.fromARGB(255, 0, 130, 195), 1, 28);
_selectedVenue.setCustomStyle(geometries, _geometryStyle, _labelStyle);

Handle tap gestures on a venue

You can select a venue object by tapping it. First, create a tap listener subclass which will put a MapMarker on top of the selected geometry:

class VenueTapController extends TapListener {
  final HereMapController hereMapController;
  final VenueMap venueMap;

  MapImage? _markerImage;
  MapMarker? _marker;

  VenueTapController(
      {required this.hereMapController,
      required this.venueMap}) {
    // Set a tap listener.
    hereMapController.gestures.tapListener = this;
    // Get an image for MapMarker.
    _loadFileAsUint8List('poi.png').then((imagePixelData) => _markerImage =
        MapImage.withPixelDataAndImageFormat(imagePixelData, ImageFormat.png));
  }

  Future<Uint8List> _loadFileAsUint8List(String fileName) async {
    // The path refers to the assets directory as specified in pubspec.yaml.
    ByteData fileData = await rootBundle.load('assets/' + fileName);
    return Uint8List.view(fileData.buffer);
  }
}

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

  
  onTap(Point2D origin) {
    deselectGeometry();

    // Get geo coordinates of the tapped point.
    GeoCoordinates? position = hereMapController!.viewToGeoCoordinates(origin);
    if (position == null) {
      return;
    }

    // Get a VenueGeometry under the tapped position.
    VenueGeometry? geometry = venueMap.getGeometry(position);
    if (geometry != null) {
      // If there is a geometry, put a marker on top of it.
      _addPOIMapMarker(position);
    } else {
      // If no geometry was tapped, check if there is a not-selected venue under
      // the tapped position. If there is one, select it.
      Venue? venue = venueMap.getVenue(position);
      if (venue != null) {
        venueMap.selectedVenue = venue;
      }
    }
  }

  void deselectGeometry() {
    // If the map marker is already on the screen, remove it.
    if (_marker != null) {
      hereMapController!.mapScene.removeMapMarker(_marker!);
      _marker = null;
    }
  }

  void _addPOIMapMarker(GeoCoordinates geoCoordinates) {
    if (_markerImage == null) {
      return;
    }

    // By default, the anchor point is set to 0.5, 0.5 (= centered).
    // Here the bottom, middle position should point to the location.
    Anchor2D anchor2D = Anchor2D.withHorizontalAndVertical(0.5, 1);
    _marker = MapMarker.withAnchor(geoCoordinates, _markerImage!, anchor2D);
    hereMapController!.mapScene.addMapMarker(_marker!);
  }

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

results matching ""

    No results matching ""