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-data-loader-standalone" % "0.21.755",
"com.here.platform.location" %% "location-integration-optimized-map" % "0.21.755"
)
<dependencies>
<dependency>
<groupId>com.here.platform.location</groupId>
<artifactId>location-data-loader-standalone_${scala.compat.version}</artifactId>
<version>0.21.755</version>
</dependency>
<dependency>
<groupId>com.here.platform.location</groupId>
<artifactId>location-integration-optimized-map_${scala.compat.version}</artifactId>
<version>0.21.755</version>
</dependency>
</dependencies>
dependencies {
compile group: 'com.here.platform.location', name: 'location-data-loader-standalone_2.12', version:'0.21.755'
compile group: 'com.here.platform.location', name: 'location-integration-optimized-map_2.12', version:'0.21.755'
}
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 Catalog 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 CatalogFactory interface. This chapter only describes one implementation of this CatalogFactory
interface, StandaloneCatalogFactory:
import com.here.platform.location.dataloader.standalone.StandaloneCatalogFactory
val catalogFactory = new StandaloneCatalogFactory()
When you are done with manipulating catalogs, terminate the StandaloneCatalogFactory
as follows:
catalogFactory.terminate()
If you don't terminate StandaloneCatalogFactory
, your application doesn't terminate.
To create a Catalog object for the Optimized Map for Location Library, use this CatalogFactory
and the appropriate catalog HRN and version:
import com.here.platform.location.dataloader.core.Catalog
val optimizedMap: Catalog = catalogFactory.create(optimizedMapHRN, 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 TileLoaders
The data in a Catalog is stored in pieces (tiles) in catalog layers. To load data from a particular layer, you need to create a TileLoader for that layer.
You can use the Catalog to create a TileLoader that returns the data as an array of bytes.
Further, you can also create a derived TransformingTileLoader that automatically transforms (deserializes) the tile contents into an object.
import com.here.platform.location.dataloader.core.{TileLoader, TransformingTileLoader}
val layerId: String =
"length"
def decoder(b: Array[Byte]): TileType = ???
val rawLoader: TileLoader[Array[Byte]] = catalog.create(layerId)
val loader: TileLoader[TileType] = new TransformingTileLoader(rawLoader, decoder)
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.
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 a TileResolver that is compatible with a TileLoader directly from that TileLoader.
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
val brandenburgerTor = GeoCoordinate(52.516268, 13.377700)
val radiusInMeters = 1000.0
val outputTiles: Iterable[TileId] =
loader.resolver.fromCenterAndRadius(brandenburgerTor, radiusInMeters)
val tiles: Iterable[(TileId, Option[TileType])] = loader.getAll(outputTiles)
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.
Load Attributes from the Optimized Map For Location Library and HERE Map Content
This section describes how to load attributes from the Optimized Map for Location Library and from HERE Map Content.
First, to reduce the traffic on the network, you need a CacheManager. The object CacheManager contains factory methods such as withLruCache
. CacheManager.withLruCache
creates a CacheManager
with an LRU cache policy:
import com.here.platform.location.dataloader.core.caching.CacheManager
val cacheManager = CacheManager.withLruCache()
You can find the Layers available for the Optimized Map for Location Library such as the Mapping
layer in OptimizedMapLayers.
HereMapContentLayers provides the attribute Layers that are available in HERE Map Content. You can create a TileLoader
for a layer, such as the navigation attributes layer, as follows:
import com.here.platform.location.integration.heremapcontent.HereMapContentLayers
import com.here.schema.rib.v2.navigation_attributes_partition.NavigationAttributesPartition
val hereMapContent: Catalog =
catalogFactory.create(hereMapContentHRN, hereMapContentVersion)
val navigationAttributeLoader: TileLoader[NavigationAttributesPartition] =
HereMapContentLayers.NavigationAttributes.tileLoader(hereMapContent, cacheManager)
For more information on the definition of the HRN and version of HERE Map Content, see the section Catalogs HRNs and versions.
Use the TileLoader
to make the attributes format compatible with the Location Library. For more information, see the section On-the-Fly Compilation from HERE Map Content.
If you use attributes from both the Optimized Map for Location Library and from HERE Map Content, make sure that the version of the HERE Map Content is the one that was used to compile the Optimized Map for Location Library. The following snippet shows how to retrieve the correct HERE Map Content catalog:
val hereMapContent: Catalog = optimizedMap.resolveDependency(hereMapContentHRN)
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 PropertyMaps. 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:
- Open https://platform.here.com/data/hrn:here:data::olp-here:rib-2/topology-geometry/inspect.
- Click a topology segment.
- Expand the
start_node_ref
object and write down its identifier. - 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
. -
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.OptimizedMapLayers
import com.here.platform.location.integration.optimizedmap.geospatial.{
HereMapContentReference,
SegmentId
}
import com.here.platform.location.integration.optimizedmap.graph.{
MappingTile,
TiledReverseHereMapContentReferencePropertyMap
}
val mappingLoader = OptimizedMapLayers.Mapping.tileLoader(optimizedMap, cacheManager)
val mappingPartitions: Map[TileId, Option[MappingTile]] = mappingLoader
.getAll(outputTiles)
.toMap
val optimizedMapMapping = new TiledReverseHereMapContentReferencePropertyMap(
mappingPartitions,
mappingLoader.resolver
)
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.OptimizedMapLayers
import com.here.platform.location.integration.optimizedmap.geospatial.HereMapContentReference
import com.here.platform.location.integration.optimizedmap.graph.{
MappingTile,
TiledHereMapContentReferencePropertyMap
}
val mappingLoader = OptimizedMapLayers.Mapping.tileLoader(optimizedMap, cacheManager)
val mappingPartitions: Map[TileId, Option[MappingTile]] = mappingLoader
.getAll(outputTiles)
.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 geometryLoader =
OptimizedMapLayers.Geometry.tileLoader(optimizedMap, cacheManager)
val geometryTiles: Iterable[(TileId, Option[GeometryTile])] =
geometryLoader.getAll(outputTiles)
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 lengthLoader = OptimizedMapLayers.Length.tileLoader(optimizedMap, cacheManager)
val lengthPartitions: Iterable[(TileId, Option[UndirectedPropertyTile[Double]])] =
lengthLoader.getAll(outputTiles)
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
In HERE Map Content, value ranges represent properties that can potentially cover many road segments or just parts of them. The Optimized Map for Location Library contains a simplified representation of these value ranges comprising a property value as well as a start and end fraction along a single vertex's length. This still allows you to attach a particular property value to only parts of a vertex but eliminates the complexity caused by multi-vertex or even multi-partition ranges.
A RangeBasedPropertyMap can represent Range Based Properties. You can use this method to retrieve all property ranges defined on a vertex or the particular range in effect at some fraction of the vertex.
import com.here.platform.location.core.graph.RangeBasedProperty
val v = Vertex(outputTiles.head, VertexIndex(17))
val vertexFraction = 0.5
val rangesOnVertex: Seq[RangeBasedProperty[SpeedLimitKmh]] = propertyMap(v)
val speedLimit: RangeBasedProperty[SpeedLimitKmh] =
propertyMap(v, vertexFraction).get
println(s"The speed limit on $v at fraction $vertexFraction is ${speedLimit.value} km/h.")
println(s"There are ${rangesOnVertex.length} value ranges defined on this vertex.")
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.roadAccess(optimizedMap, cacheManager)
val vertexHasCarAccessType: Boolean =
roadAccessType(v, vertexFraction).get.value.intersects(RoadAccess.Automobile)
val isCarOrBusAccessible =
PropertyMaps.roadAccess(optimizedMap,
cacheManager,
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.freeFlowSpeed(optimizedMap, cacheManager)
println(s"The free-flow speeds of $v are ${freeFlowSpeed(v)}")
On-the-Fly Compilation from HERE Map Content
There are properties available as HERE Map Content Segment Attributes (for example, in the road layer) but not part of the precompiled Optimized Map for Location Library. You can compile these properties to RangeBasedPropertyMaps on the fly.
This requires the following data:
-
The Attribute layer from HERE Map Content containing the property to be compiled:
val attributeLoader =
HereMapContentLayers.NavigationAttributes.tileLoader(hereMapContent, cacheManager)
-
The PartitionResolver for that attribute layer:
val partitionResolver =
HereMapContentLayers.NavigationAttributes.partitionResolver(optimizedMap, cacheManager)
Note
The PartitionResolver is required because of the way HERE Map Content stores attributes. It uses information from the Optimized Map for Location Library to determine which partitions from the attribute layer are necessary to compile properties for a particular graph partition.
-
The Mapping layer from the Optimized Map for Location Library, described above
-
The partitions of the graph whose properties you want to query (the output tiles)
Now you can construct a RangeBasedPropertyMapFactory that allows you to compile any attribute stored in the chosen attribute layer to a property map for the requested output partitions.
val propertyMapFactory =
new RangeBasedPropertyMapFactory(
attributeLoader,
mappingLoader,
partitionResolver
)(outputTiles)
To identify which attribute from the attribute layer you are interested in, you need to create an AttributeAccessor.
import com.here.platform.location.compilation.heremapcontent.{
AttributeAccessor,
AttributeAccessors
}
import com.here.schema.rib.v2.common_attributes.SpeedLimitAttribute
import com.here.schema.rib.v2.navigation_attributes_partition.NavigationAttributesPartition
type SpeedLimitKmh = Int
val attributeAccessor: AttributeAccessor[NavigationAttributesPartition, SpeedLimitKmh] =
AttributeAccessors
.forHereMapContentSegmentAnchor[NavigationAttributesPartition,
SpeedLimitAttribute,
SpeedLimitKmh](
_.speedLimit,
_.value
)
With that, you can then compile the final property map.
import com.here.platform.location.core.graph.RangeBasedPropertyMap
val propertyMap: RangeBasedPropertyMap[Vertex, SpeedLimitKmh] =
propertyMapFactory.createPropertyMapFor(attributeAccessor)