Skip to content

Commit

Permalink
#278 monitor route analysis - wip
Browse files Browse the repository at this point in the history
  • Loading branch information
vmarc committed Jan 10, 2024
1 parent 9afbb5e commit af2ebdb
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 133 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,17 @@ case class RouteWay(way: Way, reversed: Boolean = false) {
way.nodes.last
}
}

def nodes: Seq[Node] = {
if (reversed) {
way.nodes.reverse
}
else {
way.nodes
}
}

def nodeIds: Seq[Long] = {
nodes.map(_.id)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package kpn.server.analyzer.engine.monitor

import kpn.api.common.data.RelationMember
import kpn.api.common.data.Way
import kpn.api.common.data.WayMember
import kpn.api.custom.Relation
Expand All @@ -9,160 +10,83 @@ import scala.collection.mutable

class MonitorRouteStructureAnalyzer {

def analyzeRoute(relation: Relation) {
val mainSegments = analyzeMainSegments(relation)
val forwardSegments = analyzeForwardSegments(relation)
private var startWayMember: Option[WayMember] = None
private var processedStartWayMember = false

debug(s"main segments.size=${mainSegments.size}")
debug(s"forward segments.size=${forwardSegments.size}")
val segments = mutable.Buffer[Seq[RouteWay]]()
val currentSegment = mutable.Buffer[RouteWay]()
var endNodeId: Long = 0

forwardSegments.zipWithIndex.foreach { case (forwardSegment, forwardSegmentIndex) =>
debug(s"forward segment $forwardSegmentIndex")
val startNodeId = forwardSegment.head.startNode.id
val endNodeId = forwardSegment.last.endNode.id
mainSegments.zipWithIndex.foreach { case (mainSegment, mainSegmentIndex) =>
debug(s" test main segment $mainSegmentIndex")
val mainSegmentNodeIds = mainSegment.flatMap(_.way.nodes).map(_.id)
if (mainSegmentNodeIds.contains(startNodeId) || mainSegmentNodeIds.contains(endNodeId)) {
debug(s"forward segment $forwardSegmentIndex matched $mainSegmentIndex")
}
def analyzeRoute(relation: Relation): Seq[Seq[RouteWay]] = {
relation.members.foreach { member =>
member match {
case wayMember: WayMember => processWayMember(wayMember)
case relationMember: RelationMember => processRelationMember(relationMember)
case _ => None
}
}
}

private def analyzeMainSegments(relation: Relation): Seq[Seq[RouteWay]] = {

val ways = relation
.members
.flatMap(member => member match {
case wayMember: WayMember => Some(wayMember)
case _ => None
})
.filter(wayMember => !wayMember.role.isDefined)
.map(_.way)

debug(s"main segments ways.size=${ways.size}")

val segments = analyzeWaySegments(ways)
debug(s"main segments segments.size=${segments.size}")
if (currentSegment.nonEmpty) {
segments.addOne(currentSegment.toSeq)
}
segments.toSeq
}

private def analyzeForwardSegments(relation: Relation): Seq[Seq[RouteWay]] = {

val wayMembers = relation
.members
.flatMap(member => member match {
case wayMember: WayMember => Some(wayMember)
case _ => None
})
private def processRelationMember(relationMember: RelationMember): Unit = {
// TODO implement
}

val segments = mutable.Buffer[Seq[RouteWay]]()
val wayMemberIterator = wayMembers.iterator
private def processWayMember(currentWayMember: WayMember): Unit = {

if (wayMembers.isEmpty) {
// done
}
else if (wayMembers.size == 1) {
val wayMember = wayMemberIterator.next()
if (wayMember.role.contains("forward")) {
segments.addOne(Seq(RouteWay(wayMember.way)))
if (!processedStartWayMember) {
startWayMember match {
case None =>
startWayMember = Some(currentWayMember)
case Some(wayMember) =>
processStart(wayMember, currentWayMember)
}
}
else {
val currentSegment = mutable.Buffer[Way]()
var inForwardWays = false
val wayMember = wayMemberIterator.next()
if (wayMember.role.contains("forward")) {
currentSegment.addOne(wayMember.way)
inForwardWays = true
}

while (wayMemberIterator.hasNext) {
val wayMember = wayMemberIterator.next()
// debug(s"way ${wayMember.way.id} ${wayMember.role}")
if (wayMember.role.contains("forward")) {
if (inForwardWays) {
// debug("add way to existing current segment")
currentSegment.addOne(wayMember.way)
}
else {
// debug("add first way to new current segment")
currentSegment.addOne(wayMember.way)
inForwardWays = true
}
}
else {
if (inForwardWays) {
// debug("first way after forward segment, save to segments, and start new segment")
segments.addAll(analyzeWaySegments(currentSegment.toSeq))
currentSegment.clear()
inForwardWays = false
}
}
}

if (currentSegment.nonEmpty) {
segments.addAll(analyzeWaySegments(currentSegment.toSeq))
}
processNextWayMember(currentWayMember)
}
segments.toSeq
}

private def processStart(wayMember1: WayMember, wayMember2: WayMember): Unit = {
// TODO still have to look at role "forward" here

private def analyzeWaySegments(ways: Seq[Way]): Seq[Seq[RouteWay]] = {

val segments = mutable.Buffer[Seq[RouteWay]]()
val wayIterator = ways.iterator
val routeWays = firstConnection(wayMember1.way, wayMember2.way)
if (routeWays.isEmpty) {
// false start, the first 2 ways do not connect
// make way1 a separate segment
// make way2 the first way
segments.addOne(Seq(RouteWay(wayMember1.way)))
startWayMember = Some(wayMember2)
}
else {
currentSegment.addAll(routeWays)
processedStartWayMember = true
// TODO endNodeId: handle way without nodes or only one node
endNodeId = routeWays.last.endNode.id
}
}

if (ways.isEmpty) {
// done
private def processNextWayMember(wayMember: WayMember): Unit = {
if (endNodeId == wayMember.way.nodes.head.id) {
currentSegment.addOne(RouteWay(wayMember.way))
endNodeId = wayMember.way.nodes.last.id
}
else if (ways.size == 1) {
segments.addOne(Seq(RouteWay(ways.head)))
else if (endNodeId == wayMember.way.nodes.last.id) {
currentSegment.addOne(RouteWay(wayMember.way, reversed = true))
endNodeId = wayMember.way.nodes.head.id
}
else {
val way1 = wayIterator.next()
val way2 = wayIterator.next()

val routeWays = firstConnection(way1, way2)
if (routeWays.isEmpty) {
segments.addOne(Seq(RouteWay(way1)))
segments.addOne(Seq(RouteWay(way2)))
}
else {
val currentSegment = mutable.Buffer[RouteWay]()
currentSegment.addAll(routeWays)

// endNodeId: handle way without nodes or only one node
var endNodeId = routeWays.last.endNode.id

while (wayIterator.hasNext) {
val way = wayIterator.next()
if (endNodeId == way.nodes.head.id) {
currentSegment.addOne(RouteWay(way))
endNodeId = way.nodes.last.id
}
else if (endNodeId == way.nodes.last.id) {
currentSegment.addOne(RouteWay(way, reversed = true))
endNodeId = way.nodes.head.id
}
else {
if (currentSegment.nonEmpty) {
segments.addOne(currentSegment.toSeq)
currentSegment.clear()
}
}
}

if (currentSegment.nonEmpty) {
segments.addOne(currentSegment.toSeq)
}
if (currentSegment.nonEmpty) {
segments.addOne(currentSegment.toSeq)
currentSegment.clear()
}
startWayMember = Some(wayMember)
processedStartWayMember = false
}

debug(s"way segments.size=${segments.size}")
segments.toSeq
}

private def firstConnection(way1: Way, way2: Way): Seq[RouteWay] = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,89 @@
package kpn.server.analyzer.engine.monitor

import kpn.api.common.SharedTestObjects
import kpn.api.custom.Relation
import kpn.core.util.UnitTest
import kpn.server.analyzer.engine.analysis.caseStudies.CaseStudy

class MonitorRouteStructureAnalyzerTest extends UnitTest {
class MonitorRouteStructureAnalyzerTest extends UnitTest with SharedTestObjects {

test("relation") {
ignore("simulation") {
val relation = CaseStudy.load("/case-studies/monitor/4840541.xml")
println(s"relation.members.size=${relation.members.size}")
new MonitorRouteStructureAnalyzer().analyzeRoute(relation)
}

test("simple route 3 ways") {
val relation = new MonitorRouteTestData() {
memberWay(10, "", 1, 2, 3)
memberWay(11, "", 3, 4, 5)
memberWay(12, "", 5, 6, 7)
}.relation

analyze(relation).shouldMatchTo(
Seq(
Seq(1, 2, 3, 4, 5, 6, 7)
)
)
}

test("simple route 3 ways - first way reversed") {
val relation = new MonitorRouteTestData() {
memberWay(10, "", 3, 2, 1)
memberWay(11, "", 3, 4, 5)
memberWay(12, "", 5, 6, 7)
}.relation

analyze(relation).shouldMatchTo(
Seq(
Seq(1, 2, 3, 4, 5, 6, 7)
)
)
}

test("simple route 3 ways - second and third way reversed") {
val relation = new MonitorRouteTestData() {
memberWay(10, "", 1, 2, 3)
memberWay(11, "", 5, 4, 3)
memberWay(12, "", 7, 6, 5)
}.relation

analyze(relation).shouldMatchTo(
Seq(
Seq(1, 2, 3, 4, 5, 6, 7)
)
)
}

test("gaps") {
val relation = new MonitorRouteTestData() {
memberWay(10, "", 1, 2)
memberWay(11, "", 3, 4)
memberWay(12, "", 4, 5)
memberWay(13, "", 6, 7)
memberWay(14, "", 7, 8)
}.relation

analyze(relation).shouldMatchTo(
Seq(
Seq(1, 2),
Seq(3, 4, 5),
Seq(6, 7, 8),
)
)
}

private def analyze(relation: Relation): Seq[Seq[Long]] = {
val segments = new MonitorRouteStructureAnalyzer().analyzeRoute(relation)
segments.map { segment =>
segment.zipWithIndex.flatMap { case (routeWay, index) =>
if (index == 0) {
routeWay.nodeIds
}
else {
routeWay.nodeIds.tail
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package kpn.server.analyzer.engine.monitor

import kpn.api.common.SharedTestObjects
import kpn.api.common.data.raw.RawData
import kpn.api.common.data.raw.RawMember
import kpn.api.common.data.raw.RawNode
import kpn.api.common.data.raw.RawWay
import kpn.api.custom.Relation
import kpn.api.custom.Tags
import kpn.core.data.DataBuilder

import scala.collection.mutable.ListBuffer

class MonitorRouteTestData extends SharedTestObjects {

private val nodeBuffer = ListBuffer[RawNode]()
private val wayBuffer = ListBuffer[RawWay]()
private val memberBuffer = ListBuffer[RawMember]()

def memberWay(wayId: Long, role: String, nodeIds: Long*): RawMember = {
addNodesIfMissing(nodeIds)
memberWay(wayId, Tags.from("highway" -> "road"), role, nodeIds: _*)
}

private def node(id: Long, name: String = "", lattitude: Double = 0, longitude: Double = 0): RawNode = {
rawNode(newRawNode(id, lattitude.toString, longitude.toString, tags = Tags.empty))
}

private def rawNode(rawNode: RawNode): RawNode = {
nodeBuffer += rawNode
rawNode
}

private def way(wayId: Long, nodeIds: Long*): RawWay = {
way(wayId, Tags.empty, nodeIds: _*)
}

private def way(wayId: Long, tags: Tags, nodeIds: Long*): RawWay = {
addNodesIfMissing(nodeIds)
val w = newRawWay(wayId, nodeIds = nodeIds.toVector, tags = tags)
wayBuffer += w
w
}

private def memberWay(wayId: Long, tags: Tags, role: String, nodeIds: Long*): RawMember = {
addNodesIfMissing(nodeIds)
way(wayId, tags, nodeIds: _*)
member("way", wayId, role)
}

private def member(memberType: String, ref: Long, role: String = ""): RawMember = {
val m = RawMember(memberType, ref, if (role.nonEmpty) Some(role) else None)
memberBuffer += m
m
}

private def addNodesIfMissing(nodeIds: Seq[Long]): Unit = {
val missingNodeIds = nodeIds.toSet -- nodeBuffer.map(_.id).toSet
missingNodeIds.foreach(id => node(id))
}

def relation: Relation = {
val relation = newRawRelation(1, members = memberBuffer.toSeq)
val rawData = RawData(None, nodeBuffer.toSeq, wayBuffer.toSeq, Seq(relation))
new DataBuilder(rawData).data.relations(1)
}
}

0 comments on commit af2ebdb

Please sign in to comment.