diff --git a/build.gradle b/build.gradle index baf70611..02020b16 100644 --- a/build.gradle +++ b/build.gradle @@ -50,6 +50,7 @@ apply from: scriptsLocation + 'tscfg.gradle' repositories { mavenCentral() //searches in bintray's repository 'jCenter', which contains Maven Central maven { url 'https://www.jitpack.io' } // allows github repos as dependencies + maven { url 'https://oss.sonatype.org/service/local/repositories/snapshots/content' } } dependencies { @@ -76,13 +77,16 @@ dependencies { // todo CLEANUP following old dependencies // ie³ power system utils - implementation('com.github.ie3-institute:PowerSystemUtils:1.5.3') { + implementation('com.github.ie3-institute:PowerSystemUtils:1.6-SNAPSHOT') { exclude group: 'org.slf4j' exclude group: 'org.apache.logging.log4j' exclude group: 'com.github.ie3-institute' exclude group: 'com.github.johanneshiry', module: 'OSMonaut' } + // indriya + implementation 'tech.units:indriya:2.1.2' + // ie³ power system data model implementation('com.github.ie3-institute:PowerSystemDataModel:2.0.1') { exclude group: 'org.slf4j' diff --git a/src/main/scala/edu/ie3/osmogrid/guardian/OsmoGridGuardian.scala b/src/main/scala/edu/ie3/osmogrid/guardian/OsmoGridGuardian.scala index 30657f75..573cb44a 100644 --- a/src/main/scala/edu/ie3/osmogrid/guardian/OsmoGridGuardian.scala +++ b/src/main/scala/edu/ie3/osmogrid/guardian/OsmoGridGuardian.scala @@ -22,8 +22,16 @@ import edu.ie3.osmogrid.io.output.ResultListener import edu.ie3.osmogrid.io.output.ResultListener.{GridResult, ResultEvent} import edu.ie3.osmogrid.lv.LvCoordinator import edu.ie3.osmogrid.lv.LvCoordinator.ReqLvGrids +import edu.ie3.util.osm.OsmModel -import scala.jdk.CollectionConverters.* +import org.locationtech.jts.geom.{ + Coordinate, + LinearRing, + Polygon, + GeometryFactory +} +import org.locationtech.jts.geom.impl.CoordinateArraySequenceFactory +import scala.jdk.CollectionConverters._ import scala.util.{Failure, Success, Try} object OsmoGridGuardian { @@ -59,7 +67,22 @@ object OsmoGridGuardian { ctx.log.debug("Starting low voltage grid coordinator.") val lvCoordinator = ctx.spawn(LvCoordinator(), "LvCoordinator") ctx.watchWith(lvCoordinator, LvCoordinatorDied) - lvCoordinator ! ReqLvGrids(lvConfig, ctx.self) + val osmModel = OsmModel( + List.empty, + List.empty, + None, + new Polygon( + new LinearRing( + CoordinateArraySequenceFactory + .instance() + .create(Array.empty[Coordinate]), + new GeometryFactory() + ), + Array.empty[LinearRing], + new GeometryFactory() + ) + ) // TODO actually use imported input data + lvCoordinator ! ReqLvGrids(lvConfig, osmModel, ctx.self) awaitLvGrids(inputProvider, resultEventListener) case unsupported => ctx.log.error( diff --git a/src/main/scala/edu/ie3/osmogrid/lv/LvCoordinator.scala b/src/main/scala/edu/ie3/osmogrid/lv/LvCoordinator.scala index 1889207e..cbb32b0d 100644 --- a/src/main/scala/edu/ie3/osmogrid/lv/LvCoordinator.scala +++ b/src/main/scala/edu/ie3/osmogrid/lv/LvCoordinator.scala @@ -11,18 +11,35 @@ import akka.actor.typed.scaladsl.{Behaviors, Routers} import edu.ie3.datamodel.models.input.container.SubGridContainer import edu.ie3.osmogrid.cfg.OsmoGridConfig import edu.ie3.osmogrid.cfg.OsmoGridConfig.Generation.Lv +import edu.ie3.osmogrid.guardian.OsmoGridGuardian import edu.ie3.osmogrid.guardian.OsmoGridGuardian.{ OsmoGridGuardianEvent, RepLvGrids } import edu.ie3.osmogrid.lv.LvGenerator +import edu.ie3.util.osm.{OsmEntities, OsmModel} +import org.locationtech.jts.geom.impl.CoordinateArraySequence +import org.locationtech.jts.geom.{ + Coordinate, + GeometryFactory, + LinearRing, + Polygon, + PrecisionModel +} object LvCoordinator { sealed trait LvCoordinatorEvent final case class ReqLvGrids( cfg: OsmoGridConfig.Generation.Lv, + osmModel: OsmModel, replyTo: ActorRef[OsmoGridGuardianEvent] ) extends LvCoordinatorEvent + final case class RepLvGrids(grids: Vector[SubGridContainer]) + extends LvCoordinatorEvent + + // TODO replace with geoutils + private val DEFAULT_GEOMETRY_FACTORY: GeometryFactory = + new GeometryFactory(new PrecisionModel(), 4326) def apply(): Behavior[LvCoordinatorEvent] = idle @@ -35,7 +52,8 @@ object LvCoordinator { amountOfRegionCoordinators, distinctHouseConnections ), - replyTo + osmModel, + guardian ) => ctx.log.info("Starting generation of low voltage grids!") /* TODO: @@ -65,11 +83,146 @@ object LvCoordinator { val lvRegionCoordinatorProxy = ctx.spawn(lvRegionCoordinatorPool, "LvRegionCoordinatorPool") - replyTo ! RepLvGrids(Vector.empty[SubGridContainer]) - Behaviors.stopped + val boundaries = buildBoundaryPolygons(osmModel) + + def filterWay(polygon: Polygon, way: OsmEntities.Way) = { + val majority = (way.nodes.size + 1) / 2 + way.nodes + .filter { node => + polygon.covers(node.coordinates) + } + .sizeCompare(majority) > 0 + } + + def filterRelation( + polygon: Polygon, + relation: OsmEntities.Relation + ): Boolean = { + val majority = (relation.elements.size + 1) / 2 + relation.elements + .map(_.element) + .filter { + case node: OsmEntities.Node => + polygon.covers(node.coordinates) + case way: OsmEntities.Way => + filterWay(polygon, way) + case relation: OsmEntities.Relation => + filterRelation(polygon, relation) + } + .sizeCompare(majority) > 0 + } + + // TODO this scala-esk way might be inefficient, find better way + boundaries.foreach { polygon => + val nodes = osmModel.nodes.filter { node => + polygon.covers(node.coordinates) + } + val ways = osmModel.ways.filter { way => + filterWay(polygon, way) + } + val relations = osmModel.relations.map { + _.filter { relation => + filterRelation(polygon, relation) + } + } + + val model = OsmModel(nodes, ways, relations, polygon) + + lvRegionCoordinatorProxy ! LvRegionCoordinator.ReqLvGrids( + model, + ctx.self + ) + } + + /* Wait for the incoming data and check, if all replies are received. */ + awaitReplies(0, guardian) case unsupported => ctx.log.error(s"Received unsupported message: $unsupported") Behaviors.stopped } } + + private def awaitReplies( + awaitedReplies: Int, + guardian: ActorRef[OsmoGridGuardianEvent], + collectedGrids: Vector[SubGridContainer] = Vector.empty + ): Behaviors.Receive[LvCoordinatorEvent] = Behaviors.receive { + case (ctx, RepLvGrids(grids)) => + val stillAwaited = awaitedReplies - 1 + ctx.log.debug( + s"Received another ${grids.length} sub grids. ${if (stillAwaited == 0) "All requests are answered." + else s"Still awaiting $stillAwaited replies."}." + ) + val updatedGrids = collectedGrids ++ grids + if (stillAwaited == 0) { + ctx.log.info( + s"Received ${updatedGrids.length} sub grid containers in total. Join and send them to the guardian." + ) + guardian ! OsmoGridGuardian.RepLvGrids(updatedGrids) + Behaviors.stopped + } else + awaitReplies(stillAwaited, guardian, updatedGrids) + case (ctx, unsupported) => + ctx.log.error(s"Received unsupported message: $unsupported") + Behaviors.stopped + } + + private def buildBoundaryPolygons(osmModel: OsmModel) = { + extractBoundaries(osmModel).map { r => + // combine all ways of a boundary relation to one sequence of coordinates + val coordinates = r.elements + .flatMap { case OsmEntities.RelationElement(element, "outer") => + element match { + case way: OsmEntities.Way => + way.nodes + case _ => List.empty + } + } + .map { el => + el.coordinates.getCoordinate + } + .toArray + + buildPolygon(coordinates) + } + } + + /** Returns a list of boundary relations consisting of counties ("Gemeinden") + * and independent towns ("Kreisfreie Städte"). + * + * Should work in most places in Germany. + * @param osmModel + * the OSM model + * @return + * list of boundary relations + */ + private def extractBoundaries( + osmModel: OsmModel + ): List[OsmEntities.Relation] = { + val relations = osmModel.relations.getOrElse(throw new RuntimeException()) + + val boundaries = relations.filter { + _.tags.get("boundary").contains("administrative") + } + + val municipalities = + boundaries.filter(_.tags.get("admin_level").contains("8")) + + // independent towns with tag de:place=city https://forum.openstreetmap.org/viewtopic.php?id=21788 + val independentCities = boundaries.filter { b => + b.tags.get("admin_level").contains("6") && b.tags + .get("de:place") + .contains("city") + } + + municipalities.appendedAll(independentCities) + } + + // TODO replace with convenience function in PowerSystemUtils + private def buildPolygon(coordinates: Array[Coordinate]): Polygon = { + val arrayCoordinates = new CoordinateArraySequence(coordinates) + val linearRing = + new LinearRing(arrayCoordinates, DEFAULT_GEOMETRY_FACTORY) + new Polygon(linearRing, Array[LinearRing](), DEFAULT_GEOMETRY_FACTORY) + } } diff --git a/src/main/scala/edu/ie3/osmogrid/lv/LvRegionCoordinator.scala b/src/main/scala/edu/ie3/osmogrid/lv/LvRegionCoordinator.scala index 55da4bed..c9584463 100644 --- a/src/main/scala/edu/ie3/osmogrid/lv/LvRegionCoordinator.scala +++ b/src/main/scala/edu/ie3/osmogrid/lv/LvRegionCoordinator.scala @@ -7,12 +7,17 @@ package edu.ie3.osmogrid.lv import akka.actor.typed.scaladsl.Behaviors - import akka.actor.typed.ActorRef +import edu.ie3.osmogrid.lv.LvCoordinator.LvCoordinatorEvent import edu.ie3.osmogrid.lv.LvGenerator.LvGeneratorEvent +import edu.ie3.util.osm.OsmModel object LvRegionCoordinator { sealed trait LvRegionCoordinatorEvent + final case class ReqLvGrids( + osmModel: OsmModel, + replyTo: ActorRef[LvCoordinatorEvent] + ) extends LvRegionCoordinatorEvent def apply( lvGeneratorPool: ActorRef[LvGeneratorEvent]