Skip to content

Commit

Permalink
#369 monitor vector tiles poc - improve tile calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
vmarc committed Dec 15, 2023
1 parent 656ae29 commit 1e6921c
Show file tree
Hide file tree
Showing 12 changed files with 292 additions and 92 deletions.
Original file line number Diff line number Diff line change
@@ -1,70 +1,60 @@
package kpn.core.tools.monitor.support

import kpn.core.data.Data
import kpn.core.loadOld.Parser
import kpn.core.overpass.OverpassQueryExecutor
import kpn.core.overpass.OverpassQueryExecutorRemoteImpl
import kpn.core.overpass.QueryRelationTopLevel
import kpn.database.base.Database
import kpn.database.util.Mongo
import kpn.server.analyzer.engine.context.ElementIds
import kpn.server.analyzer.engine.tile.OldLinesTileCalculator
import kpn.server.analyzer.engine.tile.OldLinesTileCalculatorImpl
import kpn.server.analyzer.engine.tile.OldTileCalculatorImpl
import kpn.server.analyzer.engine.tiles.domain.Line
import kpn.server.analyzer.engine.tiles.domain.Point
import kpn.server.monitor.repository.MonitorGroupRepository
import kpn.server.analyzer.engine.tile.LineSegmentTileCalculatorImpl
import kpn.server.analyzer.engine.tile.TileCalculatorImpl
import kpn.server.analyzer.engine.tiles.domain.CoordinateTransform.wayToWorldCoordinates
import kpn.server.analyzer.engine.tiles.domain.Tile
import kpn.server.monitor.repository.MonitorGroupRepositoryImpl
import kpn.server.monitor.repository.MonitorRouteRepository
import kpn.server.monitor.repository.MonitorRouteRepositoryImpl
import kpn.server.monitor.MonitorUtil
import kpn.server.monitor.domain.MonitorRelation
import kpn.server.monitor.repository.MonitorRelationRepository
import kpn.server.monitor.repository.MonitorRelationRepositoryImpl
import kpn.server.monitor.route.update.RelationTopLevelDataBuilder
import org.locationtech.jts.geom.LineSegment

import scala.xml.XML

class MonitorCreateRelationsToolConfig(database: Database) {
val groupRepository = new MonitorGroupRepositoryImpl(database)
val routeRepository = new MonitorRouteRepositoryImpl(database)
val relationRepository = new MonitorRelationRepositoryImpl(database)
val overpassQueryExecutor = new OverpassQueryExecutorRemoteImpl()
val lineSegmentTileCalculator = new LineSegmentTileCalculatorImpl(new TileCalculatorImpl())
}

object MonitorCreateRelationsTool {
def main(args: Array[String]): Unit = {
Mongo.executeIn("kpn-monitor") { database =>
val groupRepository = new MonitorGroupRepositoryImpl(database)
val routeRepository = new MonitorRouteRepositoryImpl(database)
val relationRepository = new MonitorRelationRepositoryImpl(database)
val overpassQueryExecutor = new OverpassQueryExecutorRemoteImpl()
val linesTileCalculator = new OldLinesTileCalculatorImpl(new OldTileCalculatorImpl())
val tool = new MonitorCreateRelationsTool(
groupRepository,
routeRepository,
relationRepository,
overpassQueryExecutor,
linesTileCalculator
)
val config = new MonitorCreateRelationsToolConfig(database)
val tool = new MonitorCreateRelationsTool(config)
tool.createMonitorRelations()
}
}
}

class MonitorCreateRelationsTool(
groupRepository: MonitorGroupRepository,
routeRepository: MonitorRouteRepository,
relationRepository: MonitorRelationRepository,
overpassQueryExecutor: OverpassQueryExecutor,
linesTileCalculator: OldLinesTileCalculator
) {
class MonitorCreateRelationsTool(config: MonitorCreateRelationsToolConfig) {

def createMonitorRelations(): Unit = {
val relationIds = collectionRelationIds()
println(s"collected ${relationIds.size} relation ids")
relationIds.zipWithIndex.foreach { case (relationId, index) =>
println(s"${index + 1}/${relationIds.size}")
val monitorRelation = createMonitorRelation(relationId)
relationRepository.save(monitorRelation)
config.relationRepository.save(monitorRelation)
}
}

private def collectionRelationIds(): Seq[Long] = {
val ids = groupRepository.groups().sortBy(_.name) flatMap { group =>
val ids = config.groupRepository.groups().sortBy(_.name).flatMap { group =>
println(s"group ${group.name}")
groupRepository.groupRoutes(group._id).sortBy(_.name).flatMap { route =>
config.groupRepository.groupRoutes(group._id).sortBy(_.name).flatMap { route =>
println(s" route ${route.name}")
route.relation.toSeq.map(_.relationId) ++
MonitorUtil.subRelationsIn(route).map(_.relationId)
Expand All @@ -74,29 +64,41 @@ class MonitorCreateRelationsTool(
}

private def createMonitorRelation(relationId: Long): MonitorRelation = {
val xmlString = overpassQueryExecutor.executeQuery(None, QueryRelationTopLevel(relationId))
val data = monitorRelationData(relationId)
val elementIds = determineElementIds(data)
val tiles = determineTiles(data)
MonitorRelation(
relationId,
tiles.map(_.name),
elementIds
)
}

private def monitorRelationData(relationId: Long): Data = {
val xmlString = config.overpassQueryExecutor.executeQuery(None, QueryRelationTopLevel(relationId))
val xml = XML.loadString(xmlString)
val rawData = new Parser().parse(xml.head)
val data = new RelationTopLevelDataBuilder(rawData, relationId).data
new RelationTopLevelDataBuilder(rawData, relationId).data
}

private def determineElementIds(data: Data): ElementIds = {
val nodes = data.nodes.values.toSeq
val ways = data.ways.values.toSeq
val nodeIds = nodes.map(_.id).toSet
val wayIds = ways.map(_.id).toSet
val subRelationIds = data.relations.values.flatMap(_.raw.relationMembers.map(_.ref)).toSet
val elementIds = ElementIds(nodeIds, wayIds, subRelationIds)
val tiles = ways.flatMap { way =>
val lines = way.nodes.sliding(2).map { case Seq(node1, node2) =>
Line(Point(node1.lon, node1.lat), Point(node2.lon, node2.lat))
ElementIds(nodeIds, wayIds, subRelationIds)
}

private def determineTiles(data: Data): Seq[Tile] = {
data.ways.values.toSeq.flatMap { way =>
val worldCoordinates = wayToWorldCoordinates(way)
val lineSegments = worldCoordinates.sliding(2).map { case Seq(c1, c2) =>
new LineSegment(c1, c2)
}.toSeq
(2 to 14).flatMap { z =>
linesTileCalculator.tiles(z, lines)
config.lineSegmentTileCalculator.tiles(z, lineSegments)
}
}.distinct.sortBy(tile => (tile.z, tile.x, tile.y))

MonitorRelation(
relationId,
tiles.map(_.name),
elementIds
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import kpn.database.base.Database
import kpn.database.base.Id
import kpn.database.util.Mongo
import kpn.server.analyzer.engine.tiles.domain.CoordinateTransform.toWorldCoordinates
import kpn.server.analyzer.engine.tiles.domain.NewTile
import kpn.server.analyzer.engine.tiles.domain.Tile
import kpn.server.monitor.repository.MonitorRelationRepositoryImpl
import kpn.server.monitor.repository.MonitorRouteRepositoryImpl
import org.apache.commons.io.FileUtils
Expand Down Expand Up @@ -79,22 +79,26 @@ class MonitorTileTool(config: MonitorTileToolConfig) {
val tileDatas = config.relationRepository.tilesZoomLevel(zoomLevel)
tileDatas.zipWithIndex.foreach { case (tileData, index) =>
Log.context(s"${index + 1}/${tileDatas.size}") {
val Array(z, x, y) = tileData.name.split("-").map(namePart => java.lang.Integer.parseInt(namePart))
val tile = new NewTile(z, x, y)
val tileRelationDatas = tileData.relationIds.flatMap { relationId =>
allRelationDatas.get(relationId)
}
if (tileRelationDatas.nonEmpty) {
val tileBytes = build(tile, tileRelationDatas)
writeTile(tile, tileBytes)
try {
val Array(z, x, y) = tileData.name.split("-").map(namePart => java.lang.Integer.parseInt(namePart))
val tile = new Tile(z, x, y)
val tileRelationDatas = tileData.relationIds.flatMap { relationId =>
allRelationDatas.get(relationId)
}
if (tileRelationDatas.nonEmpty) {
val tileBytes = build(tile, tileRelationDatas)
writeTile(tile, tileBytes)
}
} catch {
case e: NumberFormatException =>
}
}
}
}
}
}

private def build(tile: NewTile, tileRelationDatas: Seq[TileRelationData]): Array[Byte] = {
private def build(tile: Tile, tileRelationDatas: Seq[TileRelationData]): Array[Byte] = {

val geometryFactory = new GeometryFactory

Expand Down Expand Up @@ -163,12 +167,12 @@ class MonitorTileTool(config: MonitorTileToolConfig) {
}
(relationId -> TileRelationData(relationId, segments))
}.toMap
(s"loaded ${testRelationIds.size} relations", result)
(s"loaded ${relationIds.size} relations", result)
}
}
}

private def writeTile(tile: NewTile, tileBytes: Array[Byte]): Unit = {
private def writeTile(tile: Tile, tileBytes: Array[Byte]): Unit = {
val fileName = s"/kpn/tiles/monitor/${tile.z}/${tile.x}/${tile.y}.mvt"
val file = new File(fileName)
if (file.exists()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package kpn.server.analyzer.engine.tile

import kpn.server.analyzer.engine.tiles.domain.Tile
import org.locationtech.jts.geom.LineSegment

trait LineSegmentTileCalculator {

def tiles(z: Int, lineSegments: Seq[LineSegment]): Seq[Tile]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package kpn.server.analyzer.engine.tile

import kpn.server.analyzer.engine.tiles.domain.Tile
import kpn.server.analyzer.engine.tiles.domain.TileUtil
import org.locationtech.jts.geom.Coordinate
import org.locationtech.jts.geom.LineSegment
import org.springframework.stereotype.Component

@Component
class LineSegmentTileCalculatorImpl(tileCalculator: TileCalculator) extends LineSegmentTileCalculator {

override def tiles(z: Int, lineSegments: Seq[LineSegment]): Seq[Tile] = {

val tileQueue = scala.collection.mutable.Queue[Tile]()
val foundTiles = scala.collection.mutable.Set[Tile]()

val tiles = lineSegments.flatMap(ls => Seq(ls.p0, ls.p1)).map { p: Coordinate =>
tileCalculator.tileContainingWorldCoordinate(z, p.x, p.y)
}.toSet

foundTiles ++= tiles.toSeq
tileQueue ++= tiles

while (tileQueue.nonEmpty) {
val tile = tileQueue.dequeue()
explore(tileQueue, foundTiles, lineSegments, tile, TileUtil.left(tile), -1, 0)
explore(tileQueue, foundTiles, lineSegments, tile, TileUtil.right(tile), 1, 0)
explore(tileQueue, foundTiles, lineSegments, tile, TileUtil.top(tile), 0, 1)
explore(tileQueue, foundTiles, lineSegments, tile, TileUtil.bottom(tile), 0, -1)
}
foundTiles.toSeq
}

private def explore(
tileQueue: scala.collection.mutable.Queue[Tile],
foundTiles: scala.collection.mutable.Set[Tile],
lineSegments: Seq[LineSegment],
tile: Tile,
side: LineSegment,
xDelta: Int,
yDelta: Int
): Unit = {
val adjecentTile = tileCalculator.tileXY(tile.z, tile.x + xDelta, tile.y + yDelta)
if (!foundTiles.map(_.name).contains(adjecentTile.name)) {
if (lineSegments.exists(_.intersection(side) != null)) {
tileQueue += adjecentTile
foundTiles += adjecentTile
()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package kpn.server.analyzer.engine.tile

import kpn.server.analyzer.engine.tiles.domain.Tile

trait TileCalculator {

def tileContainingWorldCoordinate(z: Int, worldX: Double, worldY: Double): Tile

def tileXY(z: Int, x: Int, y: Int): Tile

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package kpn.server.analyzer.engine.tile

import kpn.server.analyzer.engine.tiles.domain.Tile
import kpn.server.analyzer.engine.tiles.domain.TileCache
import org.springframework.stereotype.Component

@Component
class TileCalculatorImpl extends TileCalculator {

private val cache = new TileCache()

def tileContainingWorldCoordinate(z: Int, worldX: Double, worldY: Double): Tile = {
cache.tileContainingWorldCoordinate(z, worldX, worldY)
}

def tileXY(z: Int, x: Int, y: Int): Tile = {
cache(z, x, y)
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package kpn.server.analyzer.engine.tiles.domain

import kpn.api.common.data.Way
import org.locationtech.jts.geom.Coordinate
import org.locationtech.jts.geom.LineString

Expand Down Expand Up @@ -66,4 +67,9 @@ object CoordinateTransform {
new Coordinate(x, y)
}
}

def wayToWorldCoordinates(way: Way): Seq[Coordinate] = {
way.nodes.toArray.map(node => new Coordinate(lonToWorldX(node.lon), latToWorldY(node.lat)))
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,25 @@ object Line {

case class Line(p1: Point, p2: Point) {
def intersects(other: Line): Boolean = {
Line2D.linesIntersect(p1.x, p1.y, p2.x, p2.y, other.p1.x, other.p1.y, other.p2.x, other.p2.y)
Line2D.linesIntersect(
p1.x,
p1.y,
p2.x,
p2.y,
other.p1.x,
other.p1.y,
other.p2.x,
other.p2.y
)
}

def length: Double = {
Point2D.distance(p1.x, p1.y, p2.x, p2.y)
Point2D.distance(
p1.x,
p1.y,
p2.x,
p2.y
)
}

def points: Seq[Point] = Seq(p1, p2)
Expand Down

This file was deleted.

0 comments on commit 1e6921c

Please sign in to comment.