道路属性をセグメント形状に関連付けます
目的: HERE Map Content のアトリビューション参照モデルに従って、道路属性値をセグメントジオメトリに関連付ける方法を理解します。
複雑さ: 中級者です
所要時間: 45 分
前提条件: 資格情報 の確認、 プロジェクトでの作業の整理を行います
ソースコード: ダウンロード
この例では、道路の属性値を読み取り、 HERE Map Content のアトリビューション参照モデルに従ってセグメントのジオメトリに関連付ける方法を示します。
アプリケーションは 、データ クライアント ライブラリを使用 して、 HERE Map Content の道路属性レイヤーの 1 つのパーティションから開始します。
データ クライアント ライブラリ は、対応するセグメント形状を舗装済みおよび未舗装のセグメントに集約します。 出力は単一の GeoJSON タイルで、舗装されたセグメントは緑色、未舗装のセグメントは赤色で表示されます。
アプリケーションが処理する単一の GeoJSON パーティション 17302687
。
このタイルは 、ポータルの HERE Map Content の道路属性レイヤーで検査できます。
パーティションのジオメトリは 、道路トポロジおよびジオメトリレイヤーにあります。
出力カタログを作成します
出力カタログには 、単一の GeoJSON レイヤーが含まれています。 次のコード スニペットは、設定ファイルを示しています。 {{YOUR_CATALOG_ID}}
などの独自の ID で置き換え {{YOUR_USERNAME}}-road-attr-walkthru
ます。
{
"id": "{{YOUR_CATALOG_ID}}",
"name": "Paved and unpaved road segments from Road Attribute Walkthrough",
"summary": "Paved and unpaved road segments from Road Attribute Walkthrough",
"description": "Paved and unpaved road segments from Road Attribute Walkthrough",
"layers": [
{
"id": "roadsegments",
"name": "roadsegments",
"summary": "Paved and unpaved road segments",
"description": "Paved and unpaved road segments",
"contentType": "application/vnd.geo+json",
"layerType": "versioned",
"digest": "sha-1",
"volume": {
"volumeType": "durable"
},
"partitioning": {
"scheme": "heretile",
"tileLevels": [12]
},
"coverage": {
"adminAreas": [
"NZ"
]
}
}
]
}
OLP CLI では、データサービスで自身を認証するために有効な HERE Credentials のセットが必要 です。そのため、「資格情報の確認」のチュートリアルで予期した結果が返されることを確認してください。
まだダウンロードしていない場合は、 Java and Scala の例と CLI をダウンロードして解凍します。
tools/OLP_CLI
解凍したファイルのフォルダをに追加${PATH}
します。次のコマンドを使用してカタログを作成します。
olp catalog create {{YOUR_CATALOG_ID}} \
"Paved and unpaved road segments from Road Attribute Walkthrough" \
--config output-catalog.json \
--scope {{YOUR_PROJECT_HRN}}
CLI は次のように戻ります。
Catalog {{YOUR_CATALOG_HRN}} has been created.
注
レルムで請求タグが必要な場合 は、layer
セクションにbillingTags: ["YOUR_BILLING_TAG"]
プロパティを追加して設定 ファイルを更新します。
Maven プロジェクトを設定します
プロジェクトの次のフォルダー構造を作成します。
road-attr-walkthru
└── src
└── main
├── java
└── scala
この操作は、次の bash
1 つのコマンドで実行できます。
mkdir -p road-attr-walkthru/src/main/{java,scala}
この例の POM は、最初の Maven の例の POM と同じですが、その dependencies
'’ repositories
および build
のセクションが異なります。
依存関係は次のとおりです。
<dependency>
<groupId>com.here.platform.data.client</groupId>
<artifactId>data-client_${scala.compat.version}</artifactId>
</dependency>
<dependency>
<groupId>com.here.platform.data.client</groupId>
<artifactId>data-engine_${scala.compat.version}</artifactId>
</dependency>
<dependency>
<groupId>com.here.platform.pipeline</groupId>
<artifactId>pipeline-interface_${scala.compat.version}</artifactId>
</dependency>
<dependency>
<groupId>com.here.schema.rib</groupId>
<artifactId>road-attributes_v2_scala_${scala.compat.version}</artifactId>
</dependency>
<dependency>
<groupId>com.here.schema.rib</groupId>
<artifactId>topology-geometry_v2_scala_${scala.compat.version}</artifactId>
</dependency>
<dependency>
<groupId>org.json4s</groupId>
<artifactId>json4s-jackson_${scala.compat.version}</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>com.here.schema.rib</groupId>
<artifactId>road-attributes_v2_java</artifactId>
</dependency>
<dependency>
<groupId>com.here.schema.rib</groupId>
<artifactId>topology-geometry_v2_java</artifactId>
</dependency>
アプリケーションは HERE Map Content スキーマからの Scala バインディングを使用するため repositories
、スキーマアーティファクトを解決するには、次のセクションを追加する必要があります。
<repositories>
<repository>
<id>HERE_PLATFORM_ARTIFACT</id>
<layout>default</layout>
<url>here+https://artifact.api.platform.here.com/v1/artifact</url>
</repository>
</repositories>
extensions
また、の以下のセクションも必要 build
です。
<extensions>
<extension>
<groupId>com.here.platform.artifact</groupId>
<artifactId>artifact-wagon</artifactId>
<version>${artifact.wagon.version}</version>
</extension>
</extensions>
スキーマを解決する Artifact Service の詳細について は、依存関係管理のドキュメントの「 Artifact Service 」を参照してください。
アプリケーションを実装します
それぞれの Scala および Java の実装は次のとおりです。
/*
* Copyright (c) 2018-2023 HERE Europe B.V.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import akka.actor.CoordinatedShutdown.UnknownReason
import akka.actor.{ActorSystem, CoordinatedShutdown}
import com.here.hrn.HRN
import com.here.platform.data.client.engine.scaladsl.{DataEngine, WriteEngine}
import com.here.platform.data.client.model.VersionDependency
import com.here.platform.data.client.scaladsl.{DataClient, NewPartition, PublishApi, QueryApi}
import com.here.platform.pipeline.PipelineContext
import com.here.schema.geometry.v2.geometry.LineString
import com.here.schema.rib.v2.common.Reference
import com.here.schema.rib.v2.road_attributes_partition.RoadAttributesPartition
import com.here.schema.rib.v2.topology_geometry_partition.TopologyGeometryPartition
import com.typesafe.config.{Config, ConfigFactory, ConfigValueFactory}
import org.json4s.JsonDSL._
import org.json4s.jackson.JsonMethods._
import scala.concurrent._
import scala.concurrent.duration._
object RoadAttrWalkthruScala {
def main(args: Array[String]): Unit = {
// Initialize the Akka Actor System used by the Data Client Library.
val config: Config = ConfigFactory
.empty()
.withValue("here.platform.data-client.endpoint-locator.discovery-service-env",
ConfigValueFactory.fromAnyRef("custom"))
.withValue(
"here.platform.data-client.endpoint-locator.discovery-service-url",
// Please use https://api-lookup.data.api.platform.hereolp.cn URL for China Environment
// We should define a custom URL, specific to China Environment, for a discovery service
// endpoint that allows discovering various Data Service APIs like publish, metadata, query, etc.
ConfigValueFactory.fromAnyRef("https://api-lookup.data.api.platform.here.com")
)
implicit lazy val actorSystem: ActorSystem =
ActorSystem.create("RoadAttrWalkthruScalaExampleApp", config)
try {
val pipelineContext = new PipelineContext()
val hereMapContentHrn = pipelineContext.config.inputCatalogs("hereMapContent")
val hereMapContentVersion = pipelineContext.job.get.inputCatalogs("hereMapContent").version
// Choosing a Chatham Islands partition in New Zealand to keep things simple.
// Please use partition 23551658 in Tibet for China Environment.
// Feel free to replace this with one or more other partitions of interest.
val roadAttrPartitionNames = Seq("17302687")
val outputHrn = pipelineContext.config.outputCatalog
// Initialize the I/O for the catalog.
val queryApi = DataClient().queryApi(hereMapContentHrn)
val readEngine = DataEngine().readEngine(hereMapContentHrn)
val writeEngine = DataEngine().writeEngine(outputHrn)
val publishApi = DataClient().publishApi(outputHrn)
val waitDuration = 60 seconds
// Retrieve the metadata for the road-attribute partition(s).
val roadAttrMetadata = Await.result(queryApi.getPartitionsById(hereMapContentVersion,
"road-attributes",
roadAttrPartitionNames),
waitDuration)
roadAttrMetadata.foreach { metadata =>
// For users using platform.here.com:
// https://here-tech.skawa.fun/documentation/data-client-library/dev_guide/client/get-data.html
// https://here-tech.skawa.fun/documentation/here-map-content/topics/road-layer.html
// For users using platform.hereolp.cn:
// https://here-tech.skawa.fun/cn/documentation/data-client-library/dev_guide/client/get-data.html
// https://repo.platform.hereolp.cn/artifactory/open-location-platform-docs/Data_Specifications/HERE_Map_Content/HERE_Map_Content_OLP_China_v2019-05_Data_Specification.pdf
// Retrieve the payload for the road-attribute partition and decode its contents.
val roadAttributesPartition =
Await.result(readEngine.get(metadata, RoadAttributesPartition.parseFrom), waitDuration)
// For users using platform.here.com:
// https://here-tech.skawa.fun/documentation/here-map-content/topics_api/com.here.schema.rib.v2.physicalattribute.html
// For users using platform.hereolp.cn:
// https://repo.platform.hereolp.cn/artifactory/open-location-platform-docs/Data_Specifications/HERE_Map_Content/HERE_Map_Content_OLP_China_v2019-05_Data_Specification.pdf
// Separate the physical attribute values combinations into those which have paved=true, and those which have paved=false.
val (paved, unpaved) = roadAttributesPartition.physical.partition(_.paved)
// For users using platform.here.com:
// https://here-tech.skawa.fun/documentation/here-map-content/topics/anchor.html
// For users using platform.hereolp.cn:
// https://repo.platform.hereolp.cn/artifactory/open-location-platform-docs/Data_Specifications/HERE_Map_Content/HERE_Map_Content_OLP_China_v2019-05_Data_Specification.pdf
// Get the segment anchor indices for the paved and unpaved segments.
val pavedSegmentAnchorIndices = paved.flatMap(_.segmentAnchorIndex)
val unpavedSegmentAnchorIndices = unpaved.flatMap(_.segmentAnchorIndex)
// Follow the segment anchor references to get the name of the partition(s)
// which contain the actual segment geometry within the topology-geometry layer.
val pavedTopologyPartitions =
getTopologyPartitionNames(pavedSegmentAnchorIndices, roadAttributesPartition)
val unpavedTopologyPartitions =
getTopologyPartitionNames(unpavedSegmentAnchorIndices, roadAttributesPartition)
// Retrieve the metadata for the referenced topology-geometry partitions.
val topoMetadata = Await.result(
queryApi.getPartitionsById(hereMapContentVersion,
"topology-geometry",
(pavedTopologyPartitions ++ unpavedTopologyPartitions).toSeq),
waitDuration)
// Retrieve the payloads and decode the content of the referenced topology-geometry partitions.
// We need to do this because it is not guaranteed that every segment anchor refers to a segment
// that is in the topology-geometry partition that has the same name as the road-attributes
// partition which contains the segment anchor.
val topoPartitionsByName: Map[String, TopologyGeometryPartition] = topoMetadata.map {
topoMeta =>
topoMeta.partition -> Await.result(readEngine.get(topoMeta,
TopologyGeometryPartition.parseFrom),
waitDuration)
}(collection.breakOut)
// Follow the segment anchor references to the decoded topology-geometry content to get the geometry
// for each paved and unpaved segment.
val pavedGeo =
getGeo(pavedSegmentAnchorIndices, roadAttributesPartition, topoPartitionsByName)
val unpavedGeo =
getGeo(unpavedSegmentAnchorIndices, roadAttributesPartition, topoPartitionsByName)
// Write out the decoded geometry as GeoJSON.
publishGeoJson(pavedGeo,
unpavedGeo,
writeEngine,
publishApi,
queryApi,
hereMapContentHrn,
hereMapContentVersion,
waitDuration)
}
} finally {
Await.result(CoordinatedShutdown(actorSystem).run(UnknownReason), Duration.Inf)
}
}
// Gets the segment references for a given segment anchor index in a road attributes partition.
private def getSegmentRefs(roadAttributesPartition: RoadAttributesPartition,
segmentAnchorIdx: Int): Iterable[Reference] =
roadAttributesPartition.segmentAnchor(segmentAnchorIdx).orientedSegmentRef.flatMap(_.segmentRef)
// Gets the topology partition names referenced by the given segment anchor indices.
private def getTopologyPartitionNames(
segmentAnchorIndices: Iterable[Int],
roadAttributesPartition: RoadAttributesPartition): Set[String] =
segmentAnchorIndices.flatMap { idx =>
getSegmentRefs(roadAttributesPartition, idx).map(_.partitionName)
}(collection.breakOut)
// Follow the segment anchor references to the decoded topology-geometry content to get the geometry
// for each paved and unpaved segment.
private def getGeo(segmentAnchorIndices: Iterable[Int],
roadAttributesPartition: RoadAttributesPartition,
topoPartitionsByName: Map[String, TopologyGeometryPartition])
: Map[String, Iterable[LineString]] =
segmentAnchorIndices
.flatMap { idx =>
// Follow the segment anchor reference to get the corresponding topology-geometry partition.
getSegmentRefs(roadAttributesPartition, idx).flatMap { ref =>
topoPartitionsByName(ref.partitionName).segment
// For each segment anchor reference, get the segment from topology-geometry which matches its identifier.
.filter(_.identifier == ref.identifier)
.flatMap(_.geometry)
.map(lineString => ref.partitionName -> lineString)
}
}
// Group the LineStrings under their respective topology partitions.
.groupBy(_._1)
.map(pair => pair._1 -> pair._2.map(_._2))
// Formats paved and unpaved LineStrings as GeoJSON.
private def makeGeoJsonFeature(paved: Boolean)(lineString: LineString) = {
def makeGeoCollection[T](geometries: Iterable[T], f: T => String) =
s"[${geometries.map(f).mkString(",")}]"
def makeGeoCoord(point: com.here.schema.geometry.v2.geometry.Point) =
s"[${point.longitude}, ${point.latitude}]"
def makeGeoJsonLineString(lineString: LineString) =
("type" -> "LineString") ~
("coordinates" -> parse(makeGeoCollection(lineString.point, makeGeoCoord)))
// Give paved and unpaved segments tooltips indicating paved or unpaved, and color
// them green and red, respectively.
("type" -> "Feature") ~
("properties" -> ("tooltip" -> (if (paved) "paved" else "unpaved")) ~
("width" -> 3.0) ~ ("style" -> ("color" -> (if (paved) "#02F93E" else "#FF3333")))) ~
("geometry" -> makeGeoJsonLineString(lineString))
}
// Write out the decoded geometry as GeoJSON.
private def publishGeoJson(pavedGeo: Map[String, Iterable[LineString]],
unpavedGeo: Map[String, Iterable[LineString]],
writeEngine: WriteEngine,
publishApi: PublishApi,
queryApi: QueryApi,
inputHrn: HRN,
inputVersion: Long,
waitDuration: Duration): Unit = {
val layerId = "roadsegments";
val commitPartitions = (pavedGeo.keySet ++ unpavedGeo.keySet).map { key =>
val pavedGeoJson = pavedGeo.getOrElse(key, Nil).map(makeGeoJsonFeature(paved = true))
val unpavedGeoJson = unpavedGeo.getOrElse(key, Nil).map(makeGeoJsonFeature(paved = false))
val geoJsonString =
compact(
render(
("type" -> "FeatureCollection") ~ ("features" -> (pavedGeoJson ++ unpavedGeoJson))))
val newPartition = NewPartition(
partition = key,
layer = layerId,
data = NewPartition.ByteArrayData(geoJsonString.getBytes("UTF-8"))
)
Await.result(writeEngine.put(newPartition), waitDuration)
}
// Write the HERE Map Content catalog's dependencies as indirect dependencies of
// our output catalog, and the HERE Map Content catalog itself as a direct
// dependency. This is good practice so that consumers of this catalog can
// perform dependency analysis if needed, for example, when scheduling downstream
// pipelines, and performing incremental processing.
val dependencies = Await
.result(queryApi.getVersion(inputVersion), waitDuration)
.dependencies
.map(_.copy(direct = false)) ++ Seq(VersionDependency(inputHrn, inputVersion, direct = true))
val baseVersion = Await.result(publishApi.getBaseVersion(), waitDuration)
Await.result(publishApi.publishBatch2(baseVersion,
Some(Seq(layerId)),
dependencies,
commitPartitions.iterator),
waitDuration)
}
}
/*
* Copyright (c) 2018-2023 HERE Europe B.V.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import akka.actor.ActorSystem;
import akka.actor.CoordinatedShutdown;
import akka.japi.Pair;
import com.here.hrn.HRN;
import com.here.platform.data.client.engine.javadsl.DataEngine;
import com.here.platform.data.client.engine.javadsl.ReadEngine;
import com.here.platform.data.client.engine.javadsl.WriteEngine;
import com.here.platform.data.client.javadsl.*;
import com.here.platform.data.client.model.VersionDependency;
import com.here.platform.pipeline.PipelineContext;
import com.here.schema.geometry.v2.GeometryOuterClass.LineString;
import com.here.schema.rib.v2.Anchor;
import com.here.schema.rib.v2.Common;
import com.here.schema.rib.v2.RoadAttributes;
import com.here.schema.rib.v2.RoadAttributesPartitionOuterClass.RoadAttributesPartition;
import com.here.schema.rib.v2.TopologyGeometry;
import com.here.schema.rib.v2.TopologyGeometryPartitionOuterClass.TopologyGeometryPartition;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigValueFactory;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class RoadAttrWalkthruJava {
public static void main(String[] args)
throws InterruptedException, ExecutionException, TimeoutException {
// Initialize the Akka Actor System used by the Data Client Library.
Config config = ConfigFactory.empty();
config =
config.withValue(
"here.platform.data-client.endpoint-locator.discovery-service-env",
ConfigValueFactory.fromAnyRef("custom"));
config =
config.withValue(
"here.platform.data-client.endpoint-locator.discovery-service-url",
// Please use https://api-lookup.data.api.platform.hereolp.cn URL for China Environment
// We should define a custom URL, specific to China Environment, for a discovery service
// endpoint that allows discovering various Data Service APIs like publish, metadata,
// query, etc.
ConfigValueFactory.fromAnyRef("https://api-lookup.data.api.platform.here.com"));
ActorSystem actorSystem = ActorSystem.create("RoadAttrWalkthruJavaExampleApp", config);
try {
PipelineContext pipelineContext = new PipelineContext();
HRN hereMapContentHrn = pipelineContext.config().getInputCatalogs().get("hereMapContent");
long hereMapContentVersion =
pipelineContext.getJob().get().getInputCatalogs().get("hereMapContent").version();
// Choosing a Chatham Islands partition in New Zealand to keep things simple.
// Please use partition 23551658 in Tibet for China Environment.
// Feel free to replace this with one or more other partitions of interest.
List<String> roadAttrPartitionNames = Collections.singletonList("17302687");
HRN outputHrn = pipelineContext.config().getOutputCatalog();
// Initialize the I/O for the catalog.
QueryApi queryApi = DataClient.get(actorSystem).queryApi(hereMapContentHrn);
ReadEngine readEngine = DataEngine.get(actorSystem).readEngine(hereMapContentHrn);
WriteEngine writeEngine = DataEngine.get(actorSystem).writeEngine(outputHrn);
PublishApi publishApi = DataClient.get(actorSystem).publishApi(outputHrn);
// Retrieve the metadata for the road-attribute partition(s).
List<Partition> roadAttrMetadata =
queryApi
.getPartitionsById(
hereMapContentVersion,
"road-attributes",
roadAttrPartitionNames,
Collections.emptySet())
.toCompletableFuture()
.get(60, TimeUnit.SECONDS);
roadAttrMetadata.forEach(
metadata -> {
// For users using platform.here.com:
// https://here-tech.skawa.fun/documentation/data-client-library/dev_guide/client/get-data.html
// https://here-tech.skawa.fun/documentation/here-map-content/topics/road-layer.html
// For users using platform.hereolp.cn:
// https://here-tech.skawa.fun/cn/documentation/data-client-library/dev_guide/client/get-data.html
// https://repo.platform.hereolp.cn/artifactory/open-location-platform-docs/Data_Specifications/HERE_Map_Content/HERE_Map_Content_OLP_China_v2019-05_Data_Specification.pdf
// Retrieve the payload for the road-attribute partition and decode its contents.
RoadAttributesPartition roadAttributesPartition =
readEngine
.get(metadata, RoadAttributesPartition::parseFrom)
.toCompletableFuture()
.join();
// For users using platform.here.com:
// https://here-tech.skawa.fun/documentation/here-map-content/topics_api/com.here.schema.rib.v2.physicalattribute.html
// For users using platform.hereolp.cn:
// https://repo.platform.hereolp.cn/artifactory/open-location-platform-docs/Data_Specifications/HERE_Map_Content/HERE_Map_Content_OLP_China_v2019-05_Data_Specification.pdf
// Separate the physical attribute values combinations into those which have paved=true,
// and those which have paved=false.
Map<Boolean, List<RoadAttributes.PhysicalAttribute>> attrsByPaved =
roadAttributesPartition
.getPhysicalList()
.stream()
.collect(Collectors.partitioningBy(RoadAttributes.PhysicalAttribute::getPaved));
List<RoadAttributes.PhysicalAttribute> paved = attrsByPaved.get(true);
List<RoadAttributes.PhysicalAttribute> unpaved = attrsByPaved.get(false);
// For users using platform.here.com:
// https://here-tech.skawa.fun/documentation/here-map-content/topics/anchor.html
// For users using platform.hereolp.cn:
// https://repo.platform.hereolp.cn/artifactory/open-location-platform-docs/Data_Specifications/HERE_Map_Content/HERE_Map_Content_OLP_China_v2019-05_Data_Specification.pdf
// Get the segment anchor indices for the paved and unpaved segments.
List<Integer> pavedSegmentAnchorIndices =
paved
.stream()
.flatMap(attr -> attr.getSegmentAnchorIndexList().stream())
.collect(Collectors.toList());
List<Integer> unpavedSegmentAnchorIndices =
unpaved
.stream()
.flatMap(attr -> attr.getSegmentAnchorIndexList().stream())
.collect(Collectors.toList());
// Follow the segment anchor references to get the name of the partition(s)
// which contain the actual segment geometry within the topology-geometry layer.
Set<String> pavedTopologyPartitions =
getTopologyPartitionNames(roadAttributesPartition, pavedSegmentAnchorIndices);
Set<String> unpavedTopologyPartitions =
getTopologyPartitionNames(roadAttributesPartition, unpavedSegmentAnchorIndices);
// Retrieve the metadata for the referenced topology-geometry partitions.
Set<String> allTopoPartitions =
Stream.concat(pavedTopologyPartitions.stream(), unpavedTopologyPartitions.stream())
.collect(Collectors.toSet());
List<Partition> topoMetadata =
queryApi
.getPartitionsById(
hereMapContentVersion,
"topology-geometry",
new ArrayList<>(allTopoPartitions),
Collections.emptySet())
.toCompletableFuture()
.join();
// Retrieve the payloads and decode the content of the referenced topology-geometry
// partitions.
Map<String, TopologyGeometryPartition> topoPartitionsByName =
topoMetadata
.stream()
.map(
topoMeta ->
new Pair<>(
topoMeta.getPartition(),
readEngine
.get(topoMeta, TopologyGeometryPartition::parseFrom)
.toCompletableFuture()
.join()))
.collect(Collectors.toMap(Pair::first, Pair::second));
// Follow the segment anchor references to the decoded topology-geometry content to get
// the geometry
// for each paved and unpaved segment.
Map<String, List<LineString>> pavedGeo =
getGeo(topoPartitionsByName, roadAttributesPartition, pavedSegmentAnchorIndices);
Map<String, List<LineString>> unpavedGeo =
getGeo(topoPartitionsByName, roadAttributesPartition, unpavedSegmentAnchorIndices);
// Write out the decoded geometry as GeoJSON.
publishGeoJson(
pavedGeo,
unpavedGeo,
writeEngine,
publishApi,
queryApi,
hereMapContentHrn,
hereMapContentVersion);
});
} finally {
shutdownActorSystem(actorSystem);
}
}
private static void shutdownActorSystem(ActorSystem actorSystem) {
CoordinatedShutdown.get(actorSystem)
.runAll(CoordinatedShutdown.unknownReason())
.toCompletableFuture()
.join();
}
// Gets the segment references for a given segment anchor index in a road attributes partition.
private static Stream<Common.Reference> getSegmentRefs(
RoadAttributesPartition roadAttributesPartition, Integer segmentAnchorIdx) {
return roadAttributesPartition
.getSegmentAnchor(segmentAnchorIdx)
.getOrientedSegmentRefList()
.stream()
.map(Anchor.SegmentAnchor.OrientedSegmentReference::getSegmentRef);
}
// Follow the segment anchor references to get the name of the partition(s)
// which contain the actual segment geometry within the topology-geometry layer.
// We need to do this because it is not guaranteed that every segment anchor refers to a segment
// that is in the topology-geometry partition that has the same name as the road-attributes
// partition which contains the segment anchor.
private static Set<String> getTopologyPartitionNames(
RoadAttributesPartition roadAttributesPartition, List<Integer> segmentAnchorIndices) {
return segmentAnchorIndices
.stream()
.flatMap(idx -> getSegmentRefs(roadAttributesPartition, idx))
.map(Common.Reference::getPartitionName)
.collect(Collectors.toSet());
}
// Follow the segment anchor references to the decoded topology-geometry content to get the
// geometry
// for each paved and unpaved segment.
private static Map<String, List<LineString>> getGeo(
Map<String, TopologyGeometryPartition> topoPartitionsByName,
RoadAttributesPartition roadAttributesPartition,
List<Integer> segmentAnchorIndices) {
return segmentAnchorIndices
.stream()
// Follow the segment anchor reference to get the corresponding topology-geometry partition.
.flatMap(idx -> getSegmentRefs(roadAttributesPartition, idx))
.flatMap(
ref ->
topoPartitionsByName
.get(ref.getPartitionName())
.getSegmentList()
.stream()
// For each segment anchor reference, get the segment from
// topology-geometry which matches its identifier.
.filter(seg -> seg.getIdentifier().equals(ref.getIdentifier()))
.map(TopologyGeometry.Segment::getGeometry)
.map(lineString -> new Pair<>(ref.getPartitionName(), lineString)))
// Group the LineStrings under their respective topology partitions.
.collect(Collectors.groupingBy(Pair::first))
.entrySet()
.stream()
.map(
pair ->
new Pair<>(
pair.getKey(),
pair.getValue().stream().map(Pair::second).collect(Collectors.toList())))
.collect(Collectors.toMap(Pair::first, Pair::second));
}
// Formats paved and unpaved LineStrings as GeoJSON.
private static String makeGeoJsonFeature(boolean paved, LineString lineString) {
StringJoiner coordJoiner = new StringJoiner(",", "[", "]");
List<String> pointsAsString =
lineString
.getPointList()
.stream()
.map(point -> "[" + point.getLongitude() + ", " + point.getLatitude() + "]")
.collect(Collectors.toList());
pointsAsString.forEach(coordJoiner::add);
// Give paved and unpaved segments tooltips indicating paved or unpaved, and color
// them green and red, respectively.
String tooltip = paved ? "paved" : "unpaved";
String color = paved ? "\"#02F93E\"" : "\"#FF3333\"";
return "{ \"type\": \"Feature\", \"geometry\": "
+ "{ \"type\": \"LineString\", \"coordinates\": "
+ coordJoiner.toString()
+ "}"
+ ", \"properties\": { \"tooltip\": \""
+ tooltip
+ "\""
+ ", \"width\" : 3.0"
+ ", \"style\": { \"color\": "
+ color
+ "}"
+ "} }";
}
// Write out the decoded geometry as GeoJSON.
private static void publishGeoJson(
Map<String, List<LineString>> pavedGeo,
Map<String, List<LineString>> unpavedGeo,
WriteEngine writeEngine,
PublishApi publishApi,
QueryApi queryApi,
HRN inputHrn,
Long inputVersion) {
Set<String> allGeoKeys =
Stream.concat(pavedGeo.keySet().stream(), unpavedGeo.keySet().stream())
.collect(Collectors.toSet());
String layerId = "roadsegments";
List<CommitPartition> commitPartitions =
allGeoKeys
.stream()
.map(
key -> {
List<String> pavedGeoJson =
pavedGeo
.getOrDefault(key, Collections.emptyList())
.stream()
.map(lineString -> makeGeoJsonFeature(true, lineString))
.collect(Collectors.toList());
List<String> unpavedGeoJson =
unpavedGeo
.getOrDefault(key, Collections.emptyList())
.stream()
.map(lineString -> makeGeoJsonFeature(false, lineString))
.collect(Collectors.toList());
List<String> allGeoJson =
Stream.concat(pavedGeoJson.stream(), unpavedGeoJson.stream())
.collect(Collectors.toList());
String geoJsonString =
allGeoJson
.stream()
.collect(
Collectors.joining(
",", "{ \"type\": \"FeatureCollection\", \"features\": [", "]}"));
NewPartition newPartition =
new NewPartition.Builder()
.withPartition(key)
.withLayer(layerId)
.withData(geoJsonString.getBytes())
.build();
return writeEngine.put(newPartition).toCompletableFuture().join();
})
.collect(Collectors.toList());
// Write the HERE Map Content catalog's dependencies as indirect dependencies of
// our output catalog, and the HERE Map Content catalog itself as a direct
// dependency. This is good practice so that consumers of this catalog can
// perform dependency analysis if needed, for example, when scheduling downstream
// pipelines, and performing incremental processing.
List<VersionDependency> dependencies =
queryApi
.getVersion(inputVersion)
.toCompletableFuture()
.join()
.getDependencies()
.stream()
.map(dep -> new VersionDependency(dep.hrn(), dep.version(), false))
.collect(Collectors.toList());
dependencies.add(new VersionDependency(inputHrn, inputVersion, true));
OptionalLong baseVersion = publishApi.getBaseVersion().toCompletableFuture().join();
publishApi
.publishBatch2(
baseVersion,
Optional.of(Collections.singletonList(layerId)),
dependencies,
commitPartitions.iterator())
.toCompletableFuture()
.join();
}
}
アプリケーションを設定します
アプリケーションのパイプライン設定ファイルは次のとおりです。
pipeline-config.conf
このファイルでは、アプリケーションで必要な各入力および出力カタログについて HERE リソースネーム が宣言されています。` を、上で作成した出力カタログの HERE リソースネーム で置き換えます。
pipeline.config {
output-catalog {hrn = "{{YOUR_CATALOG_HRN}}"}
input-catalogs {
//Please, use hrn:here-cn:data::olp-cn-here:here-map-content-china-2 on China Environment
hereMapContent {hrn = "hrn:here:data::olp-here:rib-2"}
}
}
このチュートリアルでは、パブリックカタログ( HERE Map Content カタログ)を使用します。 カタログは、まずプロジェクトにリンクして、プロジェクト内で使用する必要があります。 これを行うには {{YOUR_PROJECT_HRN}}
、プレースホルダーを置き換えて、次のコマンドを実行します。
olp project resource link {{YOUR_PROJECT_HRN}} hrn:here:data::olp-here:rib-2
コマンドが成功すると、 CLI は次のメッセージを返します。
Project resource hrn:here:data::olp-here:rib-2 has been linked.
この pipeline-job.conf
ファイルは、アプリケーションで必要な各入力および出力カタログのバージョンを宣言します。
pipeline.job.catalog-versions {
output-catalog {base-version = -1}
input-catalogs {
hereMapContent {
processing-type = "reprocess"
// Please, use "version = 0" on China Environment
version = 4
}
}
}
パイプラインの設定の詳細について は、『 Pipelines 開発者ガイド』を参照してください。
アプリケーションを実行します
アプリケーションをローカルで実行するには、次のコマンドを実行します。
mvn compile exec:java -Dexec.mainClass=RoadAttrWalkthruScala \
-Dpipeline-config.file=pipeline-config.conf \
-Dpipeline-job.file=pipeline-job.conf \
-Dhere.platform.data-client.request-signer.credentials.here-account.here-token-scope={{YOUR_PROJECT_HRN}}
mvn compile exec:java -Dexec.mainClass=RoadAttrWalkthruJava \
-Dpipeline-config.file=pipeline-config.conf \
-Dpipeline-job.file=pipeline-job.conf \
-Dhere.platform.data-client.request-signer.credentials.here-account.here-token-scope={{YOUR_PROJECT_HRN}}
出力を確認します
アプリケーションの実行後、次のように出力カタログを検査します。
- カタログ の
roadsegments
レイヤーの [ 検査 ] タブの検索ボックスに「 17302687 」と入力します - 以下の画像のようにレンダリングするには、マップ内のパーティションをクリックします。
詳細情報
このチュートリアルで確定的な結果を得るために、 HERE Map Content 入力カタログのバージョンは静的です。 ただし、「資格情報の確認とカタログへのアクセス」に示すように、アプリケーションで getLatestVersion
を使用したり、 PipelineContext
からコード抽出カタログバージョンを削除したり、コマンド ラインから -Dpipeline-job.file=pipeline-job.conf
削除してアプリケーションをローカルで実行したりすることもできます。
このジョブはパイプラインで実行できます。 この方法の詳細については、『 OLP CLI 開発者ガイド』の「パイプライン」コマンドを参照してください。 このスタンドアロンアプリケーションは、 Spark でバッチで実行できますが、パイプラインは含まれていません。