Skip to content

Commit

Permalink
#368 migrate hierarchy analysis
Browse files Browse the repository at this point in the history
  • Loading branch information
vmarc committed Jul 18, 2024
1 parent 4e03422 commit 6e0d7ed
Show file tree
Hide file tree
Showing 9 changed files with 184 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,25 @@ import { LinkImageComponent } from './link-image.component';
<table class="kpn-table">
<thead>
<tr>
<th i18n="@@route.members.table.nr">Nr</th>
<th></th>
<th i18n="@@route.members.table.node">Node</th>
<th i18n="@@route.members.table.id">Id</th>
<th colSpan="2" i18n="@@route.members.table.nodes">Nodes</th>
<th i18n="@@route.members.table.name">Name</th>
<th i18n="@@route.members.table.role">Role</th>
<th i18n="@@route.members.table.length">Length</th>
<th i18n="@@route.members.table.node-count">#Nodes</th>
<th i18n="@@route.members.table.name">Name</th>
<th i18n="@@route.members.table.inaccessible">Inaccessible</th>
@if (networkType() === 'cycling') {
<th colSpan="2" i18n="@@route.members.table.one-way">One Way</th>
}
</tr>
</thead>
<tbody>
@for (member of members(); track member) {
@for (member of members(); track member; let rowIndex = $index) {
<tr>
<td>
{{ rowIndex + 1 }}
</td>
<td class="image-cell">
<kpn-link-image [linkName]="member.linkName" />
</td>
Expand All @@ -52,40 +54,28 @@ import { LinkImageComponent } from './link-image.component';
</div>
</td>
<td>
@if (member.memberType === 'node') {
<span>N</span>
} @else if (member.memberType === 'way') {
<span>W</span>
} @else if (member.memberType === 'relation') {
<span>R</span>
}
<kpn-osm-link
[kind]="member.memberType"
[elementId]="member.id.toString()"
[title]="member.id.toString()"
/>
</td>
<td>
<kpn-osm-link
kind="node"
[elementId]="member.fromNodeId.toString()"
[title]="member.from"
/>
</td>
<td>
@if (member.isWay) {
<kpn-osm-link
kind="node"
[elementId]="member.toNodeId.toString()"
[title]="member.to"
/>
}
{{ member.description }}
</td>
<td>
{{ member.role }}
</td>
<td class="distance">
{{ member.length }}
</td>
<td>
{{ member.nodeCount }}
</td>
<td>
{{ member.description }}
</td>
<td>
@if (!member.accessible) {
<div>
Expand Down
2 changes: 2 additions & 0 deletions server/src/main/scala/kpn/core/doc/RouteDetailDoc.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import kpn.api.custom.Day
import kpn.api.custom.Fact
import kpn.api.custom.RouteMemberInfo
import kpn.api.custom.Timestamp
import kpn.core.tools.next.domain.RouteRelation
import kpn.server.analyzer.engine.context.ElementIds

case class RouteDetailDoc(
Expand Down Expand Up @@ -39,6 +40,7 @@ case class RouteDetailDoc(
segments: Seq[RouteDetailSegment],
segmentElements: Seq[RouteDetailSegmentElement],
paths: Seq[RouteDetailPath],
hierarchy: Option[RouteRelation]
) extends WithId {

def id: Long = summary.id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,53 +8,121 @@ import kpn.core.tools.next.domain.RouteRelation
import kpn.core.util.Log
import kpn.core.util.Redesign
import kpn.server.analyzer.engine.analysis.route.RouteDetailDocBuilder
import kpn.server.analyzer.engine.analysis.route.structure.DependencySorter
import kpn.server.analyzer.engine.analysis.route.structure.RouteDependency

object RouteAnalysisTool {
def main(args: Array[String]): Unit = {
val configuration = new AnalysisStartConfiguration(AnalysisStartToolOptions("kpn-next"))
new RouteAnalysisTool(configuration).analyze()
}

val essenOkRouteIds: Seq[Long] = Seq(
13844575L, // 01-02
3294187L, // 01-08
13844576L, // 01-36
3665081L, // 02-92
13844574L, // 02-11
)

val law9: Seq[Long] = Seq(
7973533L, // LAW-9 super route containing other super routes
312993L, // Pieterpad deel 1 - Pieterburen-Vorden
8831649L,
8832176L,
8832222L,
8832221L,
8832220L,
8832392L,
8832391L,
8832709L,
8832708L,
8832707L,
8832706L,
8832705L,
8832704L,
156951L, // Pieterpad deel 2 - Vorden-Maastricht Pietersberg
8834446L,
8834445L,
8835026L,
8835025L,
8835024L,
8835656L,
8835655L,
8835654L,
8835653L,
8835652L,
8835651L,
8835650L,
8835649L,
)
}

class RouteAnalysisTool(config: AnalysisStartConfiguration) {

import kpn.core.tools.next.support.RouteAnalysisTool.*

private val log = Log(classOf[RouteAnalysisTool])

def analyze(): Unit = {
log.info("Start")
// analyzeRoutes(Seq(8618)) // ok route with start tenticle
// analyzeRoutes(Seq(5491)) // ok route with 2 start tenticles
analyzeRoutes(Seq(13844575)) // ok route
// analyzeRoutes(Seq(8831649)) // LAW-9 deel 1 - 01
// analyzeRoutes(Seq(3952592)) // broken route
// analyzeRoutes(Seq(7973533)) // LAW-9 super route containing other super routes
// analyzeRoutes(Seq(3963819)) // route with roundabout
// analyzeRoutes(Seq(8618)) // ok route with start tenticle
// analyzeRoutes(Seq(5491)) // ok route with 2 start tenticles

// analyzeRoutes(essenOkRouteIds)
analyzeRoutes(law9)
// analyzeRoutes(Seq(3952592)) // broken route
// analyzeRoutes(Seq(3963819)) // route with roundabout
buildTiles()
log.info(s"Done")
}

private def analyzeRoutes(routeIds: Seq[Long]): Unit = {
routeIds.foreach { routeId =>
val dependencies = routeIds.flatMap { routeId =>
config.nextRepository.nextRouteRelation(routeId) match {
case None => log.error(s"route $routeId not found in route-relations")
case Some(nextRouteRelation) =>
analyzeRoute(nextRouteRelation.relation, nextRouteRelation.structure)
case Some(nextRouteRelation) => analyzeRoute(nextRouteRelation.relation, nextRouteRelation.structure)
case None =>
log.error(s"route $routeId not found in route-relations")
Seq.empty
}
}

DependencySorter.sort(dependencies).foreach { relationId =>
config.routeRepository.findRouteDetailById(relationId).match {
case None => // TODO redesign - error message?
case Some(routeDetailDoc) =>
config.routeMainAnalyzer.analyze(routeDetailDoc) match {
case Some(routeDoc) => config.routeRepository.saveRoute(routeDoc)
case None =>
}
}
}
}

private def analyzeRoute(relation: Relation, hierarchy: Option[RouteRelation]): Unit = {
private def analyzeRoute(relation: Relation, hierarchy: Option[RouteRelation]): Seq[RouteDependency] = {
Log.context(s"route=${relation.id}") {
try {
config.routeDetailMainAnalyzer.analyze(relation, hierarchy) match {
case None =>
case None => Seq.empty
case Some(context) =>
val routeDetailDoc = new RouteDetailDocBuilder(context).build()
config.routeRepository.saveRouteDetail(routeDetailDoc)
// next analysis should go in second pass
config.routeMainAnalyzer.analyze(routeDetailDoc) match {
case Some(routeDoc) => config.routeRepository.saveRoute(routeDoc)

routeDetailDoc.hierarchy match {
case None =>
config.routeMainAnalyzer.analyze(routeDetailDoc) match {
case Some(routeDoc) =>
config.routeRepository.saveRoute(routeDoc)
Seq.empty
case None => Seq.empty
}
case Some(hierarchy) =>
// further analysis should go in second pass
hierarchy.relations.map(subrelation =>
RouteDependency(relation.id, subrelation.relationId)
)
}

// TODO saveRouteChange(routeAnalysis)
}
} catch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ class RouteDetailDocBuilder(context: RouteDetailAnalysisContext) {
context.edges,
buildSegments,
buildSegmentElements,
buildPaths
buildPaths,
context.hierarchy
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ class RouteAnalysisBuilder(context: RouteDetailAnalysisContext) {
Seq.empty, // TODO redesign
Seq.empty, // TODO redesign
Seq.empty, // TODO redesign
context.hierarchy
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package kpn.server.analyzer.engine.analysis.route.structure

object DependencySorter {
def sort(dependencies: Seq[RouteDependency]): Seq[Long] = {
new DependencySorter().sort(dependencies)
}
}

class DependencySorter {

def sort(dependencies: Seq[RouteDependency]): Seq[Long] = {
sortDependencies(Seq.empty, dependencies)
}

private def sortDependencies(relationIds: Seq[Long], remainingDependencies: Seq[RouteDependency]): Seq[Long] = {
if (remainingDependencies.isEmpty) {
relationIds
}
else {
val remainingRelationIds = remainingDependencies.map(_.parentRelationId).distinct
val withChildrenRelationIds = remainingDependencies.filter(dep =>
remainingRelationIds.contains(dep.childRelationId)
).map(_.parentRelationId)
val withoutChildrenRelationIds = remainingRelationIds.filterNot(id => withChildrenRelationIds.contains(id))
val nextRemainingDependencies = remainingDependencies.filterNot(dep =>
withoutChildrenRelationIds.contains(dep.parentRelationId)
)
sortDependencies(relationIds ++ withoutChildrenRelationIds, nextRemainingDependencies)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package kpn.server.analyzer.engine.analysis.route.structure

case class RouteDependency(
parentRelationId: Long,
childRelationId: Long
)
11 changes: 8 additions & 3 deletions server/src/test/scala/kpn/api/common/SharedTestObjects.scala
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ import kpn.core.doc.RouteDetailSegment
import kpn.core.doc.RouteDetailSegmentElement
import kpn.core.doc.RouteDoc
import kpn.core.test.OverpassData
import kpn.core.tools.next.domain.RouteRelation
import kpn.database.actions.statistics.ChangeSetCount2
import kpn.server.analyzer.engine.changes.network.NetworkChange
import kpn.server.analyzer.engine.context.ElementIds
Expand Down Expand Up @@ -425,6 +426,7 @@ trait SharedTestObjects extends MockFactory {
segments: Seq[RouteDetailSegment] = Seq.empty,
segmentElements: Seq[RouteDetailSegmentElement] = Seq.empty,
paths: Seq[RouteDetailPath] = Seq.empty,
hierarchy: Option[RouteRelation] = None,
): RouteDetailDoc = {

val summary = RouteSummary(
Expand Down Expand Up @@ -467,7 +469,8 @@ trait SharedTestObjects extends MockFactory {
edges,
segments,
segmentElements,
paths
paths,
hierarchy,
)
}

Expand Down Expand Up @@ -1042,7 +1045,8 @@ trait SharedTestObjects extends MockFactory {
edges: Seq[RouteEdge] = Seq.empty,
segments: Seq[RouteDetailSegment] = Seq.empty,
segmentElements: Seq[RouteDetailSegmentElement] = Seq.empty,
paths: Seq[RouteDetailPath] = Seq.empty
paths: Seq[RouteDetailPath] = Seq.empty,
hierarchy: Option[RouteRelation] = None
): RouteDetailDoc = {
RouteDetailDoc(
summary.id,
Expand All @@ -1069,7 +1073,8 @@ trait SharedTestObjects extends MockFactory {
edges,
segments,
segmentElements,
paths
paths,
hierarchy
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package kpn.server.analyzer.engine.analysis.route.structure

import kpn.core.util.UnitTest

class DependencySorterTest extends UnitTest {

test("determine dependency sort order") {

val dependencies = Seq(
RouteDependency(1, 11),
RouteDependency(1, 12),
RouteDependency(1, 13),
RouteDependency(11, 111),
RouteDependency(11, 112),
RouteDependency(12, 121),
RouteDependency(12, 122),
)

DependencySorter.sort(dependencies) should equal(Seq(11, 12, 1))
}

test("determine dependency sort order - 3 level hierarch") {

val dependencies = Seq(
RouteDependency(1, 11),
RouteDependency(11, 111),
RouteDependency(111, 1111),
)

DependencySorter.sort(dependencies) should equal(Seq(111, 11, 1))
}

test("no dependencies") {
DependencySorter.sort(Seq.empty) should equal(Seq.empty)
}
}

0 comments on commit 6e0d7ed

Please sign in to comment.