How to use the Optimized Map integration module

How to use the Optimized Map integration module

The HERE Map Content format is designed primarily for map compilation. So
accessing HERE Map Content directly is not efficient for the main use cases of
the Location Library, routing and path-matching.

For optimized access, HERE provides the
Optimized Map for Location Library
which is a map format compiled from the HERE Map Content specifically for these
main use cases.

libraryDependencies ++= Seq(
  "com.here.platform.location" %% "location-integration-optimized-map-dcl2" % "<version>"
)
<dependencies>
    <dependency>
        <groupId>com.here.platform.location</groupId>
        <artifactId>location-integration-optimized-map-dcl2_${scala.compat.version}</artifactId>
    </dependency>
</dependencies>
dependencies {
    compile group: 'com.here.platform.location', name: 'location-integration-optimized-map-dcl2_2.13', version:'<version>'
}

Note

This chapter demonstrates accessing the
Optimized Map for Location Library
using the lower-level Component API. Usually, the level of control this API
provides is not actually required. The chapter
High-Level API shows how to use the simplified Location
Library API to access the same information.

Create OptimizedMapVersionedLayers Objects

This section describes how to access the catalogs
Optimized Map for Location Library
and HERE Map Content in your app.

First, you have to create an instance of the
BaseClient
interface:

private val baseClient = BaseClient()

When you are done with manipulating catalogs, terminate the
BaseClient as
follows:

Await.result(baseClient.shutdown(), Duration.Inf)

When you are done with manipulating catalogs, you can optionally terminate the
BaseClient.

The
OptimizedMapVersionedLayers
objectth implements
OptimizedMapLayers,
which allows you to read the data available in an Optimized Map for Location
Library catalog through VersionedLayers.

To create an
OptimizedMapVersionedLayers
object for the
Optimized Map for Location Library,
use this
BaseClient and
the appropriate catalog HRN and version:

val optimizedMap: OptimizedMapVersionedLayers =
  OptimizedMapCatalog
    .from(optimizedMapHRN)
    .usingBaseClient(baseClient)
    .newInstance
    .version(optimizedMapVersion)

For more information on the definition of the HRN and version of the
Optimized Map for Location Library,
see the section
Catalogs HRNs and versions.

Create layer objects

The data in the
OptimizedMapLayers
object is stored in pieces (tiles) in catalog layers. To load data from a
particular layer, you can create a layer object by calling the following method:

val lengthLayer = optimizedMap.length

Each tile is uniquely identified by its
TileId.
These IDs are related the area that the data inside the tile describes, they
follow the
heretile partitioning scheme.

Layer objects expose reader methods for reading data by the
TileId.

Retrieve tiles close to a given point

To easily discover which tiles contain the data for a particular area, Location
Library provides
TileResolvers.

You can get an instance of
TileResolver
by creating a
HereTileResolver
and providing it with a
HereTileLevel
with the zoom level of the layer.

Using a
TileResolver
you can, for example, look up the tiles that cover a circle of 1000m radius
around the Brandenburger Tor as follows:

import com.here.platform.location.core.geospatial.GeoCoordinate
import com.here.platform.location.inmemory.geospatial.TileId
import com.here.platform.location.integration.herecommons.geospatial.{
  HereTileLevel,
  HereTileResolver
}

val lengthZoomLevel = optimizedMap.versionedLayers
  .versionedHereTileLayer(OptimizedMapLayers.RoadAccessLayerConfiguration.layerId)
  .zoomLevels
  .head
val tileResolver = new HereTileResolver(HereTileLevel(lengthZoomLevel))

val brandenburgerTor = GeoCoordinate(52.516268, 13.377700)
val radiusInMeters = 1000.0

val outputTiles =
  tileResolver.fromCenterAndRadius(brandenburgerTor, radiusInMeters)

val roadAccessLayer = optimizedMap.roadAccess
val readTiles = outputTiles.map(tile => (tile, roadAccessLayer.reader(tile)))

The only tile within 1000 meters of the Brandenburger Tor has the tile ID
1476150. You can search for this tile within the
Road Topology & Geometry layer.

Resolve compatible version of the Optimized Map for Location Library and the HERE Map Content

If you use both the
Optimized Map for Location Library
and the HERE Map Content, make sure that the version of the HERE Map Content is
compatible with the one of the
Optimized Map for Location Library.
The following snippet shows how to retrieve the correct HERE Map Content
catalog:

import com.here.platform.data.client.v2.main.scaladsl.DataClient

val dataClient = DataClient()
try {
  val optimizedMap = dataClient
    .catalogBuilder(optimizedMapHRN)
    .build

  println(s"The latest OM4LL version is ${optimizedMap.versionInfo.latestVersion}")

  val om4llVersion = 3305L
  val hmcVersions = optimizedMap.versionInfo.resolveDependencyVersion(
    om4llVersion,
    hereMapContentHRN
  )

  println(
    s"An OM4LL version is $om4llVersion, the compatible HMC versions are ${hmcVersions.mkString(", ")}")
} finally {
  dataClient.shutdown()
}

Properties

Properties are values attached to vertices in the routing graph including the
speed limit, accessibility by a particular type of vehicle or even the geometry
(road shape). In HERE Map Content, these are called segment attributes.

Vertex properties

Properties that always have just one value that applies for the whole vertex are
represented as
PropertyMap.
Vertex properties that are part of
Optimized Map for Location Library
are discussed in detail below.

Retrieve the vertex for a given topology segment

As mentioned in the section The Routing Graph, using
vertices is more efficient to analyze properties of a given road segment than
topology segments. The mapping layer of the
Optimized Map for Location Library
defines a mapping between vertices in the routing graph and road segments in
HERE Map Content. This section demonstrates how to convert a topology segment
into a vertex using this layer:

  1. Open
    https://platform.here.com/data/hrn:here:data::olp-here:rib-2/topology-geometry/inspect.
  2. Click a topology segment.
  3. Expand the start_node_ref object and write down its identifier.
  4. To infer the direction of the segment, click the nodes at the ends of the
    segment and compare their identifiers with the identifier of the segment
    start_node_ref.
  5. To convert the topology segment, create a TileLoader for the mapping
    layer and a
    TiledReverseHereMapContentReferencePropertyMap.
    The TiledReverseHereMapContentReferencePropertyMap consumes the partition
    that the TileLoader produces for a given tile ID. The following code
    snippet demonstrates how to retrieve the vertex that corresponds to the
    topology segment here:cm:segment:94480838 in partition 23618402:
import com.here.platform.location.integration.heremapcontent.PartitionId
import com.here.platform.location.integration.optimizedmap.geospatial.{
  HereMapContentReference,
  SegmentId
}
import com.here.platform.location.integration.optimizedmap.graph.{
  MappingTile,
  TiledReverseHereMapContentReferencePropertyMap
}

val mappingLayer = optimizedMap.mapping

val mappingPartitions: Map[TileId, Option[MappingTile]] =
  outputTiles.map(tileId => (tileId, mappingLayer.reader(tileId))).toMap

val optimizedMapMapping = new TiledReverseHereMapContentReferencePropertyMap(
  mappingPartitions,
  mappingLayer.tileResolver
)

val srcTopologySegment = HereMapContentReference(PartitionId("23618402"),
                                                 SegmentId("here:cm:segment:94480838"),
                                                 Forward)
val vertex = optimizedMapMapping(srcTopologySegment)

println(s"The topology segment $srcTopologySegment corresponds to the vertex $vertex")

Retrieve the topology segment for a given vertex

Several Location Library algorithms, such as proximity search, return vertices
instead of topology segments. Vertices have two drawbacks:

Therefore, you may need to convert vertices to a topology segment. The following
code snippet demonstrates how to perform this operation by creating a
TileLoader for the mapping layer and a
TiledHereMapContentReferencePropertyMap.
The TiledHereMapContentReferencePropertyMap consumes the partition that the
TileLoader produces for a given tile ID. The code snippet uses the vertex
retrieved in the code snippet of the previous section.

import com.here.platform.location.integration.optimizedmap.geospatial.HereMapContentReference
import com.here.platform.location.integration.optimizedmap.graph.{
  MappingTile,
  TiledHereMapContentReferencePropertyMap
}

val mappingLayer = optimizedMap.mapping

val mappingPartitions: Map[TileId, Option[MappingTile]] =
  outputTiles.map(tileId => (tileId, mappingLayer.reader(tileId))).toMap

val hereMapContentMapping = new TiledHereMapContentReferencePropertyMap(mappingPartitions)

val topologySegment = hereMapContentMapping(vertex)
val HereMapContentReference(partitionId, segmentId, direction) = topologySegment

println(s"$vertex corresponds to the segment with identifier $segmentId")
println(s"  in HERE Map Content partition $partitionId")
println(s"  travelling in $direction direction, relative to the segment")

Retrieve the geometry of a given vertex

As mentioned in The Routing Graph, a vertex
represents travel in a particular direction along a road segment. The
geometry layer in the
Optimized Map for Location Library
stores the shapes of the road segments. A tile of the layer contains the
following:

  • The road shape of each segment that crosses the tile
  • A spatial index for efficient searches within a given area

Creating a property map from geometry layer partitions is enabled by
TiledGeometryPropertyMap.
The
TiledGeometryPropertyMap
returns a directed geometry for each vertex: the points of the geometry occur
in the travel direction of this vertex.

import com.here.platform.location.core.graph.PropertyMap
import com.here.platform.location.inmemory.geospatial.{GeometryTile, PackedLineString}

val geometryLayer = optimizedMap.geometry

val geometryTiles: Iterable[(TileId, Option[GeometryTile])] =
  outputTiles.map(tileId => (tileId, geometryLayer.reader(tileId))).toMap
val geometry: PropertyMap[Vertex, PackedLineString] =
  new TiledGeometryPropertyMap(geometryTiles.toMap)

val tileId = outputTiles.head
val segmentIndex = SegmentIndex.fromVertexIndex(VertexIndex(10))

val forwardVertex = Vertex(tileId, segmentIndex.toVertexIndex(Forward))
val backwardVertex = Vertex(tileId, segmentIndex.toVertexIndex(Backward))

assert(geometry(forwardVertex).toIndexedSeq.reverse == geometry(backwardVertex).toIndexedSeq)

The length layer

The length layer provides the lengths of vertices in meters. To access the
length of a vertex, you first have to create an
TiledUndirectedPropertyMap
that contains the length data. Then use this
TiledUndirectedPropertyMap
to access the length of a given vertex.

val lengthLayer = optimizedMap.length

val lengthPartitions: Iterable[(TileId, Option[UndirectedPropertyTile[Double]])] =
  outputTiles.map(tileId => (tileId, lengthLayer.reader(tileId))).toMap
val lengthProperty: PropertyMap[Vertex, Double] =
  new TiledUndirectedPropertyMap(lengthPartitions.toMap)

val lengthVertex = Vertex(outputTiles.head, VertexIndex(10))

val length = lengthProperty(lengthVertex)
println(s"The $lengthVertex is $length meters long.")

Range-based vertex properties

From the Optimized Map for Location Library

The
Optimized Map for Location Library
contains some range based property layers that are compiled from HERE Map
Content.

One example is the roadaccess layer, which contains information on what type
of traffic can access a particular vertex.

val roadAccessType: RangeBasedPropertyMap[Vertex, RoadAccessType] =
  PropertyMaps(optimizedMap).roadAccess

val vertexHasCarAccessType: Boolean =
  roadAccessType(v, vertexFraction).get.value.intersects(RoadAccess.Automobile)

val isCarOrBusAccessible =
  PropertyMaps(optimizedMap)
    .roadAccess(RoadAccess.Automobile union RoadAccess.Bus)

val vertexIsCarOrBusAccessible: Boolean = isCarOrBusAccessible(v, vertexFraction).get.value

println(s"If $v has the car access type ($vertexHasCarAccessType) it is accessible")
println(s"for cars or buses ($vertexIsCarOrBusAccessible)")

Another example is the freeflowspeed layer, witch contains the speed at
which a car is expected to traverse the various portions of a vertex geometry,
under normal traffic conditions, expressed in km/h.

val freeFlowSpeed = PropertyMaps(optimizedMap).freeFlowSpeed
println(s"The free-flow speeds of $v are ${freeFlowSpeed(v)}")