diff --git a/server/src/main/java/kpn/server/analyzer/engine/analysis/route/structure/NewRouteSegmentElementFragmentGroup.scala b/server/src/main/java/kpn/server/analyzer/engine/analysis/route/structure/NewRouteSegmentElementFragmentGroup.scala new file mode 100644 index 000000000..e41f97565 --- /dev/null +++ b/server/src/main/java/kpn/server/analyzer/engine/analysis/route/structure/NewRouteSegmentElementFragmentGroup.scala @@ -0,0 +1,8 @@ +package kpn.server.analyzer.engine.analysis.route.structure + +case class NewRouteSegmentElementFragmentGroup( + surface: String, + fragments: Seq[NewRouteSegmentElementFragment] +) { + def nodeIds: Seq[Long] = fragments.head.nodeIds ++ fragments.tail.flatMap(_.nodeIds.tail) +} diff --git a/server/src/main/scala/kpn/api/common/route/RoutePath.scala b/server/src/main/scala/kpn/api/common/route/RoutePath.scala new file mode 100644 index 000000000..e26603d84 --- /dev/null +++ b/server/src/main/scala/kpn/api/common/route/RoutePath.scala @@ -0,0 +1,11 @@ +package kpn.api.common.route + +case class RoutePath( + id: Long, + name: String, // forward, backward, start-tentacle-1, ... + // fromNodeId: Long, + // fromNodeName: String, + // toNodeId: Long, + // toNodeName: String, + elementIds: Seq[Long] +) diff --git a/server/src/main/scala/kpn/api/common/route/RouteSegment.scala b/server/src/main/scala/kpn/api/common/route/RouteSegment.scala new file mode 100644 index 000000000..c0a615d24 --- /dev/null +++ b/server/src/main/scala/kpn/api/common/route/RouteSegment.scala @@ -0,0 +1,12 @@ +package kpn.api.common.route + +import kpn.api.common.Bounds + +case class RouteSegment( + id: Long, + startNodeId: Long, + endNodeId: Long, + meters: Long, + bounds: Bounds, + elementIds: Seq[Long] +) diff --git a/server/src/main/scala/kpn/core/doc/RouteDetailDoc.scala b/server/src/main/scala/kpn/core/doc/RouteDetailDoc.scala index d65fe9945..5584dd528 100644 --- a/server/src/main/scala/kpn/core/doc/RouteDetailDoc.scala +++ b/server/src/main/scala/kpn/core/doc/RouteDetailDoc.scala @@ -26,6 +26,9 @@ case class RouteDetailDoc( nodeRefs: Seq[Long], // networkNodeIds elementIds: ElementIds, edges: Seq[RouteEdge], + segments: Seq[RouteDetailSegment], + segmentElements: Seq[RouteDetailSegmentElement], + paths: Seq[RouteDetailPath], ) extends WithId { def id: Long = summary.id diff --git a/server/src/main/scala/kpn/core/doc/RouteDetailPath.scala b/server/src/main/scala/kpn/core/doc/RouteDetailPath.scala new file mode 100644 index 000000000..04a831f2c --- /dev/null +++ b/server/src/main/scala/kpn/core/doc/RouteDetailPath.scala @@ -0,0 +1,7 @@ +package kpn.core.doc + +case class RouteDetailPath( + id: Long, + name: String, // forward, backward, start-tentacle-1, ... + elementIds: Seq[Long] +) diff --git a/server/src/main/scala/kpn/core/doc/RouteDetailSegment.scala b/server/src/main/scala/kpn/core/doc/RouteDetailSegment.scala new file mode 100644 index 000000000..3500f1e00 --- /dev/null +++ b/server/src/main/scala/kpn/core/doc/RouteDetailSegment.scala @@ -0,0 +1,12 @@ +package kpn.core.doc + +import kpn.api.common.Bounds + +case class RouteDetailSegment( + id: Long, + startNodeId: Long, + endNodeId: Long, + meters: Long, + bounds: Bounds, + elementIds: Seq[Long] +) diff --git a/server/src/main/scala/kpn/core/doc/RouteDetailSegmentElement.scala b/server/src/main/scala/kpn/core/doc/RouteDetailSegmentElement.scala new file mode 100644 index 000000000..8c7c752f8 --- /dev/null +++ b/server/src/main/scala/kpn/core/doc/RouteDetailSegmentElement.scala @@ -0,0 +1,8 @@ +package kpn.core.doc + +case class RouteDetailSegmentElement( + segmentId: Long, + segmentElementId: Long, + surface: String, + coordinates: String +) diff --git a/server/src/main/scala/kpn/core/doc/RouteDoc.scala b/server/src/main/scala/kpn/core/doc/RouteDoc.scala index c5a1d7234..1f9fbe6dc 100644 --- a/server/src/main/scala/kpn/core/doc/RouteDoc.scala +++ b/server/src/main/scala/kpn/core/doc/RouteDoc.scala @@ -4,6 +4,8 @@ import kpn.api.base.WithId import kpn.api.common.RouteSummary import kpn.api.common.common.Ref import kpn.api.common.route.RouteInfoAnalysis +import kpn.api.common.route.RoutePath +import kpn.api.common.route.RouteSegment import kpn.api.custom.Day import kpn.api.custom.Fact import kpn.api.custom.Timestamp @@ -20,6 +22,8 @@ case class RouteDoc( facts: Seq[Fact], oldFacts: Seq[Fact], analysis: RouteInfoAnalysis, + segments: Seq[RouteSegment], + paths: Seq[RoutePath], ) extends WithId { def id: Long = summary.id diff --git a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/RouteSegment.scala b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/OldRouteSegment.scala similarity index 86% rename from server/src/main/scala/kpn/server/analyzer/engine/analysis/route/RouteSegment.scala rename to server/src/main/scala/kpn/server/analyzer/engine/analysis/route/OldRouteSegment.scala index faa637e63..2f14b5cca 100644 --- a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/RouteSegment.scala +++ b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/OldRouteSegment.scala @@ -2,7 +2,7 @@ package kpn.server.analyzer.engine.analysis.route import kpn.api.common.Bounds -case class RouteSegment( +case class OldRouteSegment( id: Long, startNodeId: Long, endNodeId: Long, diff --git a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/RouteDetailDocBuilder.scala b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/RouteDetailDocBuilder.scala index 258705301..69ffd04d7 100644 --- a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/RouteDetailDocBuilder.scala +++ b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/RouteDetailDocBuilder.scala @@ -1,7 +1,9 @@ package kpn.server.analyzer.engine.analysis.route +import kpn.api.common.Bounds import kpn.api.common.RouteSummary import kpn.api.common.data.Element +import kpn.api.common.data.Node import kpn.api.common.data.Way import kpn.api.common.route.RouteInfoAnalysis import kpn.api.custom.Fact @@ -9,7 +11,11 @@ import kpn.api.custom.RouteMemberInfo import kpn.api.custom.Timestamp import kpn.core.analysis.RouteMemberWay import kpn.core.doc.RouteDetailDoc +import kpn.core.doc.RouteDetailPath +import kpn.core.doc.RouteDetailSegment +import kpn.core.doc.RouteDetailSegmentElement import kpn.server.analyzer.engine.analysis.route.domain.RouteDetailAnalysisContext +import kpn.server.analyzer.engine.analysis.route.structure.StructurePath class RouteDetailDocBuilder(context: RouteDetailAnalysisContext) { @@ -115,7 +121,68 @@ class RouteDetailDocBuilder(context: RouteDetailAnalysisContext) { context.tiles, routeAnalysis.map.nodeIds, context.elementIds, - context.edges + context.edges, + buildSegments, + Seq.empty, // TODO redesign + Seq.empty, // TODO redesign ) } + + private def buildSegments: Seq[RouteDetailSegment] = { + val ways = context.relation.wayMembers.map(_.way) + context.segments.map { segment => + val segmentWayIds = segment.elements.flatMap(_.fragments).map(_.wayId) + val segmentWays = segmentWayIds.flatMap(wayId => ways.find(_.id == wayId)) + val meters = segmentWays.map(_.length).sum + val segmentNodes = segmentWays.flatMap(_.nodes) + val bounds = Bounds.from(segmentNodes) + RouteDetailSegment( + segment.id, + segment.fromNodeId, + segment.toNodeId, + meters, + bounds, + segment.elements.map(_.id) + ) + } + } + + private def buildSegmentElements: Seq[RouteDetailSegmentElement] = { + + val nodeMap: Map[Long, Node] = { + val nodeMemberNodes = context.relation.nodeMembers.map(_.node).toSet + val wayMemberNodes = context.relation.wayMembers.flatMap(_.way.nodes).toSet + val nodes = nodeMemberNodes ++ wayMemberNodes + nodes.map(node => node.id -> node) + }.toMap + + context.segments.flatMap { segment => + segment.elements.flatMap { element => + element.fragmentGroups.map { fragmentGroup => + val nodes = fragmentGroup.nodeIds.flatMap(nodeId => nodeMap.get(nodeId)) + val coordinates = nodes.map(node => s"[${node.latitude},${node.latitude}]").mkString("[", ",", "]") + RouteDetailSegmentElement( + segment.id, + element.id, + fragmentGroup.surface, + coordinates + ) + } + } + } + } + + private def buildPaths: Seq[RouteDetailPath] = { + Seq( + context.newStructure.forwardPath.toSeq.map(path => toRouteDetailPath(path, "forward")), + context.newStructure.backwardPath.toSeq.map(path => toRouteDetailPath(path, "backward")), + context.newStructure.startTentaclePaths.zipWithIndex.map { case (path, index) => toRouteDetailPath(path, s"start-tentacle-${index + 1}") }, + context.newStructure.endTentaclePaths.zipWithIndex.map { case (path, index) => toRouteDetailPath(path, s"end-tentacle-${index + 1}") }, + context.newStructure.otherPaths.zipWithIndex.map { case (path, index) => toRouteDetailPath(path, s"other-${index + 1}") }, + ).flatten + } + + private def toRouteDetailPath(path: StructurePath, name: String): RouteDetailPath = { + RouteDetailPath(path.id, name, path.elementIds) + } } diff --git a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/RouteMainAnalyzerImpl.scala b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/RouteMainAnalyzerImpl.scala index 2b592226b..e7015c6c5 100644 --- a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/RouteMainAnalyzerImpl.scala +++ b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/RouteMainAnalyzerImpl.scala @@ -1,5 +1,7 @@ package kpn.server.analyzer.engine.analysis.route +import kpn.api.common.route.RoutePath +import kpn.api.common.route.RouteSegment import kpn.core.doc.RouteDetailDoc import kpn.core.doc.RouteDoc import kpn.core.util.Log @@ -25,6 +27,25 @@ class RouteMainAnalyzerImpl() extends RouteMainAnalyzer { @tailrec private def doAnalyze(analyzers: List[RouteDocAnalyzer], context: RouteDocAnalysisContext): Option[RouteDoc] = { if (analyzers.isEmpty) { + val segments = context.routeDetailDoc.segments.map { segment => + RouteSegment( + segment.id, + segment.startNodeId, + segment.endNodeId, + segment.meters, + segment.bounds, + segment.elementIds + ) + } + + val paths = context.routeDetailDoc.paths.map { path => + RoutePath( + path.id, + path.name, + path.elementIds + ) + } + Some( RouteDoc( context.routeDetailDoc._id, // routeId @@ -37,7 +58,9 @@ class RouteMainAnalyzerImpl() extends RouteMainAnalyzer { context.routeDetailDoc.lastSurvey, context.routeDetailDoc.facts, context.routeDetailDoc.oldFacts, - context.routeDetailDoc.analysis + context.routeDetailDoc.analysis, + segments, + paths ) ) } diff --git a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/RouteSegmentData.scala b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/RouteSegmentData.scala index f11fa18a8..cadb8b553 100644 --- a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/RouteSegmentData.scala +++ b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/RouteSegmentData.scala @@ -4,6 +4,6 @@ import org.locationtech.jts.geom.LineString case class RouteSegmentData( id: Int, - segment: RouteSegment, + segment: OldRouteSegment, lineStrings: Seq[LineString] ) diff --git a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/analyzers/OldRouteSegmentAnalyzer.scala b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/analyzers/OldRouteSegmentAnalyzer.scala index ec09600f0..21da1a562 100644 --- a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/analyzers/OldRouteSegmentAnalyzer.scala +++ b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/analyzers/OldRouteSegmentAnalyzer.scala @@ -3,8 +3,8 @@ package kpn.server.analyzer.engine.analysis.route.analyzers import kpn.api.common.data.WayMember import kpn.core.util.Haversine import kpn.core.util.Log +import kpn.server.analyzer.engine.analysis.route.OldRouteSegment import kpn.server.analyzer.engine.analysis.route.RouteNodeAnalysis -import kpn.server.analyzer.engine.analysis.route.RouteSegment import kpn.server.analyzer.engine.analysis.route.RouteSegmentAnalysis import kpn.server.analyzer.engine.analysis.route.RouteSegmentData import kpn.server.analyzer.engine.analysis.route.domain.RouteDetailAnalysisContext @@ -75,7 +75,7 @@ class OldRouteSegmentAnalyzer(routeNodeAnalysis: RouteNodeAnalysis) { val geoJson = MonitorRouteAnalysisSupport.toGeoJson(geometryCollection) - val segment = RouteSegment( + val segment = OldRouteSegment( id = index + 1, startNodeId, endNodeId, diff --git a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/analyzers/RouteAnalysisBuilder.scala b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/analyzers/RouteAnalysisBuilder.scala index ab2d2dea4..c4c432184 100644 --- a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/analyzers/RouteAnalysisBuilder.scala +++ b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/analyzers/RouteAnalysisBuilder.scala @@ -165,7 +165,10 @@ class RouteAnalysisBuilder(context: RouteDetailAnalysisContext) { context.tiles, routeAnalysis.map.nodeIds, context.elementIds, - context.edges + context.edges, + Seq.empty, // TODO redesign + Seq.empty, // TODO redesign + Seq.empty, // TODO redesign ) } } diff --git a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/NewRouteSegmentElement.scala b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/NewRouteSegmentElement.scala index 1a1b627c6..e40f1815d 100644 --- a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/NewRouteSegmentElement.scala +++ b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/NewRouteSegmentElement.scala @@ -9,11 +9,15 @@ case class NewRouteSegmentElement( toNetworkNode: Option[RouteNodeData], fromNodeId: Long, toNodeId: Long, - fragments: Seq[NewRouteSegmentElementFragment] + fragmentGroups: Seq[NewRouteSegmentElementFragmentGroup] ) { + def fragments: Seq[NewRouteSegmentElementFragment] = { + fragmentGroups.flatMap(_.fragments) + } + def nodeIds: Seq[Long] = { fragments.headOption match { - case Some(firstLink) => firstLink.nodeIds ++ fragments.tail.flatMap(link => link.nodeIds.tail) + case Some(firstFragment) => firstFragment.nodeIds ++ fragments.tail.flatMap(_.nodeIds.tail) case None => Seq.empty } } diff --git a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/NewRouteSegmentElementFragment.scala b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/NewRouteSegmentElementFragment.scala index 44924ded1..ceca1ac9f 100644 --- a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/NewRouteSegmentElementFragment.scala +++ b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/NewRouteSegmentElementFragment.scala @@ -7,6 +7,7 @@ case class NewRouteSegmentElementFragment( wayId: Long, link: Link, role: Option[String], + surface: String, nodeIds: Seq[Long] ) { def fromNodeId: Long = nodeIds.head diff --git a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/RoutePath.scala b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/RoutePath.scala deleted file mode 100644 index f77a7d2cb..000000000 --- a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/RoutePath.scala +++ /dev/null @@ -1,43 +0,0 @@ -package kpn.server.analyzer.engine.analysis.route.structure - -case class RoutePath( - id: Long, - direction: RoutePathDirection, - elements: Seq[NewRouteSegmentElement] -) { - - if (elements.isEmpty || elements.size > 1) { - throw new RuntimeException("UNEXPECTED !!!") - } - - def nodeIds: Seq[Long] = { - val ids: Seq[Long] = elements.headOption match { - case Some(firstElement) => firstElement.nodeIds ++ elements.tail.flatMap(_.nodeIds) - case None => Seq.empty - } - if (direction == RoutePathDirection.Backward) { - ids.reverse - } - else { - ids - } - } - - def fromNodeId: Long = { - if (direction == RoutePathDirection.Backward) { - elements.last.toNodeId - } - else { - elements.head.fromNodeId - } - } - - def toNodeId: Long = { - if (direction == RoutePathDirection.Backward) { - elements.head.fromNodeId - } - else { - elements.last.toNodeId - } - } -} diff --git a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/RouteSegmentAnalyzer.scala b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/RouteSegmentAnalyzer.scala index 86079fbb8..417572cfc 100644 --- a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/RouteSegmentAnalyzer.scala +++ b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/RouteSegmentAnalyzer.scala @@ -3,8 +3,10 @@ package kpn.server.analyzer.engine.analysis.route.structure import kpn.core.analysis.LinkDirection import kpn.core.util.Triplet import kpn.core.util.Util +import kpn.server.analyzer.engine.analysis.route.RouteNodeData import kpn.server.analyzer.engine.analysis.route.analyzers.RouteAnalyzer import kpn.server.analyzer.engine.analysis.route.domain.RouteDetailAnalysisContext +import kpn.server.analyzer.engine.analysis.route.segment.SurfaceAnalyzer import scala.collection.mutable.ListBuffer @@ -204,8 +206,7 @@ class RouteSegmentAnalyzer(context: RouteDetailAnalysisContext) { val fromNetworkNode = context.nodeAnalysis.nodes.find(_.node.id == fromNodeId) val toNetworkNode = context.nodeAnalysis.nodes.find(_.node.id == toNodeId) - NewRouteSegmentElement( - elementIds.next(), + buildElement( direction, fromNetworkNode, toNetworkNode, @@ -226,19 +227,11 @@ class RouteSegmentAnalyzer(context: RouteDetailAnalysisContext) { private def buildFragmentElement(routeLinkWay: RouteLinkWay, direction: RoutePathDirection, nodeIds: Seq[Long]): NewRouteSegmentElement = { // this is a closed loop at the end of the route - val fragment = NewRouteSegmentElementFragment( - fragmentIds.next(), - routeLinkWay.way.id, - routeLinkWay.link, - routeLinkWay.role, - nodeIds - ) - + val fragment = toFragment(routeLinkWay, nodeIds) val fromNetworkNode = context.nodeAnalysis.nodes.find(_.node.id == fragment.fromNodeId) val toNetworkNode = context.nodeAnalysis.nodes.find(_.node.id == fragment.toNodeId) - NewRouteSegmentElement( - elementIds.next(), + buildElement( direction, fromNetworkNode, toNetworkNode, @@ -249,11 +242,13 @@ class RouteSegmentAnalyzer(context: RouteDetailAnalysisContext) { } private def toFragment(routeLinkWay: RouteLinkWay, nodeIds: Seq[Long]): NewRouteSegmentElementFragment = { + val surface = new SurfaceAnalyzer(context.networkTypes, routeLinkWay.way).surface() NewRouteSegmentElementFragment( fragmentIds.next(), routeLinkWay.way.id, routeLinkWay.link, routeLinkWay.role, + surface, nodeIds ) } @@ -264,4 +259,24 @@ class RouteSegmentAnalyzer(context: RouteDetailAnalysisContext) { fragments.clear() } } + + private def buildElement( + direction: RoutePathDirection, + fromNetworkNode: Option[RouteNodeData], + toNetworkNode: Option[RouteNodeData], + fromNodeId: Long, + toNodeId: Long, + fragments: Seq[NewRouteSegmentElementFragment] + ): NewRouteSegmentElement = { + val fragmentGroups = SurfaceFragmentSplitter.split(context.networkTypes, fragments) + NewRouteSegmentElement( + elementIds.next(), + direction, + fromNetworkNode, + toNetworkNode, + fromNodeId, + toNodeId, + fragmentGroups + ) + } } diff --git a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/StructureAnalyzer.scala b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/StructureAnalyzer.scala index a783aa822..5fcc0fae1 100644 --- a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/StructureAnalyzer.scala +++ b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/StructureAnalyzer.scala @@ -1,5 +1,6 @@ package kpn.server.analyzer.engine.analysis.route.structure +import kpn.core.util.Util import kpn.server.analyzer.engine.analysis.route.RouteNodeData import kpn.server.analyzer.engine.analysis.route.domain.RouteDetailAnalysisContext @@ -7,6 +8,8 @@ import scala.annotation.tailrec class StructureAnalyzer(context: RouteDetailAnalysisContext, traceEnabled: Boolean = false) { + private val pathIds = Util.ids + def analyze(): Structure = { if (context.nodeAnalysis.nodes.isEmpty) { analyzeNonNodeNetworkRoute() @@ -21,6 +24,7 @@ class StructureAnalyzer(context: RouteDetailAnalysisContext, traceEnabled: Boole if (context.segments.size > 1) { val otherElements = context.segments.flatMap(_.elements).map { element => StructurePath( + pathIds.next(), element.fromNodeId, element.toNodeId, Seq( @@ -84,13 +88,14 @@ class StructureAnalyzer(context: RouteDetailAnalysisContext, traceEnabled: Boole backwardPath: Option[StructurePath], startTentaclePaths: Seq[StructurePath], endTentaclePaths: Seq[StructurePath] - ) = { + ): Seq[StructurePath] = { val usedSegmentIds = forwardPath.toSeq.flatMap(_.elementIds) ++ backwardPath.toSeq.flatMap(_.elementIds) ++ startTentaclePaths.flatMap(_.elementIds) ++ endTentaclePaths.flatMap(_.elementIds) val remainingElements = context.segments.flatMap(_.elements).filterNot(element => usedSegmentIds.contains(element.id)) remainingElements.map { element => StructurePath( + pathIds.next(), element.fromNodeId, element.toNodeId, Seq( @@ -116,6 +121,7 @@ class StructureAnalyzer(context: RouteDetailAnalysisContext, traceEnabled: Boole case Some(firstElement) => Some( StructurePath( + pathIds.next(), firstElement.fromNodeId, firstElement.toNodeId, Seq( @@ -140,6 +146,7 @@ class StructureAnalyzer(context: RouteDetailAnalysisContext, traceEnabled: Boole case Some(firstElement) => Some( StructurePath( + pathIds.next(), firstElement.fromNodeId, firstElement.toNodeId, Seq( @@ -171,6 +178,7 @@ class StructureAnalyzer(context: RouteDetailAnalysisContext, traceEnabled: Boole if (elements.nonEmpty) { Some( StructurePath( + pathIds.next(), mainEndNode.node.id, mainStartNode.node.id, elements @@ -197,6 +205,7 @@ class StructureAnalyzer(context: RouteDetailAnalysisContext, traceEnabled: Boole if (elements.nonEmpty) { Some( StructurePath( + pathIds.next(), mainStartNode.node.id, mainEndNode.node.id, elements @@ -212,8 +221,9 @@ class StructureAnalyzer(context: RouteDetailAnalysisContext, traceEnabled: Boole private def analyzeNonNodeNetworkRoute(): Structure = { if (context.segments.size > 1) { - val otherElements = context.segments.flatMap(_.elements).map { element => + val otherPaths = context.segments.flatMap(_.elements).map { element => StructurePath( + pathIds.next(), element.fromNodeId, element.toNodeId, Seq(StructurePathElement(element, reversed = false)) @@ -224,7 +234,7 @@ class StructureAnalyzer(context: RouteDetailAnalysisContext, traceEnabled: Boole None, Seq.empty, Seq.empty, - otherElements + otherPaths ) } else { @@ -233,6 +243,7 @@ class StructureAnalyzer(context: RouteDetailAnalysisContext, traceEnabled: Boole if (elements.nonEmpty) { Some( StructurePath( + pathIds.next(), elements.head.startNodeId, elements.last.endNodeId, elements @@ -249,6 +260,7 @@ class StructureAnalyzer(context: RouteDetailAnalysisContext, traceEnabled: Boole if (elements.nonEmpty) { Some( StructurePath( + pathIds.next(), elements.head.startNodeId, elements.last.endNodeId, elements @@ -264,6 +276,7 @@ class StructureAnalyzer(context: RouteDetailAnalysisContext, traceEnabled: Boole val usedElementIds = (forwardPath.toSeq.flatMap(_.elementIds) ++ backwardPath.toSeq.flatMap(_.elementIds)).toSet context.segments.flatMap(_.elements).filterNot(element => usedElementIds.contains(element.id)).map { element => StructurePath( + pathIds.next(), element.nodeIds.head, element.nodeIds.last, Seq(StructurePathElement(element, reversed = false)) diff --git a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/StructurePath.scala b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/StructurePath.scala index f3eb56565..4ad7ddede 100644 --- a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/StructurePath.scala +++ b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/StructurePath.scala @@ -1,6 +1,7 @@ package kpn.server.analyzer.engine.analysis.route.structure case class StructurePath( + id: Long, startNodeId: Long, endNodeId: Long, elements: Seq[StructurePathElement] = Seq.empty, diff --git a/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/SurfaceFragmentSplitter.scala b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/SurfaceFragmentSplitter.scala new file mode 100644 index 000000000..af29a4621 --- /dev/null +++ b/server/src/main/scala/kpn/server/analyzer/engine/analysis/route/structure/SurfaceFragmentSplitter.scala @@ -0,0 +1,41 @@ +package kpn.server.analyzer.engine.analysis.route.structure + +import kpn.api.custom.NetworkType + +import scala.annotation.tailrec + +object SurfaceFragmentSplitter { + + def split(networkTypes: Seq[NetworkType], segmentFragments: Seq[NewRouteSegmentElementFragment]): Seq[NewRouteSegmentElementFragmentGroup] = { + + if (segmentFragments.isEmpty) { + Seq.empty + } + else { + val firstFragment = segmentFragments.head + val initialSegment = NewRouteSegmentElementFragmentGroup(firstFragment.surface, Seq(firstFragment)) + doSplit(Seq(initialSegment), segmentFragments.tail) + } + } + + @tailrec + private def doSplit(found: Seq[NewRouteSegmentElementFragmentGroup], remaining: Seq[NewRouteSegmentElementFragment]): Seq[NewRouteSegmentElementFragmentGroup] = { + if (remaining.isEmpty) { + found + } + else { + val surface = remaining.head.surface + val fragment = remaining.head + val lastSegment = found.last + if (lastSegment.surface == surface) { + val updatedSegment = lastSegment.copy(fragments = lastSegment.fragments :+ fragment) + val newFound = found.take(found.size - 1) :+ updatedSegment + doSplit(newFound, remaining.tail) + } + else { + val newSegment = NewRouteSegmentElementFragmentGroup(surface, Seq(fragment)) + doSplit(found :+ newSegment, remaining.tail) + } + } + } +} diff --git a/server/src/test/scala/kpn/api/common/SharedTestObjects.scala b/server/src/test/scala/kpn/api/common/SharedTestObjects.scala index e6a53d691..92db37993 100644 --- a/server/src/test/scala/kpn/api/common/SharedTestObjects.scala +++ b/server/src/test/scala/kpn/api/common/SharedTestObjects.scala @@ -55,6 +55,8 @@ import kpn.api.common.route.RouteEdge import kpn.api.common.route.RouteInfoAnalysis import kpn.api.common.route.RouteMap import kpn.api.common.route.RouteNetworkNodeInfo +import kpn.api.common.route.RoutePath +import kpn.api.common.route.RouteSegment import kpn.api.custom.Change import kpn.api.custom.ChangeType import kpn.api.custom.Country @@ -82,6 +84,9 @@ import kpn.core.doc.NodeDoc import kpn.core.doc.OrphanNodeDoc import kpn.core.doc.OrphanRouteDoc import kpn.core.doc.RouteDetailDoc +import kpn.core.doc.RouteDetailPath +import kpn.core.doc.RouteDetailSegment +import kpn.core.doc.RouteDetailSegmentElement import kpn.core.doc.RouteDoc import kpn.core.test.OverpassData import kpn.database.actions.statistics.ChangeSetCount2 @@ -407,7 +412,11 @@ trait SharedTestObjects extends MockFactory { analysis: RouteInfoAnalysis = newRouteInfoAnalysis(), facts: Seq[Fact] = Seq.empty, tiles: Seq[String] = Seq.empty, - elementIds: ElementIds = ElementIds() + elementIds: ElementIds = ElementIds(), + edges: Seq[RouteEdge] = Seq.empty, + segments: Seq[RouteDetailSegment] = Seq.empty, + segmentElements: Seq[RouteDetailSegmentElement] = Seq.empty, + paths: Seq[RouteDetailPath] = Seq.empty, ): RouteDetailDoc = { val summary = RouteSummary( @@ -440,7 +449,10 @@ trait SharedTestObjects extends MockFactory { tiles, analysis.map.nodeIds, elementIds, - Seq.empty + edges, + segments, + segmentElements, + paths ) } @@ -971,6 +983,8 @@ trait SharedTestObjects extends MockFactory { lastSurvey: Option[Day] = None, facts: Seq[Fact] = Seq.empty, analysis: RouteInfoAnalysis = newRouteInfoAnalysis(), + segments: Seq[RouteSegment] = Seq.empty, + paths: Seq[RoutePath] = Seq.empty, ): RouteDoc = { RouteDoc( summary.id, @@ -983,7 +997,9 @@ trait SharedTestObjects extends MockFactory { lastSurvey, facts, facts, - analysis + analysis, + segments, + paths ) } @@ -1000,7 +1016,10 @@ trait SharedTestObjects extends MockFactory { tiles: Seq[String] = Seq.empty, nodeRefs: Seq[Long] = Seq.empty, elementIds: ElementIds = ElementIds(), - edges: Seq[RouteEdge] = Seq.empty + edges: Seq[RouteEdge] = Seq.empty, + segments: Seq[RouteDetailSegment] = Seq.empty, + segmentElements: Seq[RouteDetailSegmentElement] = Seq.empty, + paths: Seq[RouteDetailPath] = Seq.empty ): RouteDetailDoc = { RouteDetailDoc( summary.id, @@ -1017,7 +1036,10 @@ trait SharedTestObjects extends MockFactory { tiles, nodeRefs, elementIds, - edges + edges, + segments, + segmentElements, + paths ) } diff --git a/server/src/test/scala/kpn/server/analyzer/engine/analysis/route/segment/PavedUnpavedSplitterTest.scala b/server/src/test/scala/kpn/server/analyzer/engine/analysis/route/segment/PavedUnpavedSplitterTest.scala index 6ee9c8ba2..1b1402195 100644 --- a/server/src/test/scala/kpn/server/analyzer/engine/analysis/route/segment/PavedUnpavedSplitterTest.scala +++ b/server/src/test/scala/kpn/server/analyzer/engine/analysis/route/segment/PavedUnpavedSplitterTest.scala @@ -5,6 +5,7 @@ import kpn.api.custom.Tags import kpn.core.test.TestData import kpn.core.util.UnitTest +// TODO redesign - move to SurfaceFragmentSplitterTest class PavedUnpavedSplitterTest extends UnitTest { test("split paved/unpaved segment fragments") { diff --git a/server/src/test/scala/kpn/server/analyzer/engine/analysis/route/structure/SurfaceFragmentSplitterTest.scala b/server/src/test/scala/kpn/server/analyzer/engine/analysis/route/structure/SurfaceFragmentSplitterTest.scala new file mode 100644 index 000000000..2d0a6d2e6 --- /dev/null +++ b/server/src/test/scala/kpn/server/analyzer/engine/analysis/route/structure/SurfaceFragmentSplitterTest.scala @@ -0,0 +1,9 @@ +package kpn.server.analyzer.engine.analysis.route.structure + +import kpn.core.util.UnitTest + +class SurfaceFragmentSplitterTest extends UnitTest { + test("migrate from PavedUnpavedSplitterTest") { + pending // TODO redesign + } +}