HERE Android SDK Developer's Guide

Platform Data Extension

Platform Data Extension (PDE) provides the ability to easily access the Platform Data Extension API from the HERE Android SDK. You can use this extension to access a wide range of data that can be later used for different use cases. Some examples include displaying road elevation, slopes, and traffic signs. For more information about use cases and the types of data that can be accessed through PDE, check the Platform Data Extension API Developer's Guide .

PDE Thematic Layers

PDE divides map content across many thematic layers. Each thematic data layer serves a specific use case and only contains the data required for it, such as road elevation. To use PDE, you need to first decide on the required data for your app and select the PDE thematic layers accordingly. The available thematic layers can be found via the PDE Layers API. You can then check the targeted thematic layers via the individual Layer API. Before starting to use PDE in your app, you need to select the correct thematic layers, and then decide on what data to use and how to use it. This is a crucial step.

Note: It is common for routes to start on smaller roads, climb to bigger roads, stay on motorways for the main part, and finally steps down to smaller roads again when approaching the destination. Since retrieving all information about smaller roads along the entire route requires a large amount of data, road link-related thematic layers are actually split into five layers each, corresponding to the functional road classes in the HERE map. Functional Class 1 roads are generally motorways, while Functional Class 5 roads are small roads that are only used near a destination. To use these layers, you need to specify the tile layer by appending the functional class suffix "_FCx", where x is a number from 1 to 5. For example, ROAD_GEOM_FC1. If a layer isn't related to road links, such as the PSTLCB_GEN layer, you don't need to append the "_FCx" suffix and specify the tile layer.

PDE Classes

Class Description
PlatformDataRequest Used to create and execute a PDE data request with the specified layers and GeoBoundingBox object.
PlatformDataResult The result of running a PDE data request. Contains data for requested layers, which are accessible by the layer name. This implements the java.util.Map interface for ease of use and interoperability, although objects of this class are unmodifiable. Provides extract method to convert the result into an Map<String, List<Map<String, String>>> which is a raw representation of the underlying data.
PlatformDataItemCollection Represents layer data in the form of an array of PlatformDataItem objects. This is what you get by asking PlatformDataResult for a specific layer—for example, by calling platformDataResult.get("ROAD_GEOM_FC1"). PlatformDataItemCollection implements the java.util.List interface for ease of use and interoperability, although objects of this class are unmodifiable. It also provides an extract() method to convert the collection into an List<Map<String, String>> object.
PlatformDataItem Represents actual data records in a layer as a map of attribute names and values. This is what you get from PlatformDataItemCollection by iterating over its contents or asking for an item at a specific index. Note that for several attributes, shortcut methods are provided, such as item.getLinkId(), which can be used instead of item.get("LINK_ID"). This implements the java.util.Map interface for ease of use and interoperability, although objects of this class are unmodifiable. Provides extract() method to convert it into an Map<String, String> object.

In summary, PlatformDataRequest produces PlatformDataResult, a Map of layer names to PlatformDataItemCollection objects, which in turn are List of PlatformDataItem objects, with each containing attribute names and their values in the form of a Map.

Example: Requesting the PDE Data

The example below shows how a feature is implemented using the PDE data. The goal is to colorize each road segment according to its average height. For this feature, you need the PDE data from the ROAD_GEOM_FC1 and BASIC_HEIGHT_FC1 layers. As of now, the only way to request the PDE data requires the layers specified with an GeoBoundingBox.

Note: The ROAD_GEOM_FC[number] and BASIC_HEIGHT_FC[number] layers are also referred to as "tile" layers, since their data is split into multiple tiles. Due to server limitations, up to 15 tiles can be requested at a time. PDE also supports non-tile layers.

To use the data from both layers, you need to join the two layers by using the LINK_ID property. This is demonstrated in the next section.

The following are sample results from the Layer request.

ROAD_GEOM_FC1 Layer API result:

{
  "description": "Ungeneralized road, ferry and rail ferry geometry (polylines).<br/>If a road link crosses a tile boundary, it will be written into each of the tiles, each including the full link geometry. This simplifies use cases other than pure display of all geometry within a rectangle.",
  "attributes": {
    "LINK_ID": "Permanent link ID. Positive 64 bit Integer that globally identifies the road, carto or buildin footprint link, also across map releases. Link IDs are never reused.",
    "LONG_HAUL": "This link or polygon or POI is of major importance. It should be displayed at high zoom lavels, and it should be included for routing in/through regions where no detailed routing is supported.",
    "NAME": "A name of this road line. Roads can have multiple names, in the same or multiple languages. This field contains any of those.",
    "NAMES": "List of all names for this object, in all languages [...]",
    "TUNNEL": "Is this navigable link or railroad a tunnel?",
    "BRIDGE": "Is this navigable link or railroad a bridge?",
    "LAT": "Latitude coordinates [10^-5 degree WGS84] along the polyline. ",
    "LON": "Longitude coordinates [10^-5 degree WGS84] along the polyline. ",
    "ZLEVEL": "(-4 ... 11) indicates the height of the point relative to another point on a grade separated crossing with any other line. Comma separated. If z-level is null then the value '0' is left out."
  },
  "referencedStaticContents": [],
  "tileRequestsLevel": 9,
  "tileX": 499,
  "tileY": 403,
  "isStaticContent": false
}

BASIC_HEIGHT_FC1 Layer API result:

{
  "description": "Link height values computed from a Digital Terrain Model, cleaned up for continuity along links, bridges and tunnels. Less accurate than ADAS link height values, but full coverage and sufficient for certain use cases.",
  "attributes": {
    "LINK_ID": "Permanent link ID. Positive 64 bit Integer that globally identifies the road, carto or buildin footprint link, also across map releases. Link IDs are never reused.",
    "DTM_MIN_HEIGHT": "The minimum height [cm above WGS84 ellipsoid] encountered along the link.",
    "DTM_MAX_HEIGHT": "The maximum height [cm above WGS84 ellipsoid] encountered along the link.",
    "DTM_AVG_HEIGHT": "The average height [cm above WGS84 ellipsoid] along the link.",
    "DTM_REF_ZCOORD": "Height [cm above WGS84 ellipsoid] at the reference node of the link.",
    "DTM_NONREF_ZCOORD": "Height [cm above WGS84 ellipsoid] at the non-reference node of the link."
  },
  "referencedStaticContents": [],
  "tileRequestsLevel": 9,
  "tileX": 496,
  "tileY": 358,
  "isStaticContent": false
}

To begin using the PDE layers, create a PlatformDataRequest object:

Set<String> layers = new HashSet<String>(Arrays.asList("LINK_FC1", "BASIC_HEIGHT_FC1"));
GeoBoundingBox box = mapView.getBoundingBox();
PlatformDataRequest request =
  PlatformDataRequest.createBoundingBoxRequest(RoadElevationProcessor.LAYERS, box);

Note that trying to create a request with invalid parameters causes a java.lang.IllegalArgumentException.

Next, you need to supply a Listener<PlatformDataResult> object to get the results of the request.

request.execute(new PlatformDataRequest.Listener<PlatformDataResult>() {
  @Override
  public void onCompleted(PlatformDataResult data, PlatformDataRequest.Error error) {
    if (error != null) {
      Log.w(TAG, "PlatformDataRequest failed with error: " + error);
    } else {
      // process received data
      RoadElevationProcessor processor = new RoadElevationProcessor(data);
      processor.process(new Listener<List<MapObject>>() {
        @Override
        public void onResult(List<MapObject> result) {
          m_elevationPolylines = result;
          m_elevationLegend.setVisibility(View.VISIBLE);
          m_map.addMapObjects(m_elevationPolylines);
          onRequestEnd(null);
        }
      });
    }
  }
});

Example: Processing the PDE Data

After the data result is successfully retrieved, you need to process the data. In the example above, you have asked for ROAD_GEOM_FC1 and BASIC_HEIGHT_FC1 data, restricted with the map view's bounding box. For tutorial purposes, we then implemented a sample data processor class, PlatformDataProcessor, which provides methods to join the layers and can be extended to consume the joined data. We use these classes to implement the colorize feature.

public abstract class PlatformDataProcessor<T> {
  protected final PlatformDataResult m_data;
  private Handler m_handler = new Handler(Looper.getMainLooper());

  public PlatformDataProcessor(PlatformDataResult data) {
    m_data = data;
  }

  public void process(final Listener<T> listener) {
    new Thread(new Runnable() {
      @Override
      public void run() {
        final T result = doProcess();
        m_handler.post(new Runnable() {
          @Override
          public void run() {
            listener.onResult(result);
          }
        });
      }
    }).start();
  }

  protected abstract T doProcess();

  protected Map<String, PlatformDataItem> map(Indexer indexer, PlatformDataItemCollection items) {
    Map<String, PlatformDataItem> ret = new HashMap<String, PlatformDataItem>();
    map(indexer, items, ret);
    return ret;
  }

  protected void map(Indexer indexer, PlatformDataItemCollection items,
      Map<String, PlatformDataItem> output) {
    for (PlatformDataItem item : items) {
      output.put(indexer.getIndexValue(item), item);
    }
  }

  protected Map<String, List<PlatformDataItem>> multimap(MultiIndexer indexer,
      PlatformDataItemCollection items) {
    Map<String, List<PlatformDataItem>> ret = new HashMap<String, List<PlatformDataItem>>();
    multimap(indexer, items, ret);
    return ret;
  }

  protected void multimap(MultiIndexer indexer, PlatformDataItemCollection items,
      Map<String, List<PlatformDataItem>> output) {
    for (PlatformDataItem item : items) {
      for (String value : indexer.getIndexValue(item)) {
        List<PlatformDataItem> entry = output.get(value);
        if (entry == null) {
          entry = new ArrayList<PlatformDataItem>();
          output.put(value, entry);
        }
        entry.add(item);
      }
    }
  }

  protected Map<PlatformDataItem, PlatformDataItem> join(Indexer commonIndexer,
      PlatformDataItemCollection mainLayer, PlatformDataItemCollection otherLayer) {
    return join(commonIndexer, mainLayer, commonIndexer, otherLayer);
  }

  protected Map<PlatformDataItem, PlatformDataItem> join(Indexer mainIndexer,
      PlatformDataItemCollection mainLayer, Indexer otherIndexer,
      PlatformDataItemCollection otherLayer) {
    Map<PlatformDataItem, PlatformDataItem> ret =
        new HashMap<PlatformDataItem, PlatformDataItem>();
    Map<String, PlatformDataItem> mappedItems = map(otherIndexer, otherLayer);

    for (PlatformDataItem item : mainLayer) {
      String value = mainIndexer.getIndexValue(item);
      PlatformDataItem matchedItem = mappedItems.get(value);
      ret.put(item, matchedItem);
    }
    return ret;
  }

  protected Map<PlatformDataItem, List<PlatformDataItem>> join(MultiIndexer mainIndexer,
      PlatformDataItemCollection mainLayer, PlatformDataItemCollection otherLayer) {
    return join(mainIndexer, mainLayer, mainIndexer, otherLayer);
  }

  protected Map<PlatformDataItem, List<PlatformDataItem>> join(MultiIndexer mainIndexer,
      PlatformDataItemCollection mainLayer, MultiIndexer otherIndexer,
      PlatformDataItemCollection otherLayer) {
    Map<String, List<PlatformDataItem>> mappedItems = multimap(otherIndexer, otherLayer);
    Map<PlatformDataItem, List<PlatformDataItem>> ret =
        new HashMap<PlatformDataItem, List<PlatformDataItem>>();
    for (PlatformDataItem item : mainLayer) {
      for (String value : mainIndexer.getIndexValue(item)) {
        List<PlatformDataItem> matchedItems = mappedItems.get(value);
        List<PlatformDataItem> entry = ret.get(item);
        if (entry == null) {
          entry = new ArrayList<PlatformDataItem>();
          ret.put(item, entry);
        }
        if (matchedItems != null) {
          entry.addAll(matchedItems);
        }
      }
    }
    return ret;
  }

  interface Indexer {
    String getIndexValue(PlatformDataItem item);
  }

  interface MultiIndexer {
    String[] getIndexValue(PlatformDataItem item);
  }

  interface Listener<T> {
    void onResult(T result);
  }
}

Note that the PlatformDataProcessor.join() method is for inner joining the thematic layers with properties where more than one data item is possible. For example, the TRAFFIC_SIGN_FCx thematic layer has the LINK_IDS property where as much as two LINK_IDs are contained.

Next, create RoadElevationProcessor. The RoadElevationProcessor class extends the PlatformDataProcessor for coloring the road segments according to their average height. This class needs the result and a dedicated map container intended for the MapPolyline objects. Note that the base class PlatformDataProcessor performs the inner joining of the thematic layer data depending on the indexer blocks provided. The RoadElevationProcessor class utilizes the joined data to implement the intended feature.

public class RoadElevationProcessor extends PlatformDataProcessor<List<MapObject>> {
  public static final String ROAD_GEOM_LAYER = "ROAD_GEOM_FC1";
  public static final String BASIC_HEIGHT_LAYER = "BASIC_HEIGHT_FC1";
  public static final Set<String> LAYERS = new HashSet<String>(Arrays.asList(ROAD_GEOM_LAYER,
      BASIC_HEIGHT_LAYER));

  public RoadElevationProcessor(PlatformDataResult data) {
    super(data);
  }

  protected List<MapObject> doProcess() {
    List<MapObject> polylines = new ArrayList<MapObject>();
    PlatformDataItemCollection roadGeomLayer = m_data.get(ROAD_GEOM_LAYER);
    PlatformDataItemCollection basicHeightLayer = m_data.get(BASIC_HEIGHT_LAYER);

    Map<PlatformDataItem, PlatformDataItem> combinedData =
        join(LINK_ID_INDEXER, roadGeomLayer, basicHeightLayer);

    for (Map.Entry<PlatformDataItem, PlatformDataItem> entry : combinedData.entrySet()) {
      PlatformDataItem roadGeomItem = entry.getKey();
      PlatformDataItem basicHeightItem = entry.getValue();
      List<GeoCoordinate> linkGeometry = roadGeomItem.getCoordinates();
      if (linkGeometry.size() > 1) {
        GeoPolyline geoPolyline = new GeoPolyline(linkGeometry);
        MapPolyline mapPolyline = new MapPolyline(geoPolyline);
        mapPolyline.setLineColor(calculateColor(basicHeightItem.getAverageHeight()));
        mapPolyline.setLineWidth(10);
        polylines.add(mapPolyline);
      }
    }

    return polylines;
  }

  private int calculateColor(int height) {
    if (height > 75000) {
      return 0xff00695c;
    } else if (height > 25000) {
      return 0xff009688;
    } else if (height > 0) {
      return 0xff4db6ac;
    } else if (height > -25000) {
      return 0xff7986cb;
    } else if (height > -75000) {
      return 0xff5c6bc0;
    } else {
      return 0xff3f51b5;
    }
  }

  private Indexer LINK_ID_INDEXER = new Indexer() {
    @Override
    public String getIndexValue(PlatformDataItem item) {
      return item.getLinkId();
    }
  };
}

The main method for the RoadElevationProcessor class is the doProcess() method.

The first thing this method does is joining the data coming from two different thematic layers with the "indexer" blocks. Here, the data is joined via the LINK_ID's. Note that the linkId property is available as a shortcut property in the PlatformDataItem class. After joining the data, each ROAD_GEOM_FC1 data item is inner joined with the relevant BASIC_HEIGHT_FC1 data item, and the joined data is returned as a dictionary where the actual type is Map<PlatformDataItem, PlatformDataItem>. Each key-value pairs provides the necessary data for the feature: the key of PlatformDataItem type representing the ROAD_GEOM_FC1 thematic layer data provides the road geometry, such as coordinates of the road segment, and the value of PlatformDataItem type, representing the BASIC_HEIGHT_FC1 thematic layer data, provides the average height of that road segment. The joined data provides all the data required for implementing the intended feature.

{
  BRIDGE = N;
  LAT = "5246124,2";
  "LINK_ID" = 936938339;
  LON = "1342560,24";
  "LONG_HAUL" = Y;
  NAME = A100;
  NAMES = "GERBNTunnel BritzGERY\"tU|n@l \"brItsGERBNA100GERN\"?a: \"hUn|d6t;GERN\"?aU|to:|ba:n ?aIn|\"hUn|d6t;GERY\"?a: ?aIn|\"hUn|d6t;GERN\"?aU|to:|ba:n \"hUn|d6tGERBNStadtring BerlinGERY\"Stat|rIN bEr|\"li:n";
  TUNNEL = Y;
  ZLEVEL = ",";
} = {
  "DTM_AVG_HEIGHT" = 8500;
  "DTM_MAX_HEIGHT" = 8693;
  "DTM_MIN_HEIGHT" = 8099;
  "DTM_NONREF_ZCOORD" = 8500;
  "DTM_REF_ZCOORD" = 8500;
  "LINK_ID" = 936938339;
}

Note that the key-value pair has the same LINK_ID.