SDK for Android Developer's Guide

Objects and Interaction

You can select ViewObject objects by using a single tap gesture. To enable this in your code, create an OnGestureListener object and pass it to AndroidXMapFragment.getMapGesture().addOnGestureListener(OnGestureListener). When a single tap occurs, the listener receives the onTapEvent(PointF) callback, and if that event is not handled, then the listener receives the onMapObjectsSelected(List<ViewObject>) callback. The application can then define what to do with the selected ViewObject.

Types of ViewObject objects that are selectable are defined within the ViewObject.Type enumeration includes the following:

  • USER_OBJECT - an object the application adds to a map with a MapObject base class (MapPolygon for example).
  • PROXY_OBJECT - an object that is added automatically to a map with a MapProxyObject base class. A proxy object may contain special information about the object depending on the type (for example, TransitStopObject may provide transit stop related information) but it cannot be created or modified.
  • UNKNOWN_OBJECT - a selectable map object that is neither USER_OBJECT nor PROXY_OBJECT.

Map Objects Example on GitHub

You can find an example that demonstrates this feature at https://github.com/heremaps/.

The ViewObject Abstract Class

The ViewObject abstract class represents the base implementation for all objects selectable on a MapView or AndroidXMapFragment. The AndroidXMapFragment features user-selectable objects.

Sub-classes of the ViewObject class include MapObject and MapProxyObject .

MapObject and Geo Objects

MapObject represents an abstract class for all map related objects that can be added on a Map. The subclasses of this abstract class include the following:

  • MapContainer
  • MapCircle
  • MapPolyline
  • MapPolygon
  • MapRoute
  • MapMarker
  • MapLocalModel
  • MapGeoModel
  • MapLabeledMarker
  • MapScreenMarker

These objects can be created by calling the appropriate constructor methods. In some cases a geo object is required in the constructor. Geo objects (for example, GeoPolyline and GeoPolygon) are geographical data representations that act as models to MapObjects, which act as views. Unlike map objects, geo objects cannot be added directly to a Map. For more information on geo objects and creating map objects see the API Reference.

The following code snippet demonstrates how to create a MapPolyline and a GeoPolyline object:


List<GeoCoordinate> testPoints = new ArrayList<GeoCoordinate>();
testPoints.add(new GeoCoordinate(49.163, -123.137766, 10));
testPoints.add(new GeoCoordinate(59.163, -123.137766, 10));
testPoints.add(new GeoCoordinate(60.163, -123.137766, 10));
GeoPolyline polyline = new GeoPolyline(testPoints);
MapPolyline mapPolyline = new MapPolyline(polyline);

To add a MapObject to the map, use Map.addMapObject(MapObject) or Map.addMapObjects(List<MapObject>). You can use the setOverlayType(MapOverlayType) method to set the display layer for the map object. By default map objects are assigned to the foreground.

Note: For use cases where a map object needs to be viewable in 3D space use MapLocalModel or MapGeoModel. Other map objects are not guaranteed to support 3D.

MapContainer

You can use MapContainer as a container for other MapObject instances. Map containers determine the stacking order of objects displayed on a map. To add a map object, call the MapContainer.addMapObject(MapObject) method.

Note: MapRoute and MapContainer cannot be added to a MapContainer.
Note: If a map object is a part of a MapContainer, it has the same MapOverlayType as the map container.

MapCircle

A MapCircle represents a type of MapObject in the shape of a circle with an assigned radius distance and a GeoCoordinate center. It can be created by calling the constructor MapCircle(double radius, GeoCoordinate center).

Figure 1. A MapCircle object

MapPolyline

A MapPolyline is a MapObject in the shape of a polyline with anchor points at any number of GeoCoordinate points. It can be created via a GeoPolyline object, which can be created by calling the GeoPolyline(List<GeoCoordinate> points) constructor.

Note: A MapPolyline or MapPolygon can only contain up to 65536 vertices.
Figure 2. A MapPolyline object

MapPolygon

A MapPolygon is a MapObject in the shape of a polygon. In contrast to a MapPolyline it is assumed that the last coordinate in the line path is connected to the first coordinate thereby constructing an enclosed geometry. A MapPolygon may have separate border and fill colors. To create a MapPolygon, use the constructor MapPolygon(GeoPolygon polygon). A GeoPolygon can be created by calling GeoPolygon(List<GeoCoordinate> points).

Figure 3. A MapPolygon object

MapRoute

A MapRoute is a MapObject that displays a calculated route on a map. For more information on MapRoute see Routing .

MapMarker

A MapMarker is a MapObject that displays an icon at a geographical position on a map. You can create a MapMarker with your own custom icon by calling MapMarker(GeoCoordinate, Image).

Figure 4. A MapMarker object

MapMarker instances are always placed on top of other map objects. Refer to the diagram below for more information about z-index ordering for multiple map markers.

Figure 5. MapMarker order

You can set MapMarker to be draggable by using the MapMarker.setDraggable(true) method. To listen to drag events, such as marker position changes, use MapMarker.OnDragListener.

MapLabeledMarker

A MapLabeledMarker is a different type of marker object that avoids overlapping with other icons and text on the map. By default the visual appearance of a MapLabeledMarker is similar to a point of interest. You can choose a preset category icon (for example, IconCategory.ZOO) or set your own Image as the marker icon.

Figure 6. A MapLabeledMarker object

Unlike MapMarker, setting the label text to a MapLabeledMarker does not require enabling an info bubble. You can set the marker label text by providing a language and a localized string to the MapLabeledMarker.setLabelText(String, String) method. The localized text in the language that matches the current Map.getMapDisplayLanguage() is displayed (if available). Otherwise, the first added localized text is displayed.

Note: Although a MapLabeledMarker is visually similar to a point of interest, its overlay type is set to FOREGROUND_OVERLAY by default.

MapLocalModel

A MapLocalModel is an arbitrary 3D map object drawn using a local coordinate (as opposed to a geocoordinate) mesh. You can create a custom MapLocalModel by calling MapLocalModel() and setting the model mesh, texture, orientation, and geographical location before adding it to the map. You can set geographical location by providing a geocoordinate to the MapLocalModel.setAnchor(GeoCoordinate) method. For example:

FloatBuffer buff = FloatBuffer.allocate(12); // Two triangles
buff.put(0- delta);
buff.put(0- delta);
buff.put(1.f);
buff.put(0 + delta);
buff.put(0 - delta);
buff.put(1.f);
buff.put(0 - delta);
buff.put(0 + delta);
buff.put(1.f);
buff.put(0 + delta);
buff.put(0 + delta);
buff.put(1.f);

// Two triangles to generate the rectangle. Both front and back face
IntBuffer vertIndicieBuffer = IntBuffer.allocate(12);
vertIndicieBuffer.put(0);
vertIndicieBuffer.put(2);
vertIndicieBuffer.put(1);
vertIndicieBuffer.put(2);
vertIndicieBuffer.put(3);
vertIndicieBuffer.put(1);
vertIndicieBuffer.put(0);
vertIndicieBuffer.put(1);
vertIndicieBuffer.put(2);
vertIndicieBuffer.put(1);
vertIndicieBuffer.put(3);
vertIndicieBuffer.put(2);

// Texture coordinates
FloatBuffer textCoordBuffer = FloatBuffer.allocate(8);
textCoordBuffer.put(0.f);
textCoordBuffer.put(0.f);
textCoordBuffer.put(1.f);
textCoordBuffer.put(0.f);
textCoordBuffer.put(0.f);
textCoordBuffer.put(1.f);
textCoordBuffer.put(1.f);
textCoordBuffer.put(1.f);

LocalMesh myMesh = new LocalMesh();
myMesh.setVertices(buff);
myMesh.setVertexIndices(vertIndicieBuffer);
myMesh.setTextureCoordinates(textCoordBuffer);

MapLocalModel myObject = new MapLocalModel();
myObject.setMesh(myMesh); //a LocalMesh object
myObject.setTexture(myImage); //an Image object
myObject.setAnchor(myLocation); //a GeoCoordinate object
myObject.setScale(20.0f);
myObject.setDynamicScalingEnabled(true);
myObject.setYaw(45.0f);
map.addMapObject(myObject);

When translating the 3D model mesh to the map a unit of 1.0f represents 1 meter in the real world. For example, a Vector3f(100,200,300) represents an offset of +100 meters in the x-axis (East), +200 meters in the y-axis (North), and +300 meters in the z-axis direction (Up). You can further control the size of the 3D model mesh by setting a scaling factor with the setScale() method.

Figure 7. A MapLocalModel object

Aside from setting a texture, a MapLocalModel can also be customized by setting its material and lighting using the Phong reflection model. For example, the following code sets the ambient color, diffuse color, and light source to the MapLocalModel.

// This light shines from above in the Z axis
DirectionalLight light = new DirectionalLight(new Vector3f(0, 0.5f, 1));
m_model.addLight(light);

// Give this a default color
PhongMaterial mat = new PhongMaterial();
mat.setAmbientColor(0xffffffff);
mat.setDiffuseColor(0x00000000);
m_model.setMaterial(mat);
Note:
  • As 3D objects consume large amounts of memory, avoid using MapLocalModel and MapGeoModel to replace 2D map markers. Two examples of recommended uses of these classes are adding a few 3D structures to the map or showing a realistic car model during guidance.
  • If you use MapLocalModel to create a two-dimensional object and an anchor with an undefined or zero altitude value, there is a known rendering issue with OpenGL where parts of the object may conflict with the map layer causing the object to flicker. To get around this issue, use a z-coordinate offset that is greater than 0. For example, you can use a small floating point number such as 0.001 so that the user is unable to distinguish between the object altitude and the map.

MapGeoModel

A MapGeoModel is an arbitrary 3D map object drawn using geocoordinate vertices. You can create a MapGeoModel by calling its constructor and setting a list of geocoordinates, a list indicating the vertex order, a list of UV coordinates, and a texture Image. For example:


List<GeoCoordinate> myLocations = Arrays.asList(
    new GeoCoordinate(37.783409, -122.439473),
    new GeoCoordinate(37.785444, -122.424667),
    new GeoCoordinate(37.774149, -122.429345));

// vertices must be specified in a counter-clockwise manner

IntBuffer vertIndicieBuffer = IntBuffer.allocate(3);
vertIndicieBuffer.put(0);
vertIndicieBuffer.put(2);
vertIndicieBuffer.put(1);

FloatBuffer textCoordBuffer = FloatBuffer.allocate(6);
textCoordBuffer.put(0.5f);
textCoordBuffer.put(0.5f);
textCoordBuffer.put(0.5f);
textCoordBuffer.put(0.5f);
textCoordBuffer.put(0.5f);
textCoordBuffer.put(0.5f);

GeoMesh meshy = new GeoMesh();
meshy.setVertices(myLocations);
meshy.setVertexIndices(vertIndicieBuffer);
meshy.setTextureCoordinates(textCoordBuffer);

MapGeoModel myGeoModel = new MapGeoModel();
myGeoModel.setMesh(meshy);
myGeoModel.setTexture(myTexture);

As with MapLocalModel, you can also set the lighting and color properties for a MapGeoModel using the addLight(DirectionalLight) and setMaterial(PhongMaterial) methods.

Figure 8. A MapGeoModel object

MapCartoMarker

Points of interest are represented by instances of the MapCartoMarker proxy object class.

Figure 9. Examples of Points of Interest

In the above screenshot there are four points of interests: two shops, one restaurant, and one car dealership. Each of these points of interest may be selected by tapping on the map.

The following is an example of how to retrieve point of interest information from a MapCartoMarker:


switch (proxyObj.getType()) {
  case MAP_CARTO_MARKER:
    MapCartoMarker mapCartoMarker =
      (MapCartoMarker) proxyObj;
    Location location = mapCartoMarker.getLocation();
    String placeName =
      location.getInfo().getField(Field.PLACE_NAME);
    String placeCategory =
      location.getInfo().getField(Field.PLACE_CATEGORY);
    String placePhone =
      location.getInfo().getField(Field.PLACE_PHONE_NUMBER);
    //...
    break;
  //...
  default:
    Log.d(TAG, "ProxyObject.getType() unknown");
}

You can extract further Point of Interest (POI) information from a cartographic marker by using Places feature in HERE SDK, since cartographic markers contain identification data that can be passed to a Place search request. For example:

if (mapCartoMarker.getLocation() != null &&
  mapCartoMarker.getLocation().getInfo() != null)
{
  LocationInfo info = mapCartoMarker.getLocation().getInfo();
  String foreignSource = info.getField(Field.FOREIGN_ID_SOURCE);
  String foreignId = info.getField(Field.FOREIGN_ID);

  PlaceRequest request = new PlaceRequest(foreignSource, foreignId);
  request.execute(new ResultListener<Place>() {
    @Override
    public void onCompleted(Place data, ErrorCode error) {
      if (error == ErrorCode.NONE) {
        // extract Place data
      }
    }
  });
}

For more information about this feature see the External References section.

User Interactions with MapObject

This section provides an example of handling MapObject tap events. In the following code:

  • addMapObject() adds the object on the Map.
  • List<ViewObject> holds the objects that have been selected in this tap event. By looping through this list of objects your code can find the MapObject that should respond to this tap event.
Note: The onMapObjectsSelected(List) callback is triggered after the onTapEvent(PointF) callback. For more information on this refer to Map Gestures.

// Create a custom marker image
com.here.android.mpa.common.Image myImage =
  new com.here.android.mpa.common.Image();

try {
  myImage.setImageResource(R.drawable.my_png);
} catch (IOException e) {
  finish();
}

// Create the MapMarker
MapMarker myMapMarker =
  new MapMarker(new GeoCoordinate(LAT, LNG), myImage);

map.addMapObject(myMapMarker);

...

// Create a gesture listener and add it to the AndroidXMapFragment
MapGesture.OnGestureListener listener =
  new MapGesture.OnGestureListener.OnGestureListenerAdapter() {
    @Override
    public boolean onMapObjectsSelected(List<ViewObject> objects) {
      for (ViewObject viewObj : objects) {
        if (viewObj.getBaseType() == ViewObject.Type.USER_OBJECT) {
          if (((MapObject)viewObj).getType() == MapObject.Type.MARKER) {
            // At this point we have the originally added
            // map marker, so we can do something with it
            // (like change the visibility, or more
            // marker-specific actions)
            ((MapObject)viewObj).setVisible(false);
          }
        }
      }
      // return false to allow the map to handle this callback also
      return false;
    }
    ...
  };

The MapOverlay Class

The MapOverlay class represents a special type of map object that does not inherit from the MapObject base class. Instead, it provides a way for any Android View to be displayed at a fixed geographical location on the map.

You can add content to a map overlay by using the MapOverlay(View, GeoCoordinate) constructor. If complex view contents are required, such as a view with subviews of its own, the content should be fully initialized before adding it to the map overlay.

Due to the extra performance cost of Android views it is recommended that the MapOverlay only be used in situations where the additional functionality provided by a View, such as a button, is needed. If the map object only needs to display a static image, use MapMarker.

Note: MapOverlay does not inherit from MapObject but overlays are returned as a MapMarker from a tap gesture callback by default. To avoid this behavior and these substitute markers, the appropriate gesture handling must be implemented either in a MapOverlay subclass, or in a custom view added as a subview to a standard MapOverlay.

The following code shows how to use a simple button in a MapOverlay.

private Button button;

private void onMapFragmentInitializationCompleted() {
  // retrieve a reference of the map from the map fragment
  map = mapFragment.getMap();
  // Set the map center coordinate to the Vancouver region (no animation)
  map.setCenter(new GeoCoordinate(49.196261, -123.004773, 0.0),
      Map.Animation.NONE);
  // Set the map zoom level to the average between min and max (no
  // animation)
  map.setZoomLevel((map.getMaxZoomLevel() + map.getMinZoomLevel()) / 2);
  // create the button
  button = new Button(this);
  button.setText("TEST");
  // create overlay and add it to the map
  map.addMapOverlay(
      new MapOverlay(button,
          new GeoCoordinate(37.77493, -122.419416, 0.0)));
}      

Handling MapProxyObject objects

The following code demonstrates how to handle tap events on a MapProxyObject:

  • The onMapObjectsSelected event of the OnGestureListener listens to object selected. For more information on OnGestureListener refer to Map Gestures.
  • If the selected object is a PROXY_OBJECT, then you can safely cast the ViewObject into a MapProxyObject.
  • If the selected object is a USER_OBJECT, then you need to find the object using the hash map; refer to the preceding example.

private MapGesture.OnGestureListener listener =
  new MapGesture.OnGestureListener.OnGestureListenerAdapter() {
    ...
    @Override
    public boolean onMapObjectsSelected(List<ViewObject> objects) {
      for (ViewObject obj : objects) {
        switch (obj.getBaseType()) {
        case PROXY_OBJECT:
          MapProxyObject proxyObj = (MapProxyObject) obj;
          switch (proxyObj.getType()) {
          case TRANSIT_ACCESS:
            TransitAccessObject transitAccessObj =
                (TransitAccessObject) proxyObj;
            Log.d(TAG, "Found a TransitAccessObject");
            break;
          case TRANSIT_LINE:
            TransitLineObject transitLineObj =
                (TransitLineObject) proxyObj;
            Log.d(TAG, "Found a TransitLineObject");
            break;
          case TRANSIT_STOP:
            TransitStopObject transitStopObj =
                (TransitStopObject) proxyObj;
            Log.d(TAG, "Found a TransitStopObject");
            break;
          default:
            Log.d(TAG, "ProxyObject.getType() unknown");
          }
          break;

        // User objects are more likely to be handled
        // as in the previous example
        case USER_OBJECT:
        default:
          Log.d(TAG,
            "ViewObject.getBaseType() is USER_OBJECT or unknown");
          break;
        }
      }
      return true;
    }
    ...
  };