diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..ce6e4c0 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 igloo-4002 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/config/WebConfig.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/config/WebConfig.kt index 2761508..218c48a 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/config/WebConfig.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/config/WebConfig.kt @@ -12,11 +12,22 @@ import org.springframework.web.servlet.config.annotation.CorsRegistry import org.springframework.web.servlet.config.annotation.EnableWebMvc import org.springframework.web.servlet.config.annotation.WebMvcConfigurer +/** + * Configuration class for Spring MVC. + */ @Configuration @EnableWebMvc class WebConfig: WebMvcConfigurer { + /** + * URL of frontend for CORS, as specified in `application.yml`. Defaults fo `http://localhost:5173` if not present, + * and is not used if [allowAllCorsOrigins] is set to `true`. + */ @Value("\${urbanflo.frontend-url}") private lateinit var frontendUrl: String + + /** + * If set to `true`, all endpoints will accept all CORS origins. + */ @Value("\${urbanflo.allow-all-cors-origins}") private var allowAllCorsOrigins: Boolean = false diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/config/WebSocketConfig.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/config/WebSocketConfig.kt index 32b67fa..bc16af2 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/config/WebSocketConfig.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/config/WebSocketConfig.kt @@ -6,6 +6,9 @@ import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBr import org.springframework.web.socket.config.annotation.StompEndpointRegistry import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer +/** + * Configuration class for Spring WebSocket. + */ @Configuration @EnableWebSocketMessageBroker class WebSocketConfig : WebSocketMessageBrokerConfigurer { diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/controller/SimulationController.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/controller/SimulationController.kt index aee17be..b72cd7b 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/controller/SimulationController.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/controller/SimulationController.kt @@ -41,6 +41,9 @@ class SimulationController( private var instances: MutableMap = mutableMapOf() private var disposables: MutableMap = mutableMapOf() + /** + * Endpoint for simulation websocket. + */ @MessageMapping("/simulation/{id}") fun simulationSocket( @DestinationVariable id: SimulationId, @@ -278,32 +281,55 @@ class SimulationController( @ResponseBody fun getSimulationAnalytics(@PathVariable id: SimulationId) = storageService.getSimulationAnalytics(id.trim()) + /** + * Exception handler for when the simulation cannot be found. Returns a 404 response. + */ @ExceptionHandler(StorageSimulationNotFoundException::class) fun handleStorageNotFound(e: StorageSimulationNotFoundException): ResponseEntity { return ResponseEntity(ErrorResponse(e.message ?: "No such simulation"), HttpStatus.NOT_FOUND) } + /** + * Exception handler for any malformed or invalid request. Returns a 400 response. + * + * @see [handleJsonError] + */ @ExceptionHandler(StorageBadRequestException::class) fun handleStorageBadRequest(e: StorageBadRequestException): ResponseEntity { return ResponseEntity(ErrorResponse(e.message ?: "Invalid request"), HttpStatus.BAD_REQUEST) } + /** + * Exception handler for any malformed or invalid JSON body. Returns a 400 response. + * + * @see [handleStorageBadRequest] + */ @ExceptionHandler(JsonProcessingException::class) fun handleJsonError(e: JsonProcessingException): ResponseEntity { return ResponseEntity(ErrorResponse("Invalid JSON body"), HttpStatus.BAD_REQUEST) } + /** + * Exception handler for any storage exceptions not covered by the other handlers. Returns a 500 response. + */ @ExceptionHandler(StorageException::class) fun handleStorageException(e: StorageException): ResponseEntity { return ResponseEntity(ErrorResponse("An internal error occurred"), HttpStatus.INTERNAL_SERVER_ERROR) } + /** + * Exception handler for any validation failures. Returns a 400 response with each validation error specified in + * [ErrorResponse.errorFields]. + */ @ExceptionHandler(MethodArgumentNotValidException::class) fun handleValidationErrors(e: MethodArgumentNotValidException): ResponseEntity { val fields = e.allErrors.associate { err -> (err as FieldError).field to err.defaultMessage }.toMap() return ResponseEntity(ErrorResponse("Invalid JSON body", fields), HttpStatus.BAD_REQUEST) } + /** + * Force stop all simulations before shutting down the server. + */ @PreDestroy fun stopAllSimulations() { logger.info { "Server is shutting down. Stopping all simulations" } diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/jackson/UnixDoubleTimestampDeserializer.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/jackson/UnixDoubleTimestampDeserializer.kt index bb316c3..2341622 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/jackson/UnixDoubleTimestampDeserializer.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/jackson/UnixDoubleTimestampDeserializer.kt @@ -9,7 +9,11 @@ import java.time.OffsetDateTime import java.time.ZoneOffset import kotlin.math.roundToLong -// https://stackoverflow.com/questions/20635698/how-do-i-deserialize-timestamps-that-are-in-seconds-with-jackson +/** + * Jackson deserialization class to parse Unix timestamp encoded as [Double] to [OffsetDateTime]. + * + * [Source/adapted from](https://stackoverflow.com/a/20638114) + */ class UnixDoubleTimestampDeserializer : StdDeserializer(OffsetDateTime::class.java) { override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): OffsetDateTime { val number = p?.valueAsString?.toDouble() ?: run { throw JsonParseException("Value is null") } diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/ErrorResponse.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/ErrorResponse.kt index 8e50f80..95ccef7 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/ErrorResponse.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/ErrorResponse.kt @@ -1,3 +1,15 @@ package app.urbanflo.urbanflosumoserver.model -data class ErrorResponse(val error: String, val errorFields: Map? = null) \ No newline at end of file +/** + * Response body for all errors. + */ +data class ErrorResponse( + /** + * Error message + */ + val error: String, + /** + * Mapping of fields in request body which failed validation or contains errors. + */ + val errorFields: Map? = null +) \ No newline at end of file diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/SimulationException.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/SimulationException.kt index 2e9fcf0..5fda6d6 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/SimulationException.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/SimulationException.kt @@ -1,3 +1,6 @@ package app.urbanflo.urbanflosumoserver.model +/** + * Exception class for all errors during simulation. + */ class SimulationException(message: String) : Exception(message) \ No newline at end of file diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/SimulationInfo.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/SimulationInfo.kt index 66a252e..c67c009 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/SimulationInfo.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/SimulationInfo.kt @@ -3,10 +3,25 @@ package app.urbanflo.urbanflosumoserver.model import java.nio.file.Path import java.time.OffsetDateTime +/** + * Data class for simulation information. + */ data class SimulationInfo( + /** + * Simulation ID + */ val id: String, + /** + * Document name as sent by frontend. + */ val documentName: String, + /** + * Creation date, in ISO8601 format. + */ val createdAt: OffsetDateTime, + /** + * Last modified date, in ISO8601 format. + */ val lastModifiedAt: OffsetDateTime ) { companion object { diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/SimulationMessageType.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/SimulationMessageType.kt index 70b31f1..c136ff2 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/SimulationMessageType.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/SimulationMessageType.kt @@ -1,7 +1,11 @@ package app.urbanflo.urbanflosumoserver.model + enum class SimulationMessageType { START, STOP } +/** + * WebSocket simulation message to signal the simulation instance to start or stop. + */ data class SimulationMessageRequest(var status: SimulationMessageType) \ No newline at end of file diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/SumoEntity.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/SumoEntity.kt new file mode 100644 index 0000000..4502607 --- /dev/null +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/SumoEntity.kt @@ -0,0 +1,6 @@ +package app.urbanflo.urbanflosumoserver.model + +/** + * Type alias for all SUMO network entity IDs. + */ +typealias SumoEntityId = String \ No newline at end of file diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/VehicleData.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/VehicleData.kt index b782cc7..5239e30 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/VehicleData.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/VehicleData.kt @@ -1,5 +1,8 @@ package app.urbanflo.urbanflosumoserver.model +/** + * Vehicle data produced by TraCI from each simulation step. + */ data class VehicleData( val vehicleId: String, val position: Pair, // [x, y] diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoConnection.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoConnection.kt index 39dcd07..aef1115 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoConnection.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoConnection.kt @@ -1,7 +1,11 @@ package app.urbanflo.urbanflosumoserver.model.network +import app.urbanflo.urbanflosumoserver.model.SumoEntityId import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +/** + * @see [SumoConnectionsXml] + */ data class SumoConnection( @field:JacksonXmlProperty(isAttribute = true) val from: SumoEntityId, diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoConnectionsXml.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoConnectionsXml.kt index a9bdf47..a578a97 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoConnectionsXml.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoConnectionsXml.kt @@ -6,6 +6,9 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement import java.nio.file.Path +/** + * Data class representing [connection description XML.](https://sumo.dlr.de/docs/Networks/PlainXML.html#connection_descriptions) + */ @JacksonXmlRootElement(localName = "connections") data class SumoConnectionsXml( @field:JacksonXmlProperty(localName = "connection") diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoEdge.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoEdge.kt index 2c8b524..cee0378 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoEdge.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoEdge.kt @@ -1,9 +1,13 @@ package app.urbanflo.urbanflosumoserver.model.network +import app.urbanflo.urbanflosumoserver.model.SumoEntityId import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty import jakarta.validation.constraints.Positive import jakarta.validation.constraints.PositiveOrZero +/** + * @see [SumoEdgesXml] + */ data class SumoEdge( @field:JacksonXmlProperty(isAttribute = true) val id: SumoEntityId, diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoEdgesXml.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoEdgesXml.kt index 3dae2ca..ec8124c 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoEdgesXml.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoEdgesXml.kt @@ -6,8 +6,9 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement import java.nio.file.Path -typealias SumoEntityId = String - +/** + * Data class representing SUMO's [edge description XML.](https://sumo.dlr.de/docs/Networks/PlainXML.html#edge_descriptions) + */ @JacksonXmlRootElement(localName = "edges") data class SumoEdgesXml( @field:JacksonXmlProperty(localName = "edge") diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoFlow.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoFlow.kt index badb883..0db88dc 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoFlow.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoFlow.kt @@ -1,7 +1,11 @@ package app.urbanflo.urbanflosumoserver.model.network +import app.urbanflo.urbanflosumoserver.model.SumoEntityId import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +/** + * SUMO [flow.](https://sumo.dlr.de/docs/Definition_of_Vehicles%2C_Vehicle_Types%2C_and_Routes.html#repeated_vehicles_flows) + */ data class SumoFlow( @field:JacksonXmlProperty(isAttribute = true) val id: SumoEntityId, diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoNetwork.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoNetwork.kt index 000bf18..cd57d5b 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoNetwork.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoNetwork.kt @@ -6,19 +6,43 @@ import jakarta.validation.Valid import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.NotEmpty +/** + * SUMO network data, in the format used by the frontend. + */ data class SumoNetwork( + /** + * Document name + */ @field:NotBlank(message = "Document name cannot be blank") val documentName: String, + /** + * List of nodes + */ @field:NotEmpty(message = "Nodes must not be empty") val nodes: List, + /** + * List of edges + */ @field:NotEmpty(message = "Edges must not be empty") @field:Valid val edges: List, + /** + * List of connections + */ @field:NotEmpty(message = "Connections must not be empty") val connections: List, + /** + * List of vehicle types (as `vType` in the JSON) + */ @field:JsonProperty("vType") val vehicleType: List, + /** + * List of routes + */ val route: List, + /** + * List of flows + */ val flow: List ) { constructor( @@ -37,9 +61,21 @@ data class SumoNetwork( routesXml.flows ) + /** + * Returns a node description compatible with SUMO's [node description XML.](https://sumo.dlr.de/docs/Networks/PlainXML.html#node_descriptions) + */ fun nodesXml() = SumoNodesXml(this.nodes) + /** + * Returns an edge description compatible with SUMO's [edge description XML.](https://sumo.dlr.de/docs/Networks/PlainXML.html#edge_descriptions) + */ fun edgesXml() = SumoEdgesXml(this.edges) + /** + * Returns a connection description compatible with SUMO's [connection description XML.](https://sumo.dlr.de/docs/Networks/PlainXML.html#connection_descriptions) + */ fun connectionsXml() = SumoConnectionsXml(this.connections) + /** + * Returns a route description compatible with SUMO's [route description XML.](https://sumo.dlr.de/docs/Definition_of_Vehicles%2C_Vehicle_Types%2C_and_Routes.html#vehicles_and_routes) + */ fun routesXml() = SumoRoutesXml( this.vehicleType, this.route, diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoNode.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoNode.kt index ad50eb0..a558740 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoNode.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoNode.kt @@ -1,7 +1,11 @@ package app.urbanflo.urbanflosumoserver.model.network +import app.urbanflo.urbanflosumoserver.model.SumoEntityId import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +/** + * @see [SumoNodesXml] + */ data class SumoNode( @field:JacksonXmlProperty(isAttribute = true) val id: SumoEntityId, @@ -10,29 +14,5 @@ data class SumoNode( @field:JacksonXmlProperty(isAttribute = true) val y: Double, @field:JacksonXmlProperty(isAttribute = true) - val type: String -) { - init { - type.let { - require( - it.lowercase() in listOf( - "priority", - "traffic_light", - "right_before_left", - "left_before_right", - "unregulated", - "priority_stop", - "traffic_light_unregulated", - "allway_stop", - "rail_signal", - "zipper", - "traffic_light_right_on_red", - "rail_crossing", - "dead_end" - ) - ) { - "Invalid value for node type: $it." - } - } - } -} + val type: SumoNodeType +) diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoNodeType.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoNodeType.kt new file mode 100644 index 0000000..76da7ca --- /dev/null +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoNodeType.kt @@ -0,0 +1,22 @@ +package app.urbanflo.urbanflosumoserver.model.network + +import com.fasterxml.jackson.annotation.JsonValue + +/** + * SUMO [node type.](https://sumo.dlr.de/docs/Networks/PlainXML.html#node_types) + */ +enum class SumoNodeType(@JsonValue val nodeType: String) { + PRIORITY("priority"), + TRAFFIC_LIGHT("traffic_light"), + RIGHT_BEFORE_LEFT("right_before_left"), + LEFT_BEFORE_RIGHT("left_before_right"), + UNREGULATED("unregulated"), + PRIORITY_STOP("priority_stop"), + TRAFFIC_LIGHT_UNREGULATED("traffic_light_unregulated"), + ALLWAY_STOP("allway_stop"), + RAIL_SIGNAL("rail_signal"), + ZIPPER("zipper"), + TRAFFIC_LIGHT_RIGHT_ON_RED("traffic_light_right_on_red"), + RAIL_CROSSING("rail_crossing"), + DEAD_END("dead_end") +} \ No newline at end of file diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoNodesXml.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoNodesXml.kt index c6bc122..a8e2aae 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoNodesXml.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoNodesXml.kt @@ -6,6 +6,9 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement import java.nio.file.Path +/** + * Data class representing SUMO's [node description XML.](https://sumo.dlr.de/docs/Networks/PlainXML.html#node_descriptions) + */ @JacksonXmlRootElement(localName = "nodes") data class SumoNodesXml( @field:JacksonXmlProperty(localName = "node") diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoRoute.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoRoute.kt index 0c9f9c3..3d3730e 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoRoute.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoRoute.kt @@ -1,7 +1,11 @@ package app.urbanflo.urbanflosumoserver.model.network +import app.urbanflo.urbanflosumoserver.model.SumoEntityId import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +/** + * SUMO [route.](https://sumo.dlr.de/docs/Definition_of_Vehicles%2C_Vehicle_Types%2C_and_Routes.html#routes) + */ data class SumoRoute( @field:JacksonXmlProperty(isAttribute = true) val id: SumoEntityId, diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoRoutesXml.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoRoutesXml.kt index de793fb..ed0b055 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoRoutesXml.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoRoutesXml.kt @@ -6,7 +6,9 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement import java.nio.file.Path - +/** + * Data class representing SUMO's [route description XML.](https://sumo.dlr.de/docs/Definition_of_Vehicles%2C_Vehicle_Types%2C_and_Routes.html#vehicles_and_routes) + */ @JacksonXmlRootElement(localName = "routes") data class SumoRoutesXml( @field:JacksonXmlProperty(localName = "vType") diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoSpreadType.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoSpreadType.kt index 7772752..19bad83 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoSpreadType.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoSpreadType.kt @@ -2,6 +2,9 @@ package app.urbanflo.urbanflosumoserver.model.network import com.fasterxml.jackson.annotation.JsonValue +/** + * SUMO [spread type](https://sumo.dlr.de/docs/Networks/PlainXML.html#spreadtype). + */ enum class SumoSpreadType(@JsonValue val spreadType: String) { RIGHT("right"), CENTER("center"), diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoVehicleType.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoVehicleType.kt index 90962d6..1a34ffe 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoVehicleType.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/network/SumoVehicleType.kt @@ -1,7 +1,11 @@ package app.urbanflo.urbanflosumoserver.model.network +import app.urbanflo.urbanflosumoserver.model.SumoEntityId import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +/** + * SUMO [vehicle type.](https://sumo.dlr.de/docs/Definition_of_Vehicles%2C_Vehicle_Types%2C_and_Routes.html#vehicle_types) + */ data class SumoVehicleType( @field:JacksonXmlProperty(isAttribute = true) val id: SumoEntityId, diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/netstate/SumoNetstateEdge.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/netstate/SumoNetstateEdge.kt index f2f340a..ff11674 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/netstate/SumoNetstateEdge.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/netstate/SumoNetstateEdge.kt @@ -3,6 +3,9 @@ package app.urbanflo.urbanflosumoserver.model.output.netstate import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +/** + * Time step item for [SumoNetstateXml]. + */ data class SumoNetstateEdge( val id: String, @field:JacksonXmlProperty(localName = "lane") diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/netstate/SumoNetstateLane.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/netstate/SumoNetstateLane.kt index 8db9ebf..a9f668b 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/netstate/SumoNetstateLane.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/netstate/SumoNetstateLane.kt @@ -3,6 +3,9 @@ package app.urbanflo.urbanflosumoserver.model.output.netstate import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +/** + * @see [SumoNetstateXml] + */ data class SumoNetstateLane( val id: String, @field:JacksonXmlProperty(localName = "vehicle") diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/netstate/SumoNetstateTimestep.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/netstate/SumoNetstateTimestep.kt index e4af991..ac08e37 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/netstate/SumoNetstateTimestep.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/netstate/SumoNetstateTimestep.kt @@ -3,6 +3,9 @@ package app.urbanflo.urbanflosumoserver.model.output.netstate import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +/** + * @see [SumoNetstateXml] + */ data class SumoNetstateTimestep( val time: Double, @field:JacksonXmlProperty(localName = "edge") diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/netstate/SumoNetstateVehicle.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/netstate/SumoNetstateVehicle.kt index 91a8fba..3f4499a 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/netstate/SumoNetstateVehicle.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/netstate/SumoNetstateVehicle.kt @@ -1,7 +1,12 @@ package app.urbanflo.urbanflosumoserver.model.output.netstate +import app.urbanflo.urbanflosumoserver.model.SumoEntityId + +/** + * @see [SumoNetstateXml] + */ data class SumoNetstateVehicle( - val id: String, + val id: SumoEntityId, val pos: Double, val speed: Double ) \ No newline at end of file diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/netstate/SumoNetstateXml.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/netstate/SumoNetstateXml.kt index f7125ae..3d871e1 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/netstate/SumoNetstateXml.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/netstate/SumoNetstateXml.kt @@ -7,6 +7,9 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement import java.nio.file.Path +/** + * Data class for SUMO [`netstate` output.](https://sumo.dlr.de/docs/Simulation/Output/RawDump.html) + */ @JacksonXmlRootElement(localName = "netstate") @JsonIgnoreProperties(ignoreUnknown = true) data class SumoNetstateXml( diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoPedestrianStatistics.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoPedestrianStatistics.kt index a074c58..dbb7ca2 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoPedestrianStatistics.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoPedestrianStatistics.kt @@ -2,6 +2,9 @@ package app.urbanflo.urbanflosumoserver.model.output.statistics import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +/** + * @see [SumoStatisticsXml] + */ data class SumoPedestrianStatistics( @field:JacksonXmlProperty(isAttribute = true) val number: Int, diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoPerformanceStatistics.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoPerformanceStatistics.kt index 1e282fd..524e465 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoPerformanceStatistics.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoPerformanceStatistics.kt @@ -5,6 +5,9 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty import java.time.OffsetDateTime +/** + * @see [SumoStatisticsXml] + */ data class SumoPerformanceStatistics( @field:JacksonXmlProperty(isAttribute = true) @field:JsonDeserialize(using = UnixDoubleTimestampDeserializer::class) diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoPersonTeleports.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoPersonTeleports.kt index 1b8f17d..0766012 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoPersonTeleports.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoPersonTeleports.kt @@ -2,9 +2,14 @@ package app.urbanflo.urbanflosumoserver.model.output.statistics import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +/** + * @see [SumoStatisticsXml] + */ data class SumoPersonTeleports( @field:JacksonXmlProperty(isAttribute = true) val total: Int, + @field:JacksonXmlProperty(isAttribute = true) val abortWait: Int, + @field:JacksonXmlProperty(isAttribute = true) val wrongDest: Int ) diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoPersonsStatistics.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoPersonsStatistics.kt new file mode 100644 index 0000000..164a4aa --- /dev/null +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoPersonsStatistics.kt @@ -0,0 +1,15 @@ +package app.urbanflo.urbanflosumoserver.model.output.statistics + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty + +/** + * @see [SumoStatisticsXml] + */ +data class SumoPersonsStatistics( + @field:JacksonXmlProperty(isAttribute = true) + val loaded: Int, + @field:JacksonXmlProperty(isAttribute = true) + val running: Int, + @field:JacksonXmlProperty(isAttribute = true) + val jammed: Int +) \ No newline at end of file diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoPersonsStatitsics.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoPersonsStatitsics.kt deleted file mode 100644 index 5e8ee52..0000000 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoPersonsStatitsics.kt +++ /dev/null @@ -1,7 +0,0 @@ -package app.urbanflo.urbanflosumoserver.model.output.statistics - -data class SumoPersonsStatitsics( - val loaded: Int, - val running: Int, - val jammed: Int -) \ No newline at end of file diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoRideStatistics.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoRideStatistics.kt index d26978d..d46ac51 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoRideStatistics.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoRideStatistics.kt @@ -2,6 +2,9 @@ package app.urbanflo.urbanflosumoserver.model.output.statistics import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +/** + * @see [SumoStatisticsXml] + */ data class SumoRideStatistics( @field:JacksonXmlProperty(isAttribute = true) val number: Int diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoSafetyStatistics.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoSafetyStatistics.kt index d4ca4ef..9ac1004 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoSafetyStatistics.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoSafetyStatistics.kt @@ -2,9 +2,14 @@ package app.urbanflo.urbanflosumoserver.model.output.statistics import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +/** + * @See [SumoStatisticsXml] + */ data class SumoSafetyStatistics( @field:JacksonXmlProperty(isAttribute = true) val collisions: Int, + @field:JacksonXmlProperty(isAttribute = true) val emergencyStops: Int, + @field:JacksonXmlProperty(isAttribute = true) val emergencyBraking: Int, ) diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoStatisticsXml.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoStatisticsXml.kt index 033b021..1ac44f3 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoStatisticsXml.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoStatisticsXml.kt @@ -5,7 +5,9 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement import java.nio.file.Path - +/** + * Data class for SUMO [statistic output.](https://sumo.dlr.de/docs/Simulation/Output/StatisticOutput.html) + */ @JacksonXmlRootElement(localName = "statistics") @JsonIgnoreProperties(ignoreUnknown = true) data class SumoStatisticsXml( @@ -13,7 +15,7 @@ data class SumoStatisticsXml( val vehicles: SumoVehiclesStatistics, val teleports: SumoTeleportsStatistics, val safety: SumoSafetyStatistics, - val persons: SumoPersonsStatitsics, + val persons: SumoPersonsStatistics, val personTeleports: SumoPersonTeleports, val vehicleTripStatistics: SumoVehicleTripStatistics, val pedestrianStatistics: SumoPedestrianStatistics, diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoTeleportsStatistics.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoTeleportsStatistics.kt index 5d6c894..4dbf18b 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoTeleportsStatistics.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoTeleportsStatistics.kt @@ -2,10 +2,16 @@ package app.urbanflo.urbanflosumoserver.model.output.statistics import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +/** + * @See [SumoStatisticsXml] + */ data class SumoTeleportsStatistics( @field:JacksonXmlProperty(isAttribute = true) val total: Int, + @field:JacksonXmlProperty(isAttribute = true) val jam: Int, + @field:JacksonXmlProperty(isAttribute = true) val yield: Int, + @field:JacksonXmlProperty(isAttribute = true) val wrongLane: Int ) \ No newline at end of file diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoTransportStatistics.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoTransportStatistics.kt index bb8f949..030819e 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoTransportStatistics.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoTransportStatistics.kt @@ -2,6 +2,9 @@ package app.urbanflo.urbanflosumoserver.model.output.statistics import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +/** + * @see [SumoStatisticsXml] + */ data class SumoTransportStatistics( @field:JacksonXmlProperty(isAttribute = true) val number: Int diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoVehicleTripStatistics.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoVehicleTripStatistics.kt index bfdf377..fe1ff0c 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoVehicleTripStatistics.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoVehicleTripStatistics.kt @@ -2,6 +2,9 @@ package app.urbanflo.urbanflosumoserver.model.output.statistics import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +/** + * @see [SumoStatisticsXml] + */ data class SumoVehicleTripStatistics( @field:JacksonXmlProperty(isAttribute = true) val count: Int, diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoVehiclesStatistics.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoVehiclesStatistics.kt index eb24dca..bacb3ee 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoVehiclesStatistics.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/statistics/SumoVehiclesStatistics.kt @@ -2,6 +2,9 @@ package app.urbanflo.urbanflosumoserver.model.output.statistics import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +/** + * @see [SumoStatisticsXml] + */ data class SumoVehiclesStatistics( @field:JacksonXmlProperty(isAttribute = true) val loaded: Int, diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/summary/SumoSummaryStep.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/summary/SumoSummaryStep.kt index f5513b8..b1c9a47 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/summary/SumoSummaryStep.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/summary/SumoSummaryStep.kt @@ -2,6 +2,9 @@ package app.urbanflo.urbanflosumoserver.model.output.summary import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +/** + * @see [SumoSummaryXml] + */ data class SumoSummaryStep( @field:JacksonXmlProperty(isAttribute = true) val time: Double, diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/summary/SumoSummaryXml.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/summary/SumoSummaryXml.kt index 381fe73..735f614 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/summary/SumoSummaryXml.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/summary/SumoSummaryXml.kt @@ -7,6 +7,9 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement import java.nio.file.Path +/** + * Data class for SUMO [summary output.](https://sumo.dlr.de/docs/Simulation/Output/Summary.html) + */ @JacksonXmlRootElement(localName = "summary") @JsonIgnoreProperties(ignoreUnknown = true) data class SumoSummaryXml( diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/tripinfo/SumoTripInfo.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/tripinfo/SumoTripInfo.kt index 278f092..e354951 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/tripinfo/SumoTripInfo.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/tripinfo/SumoTripInfo.kt @@ -2,6 +2,9 @@ package app.urbanflo.urbanflosumoserver.model.output.tripinfo import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +/** + * @see [SumoTripInfoXml] + */ data class SumoTripInfo( @field:JacksonXmlProperty(isAttribute = true) val id: String, diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/tripinfo/SumoTripInfoXml.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/tripinfo/SumoTripInfoXml.kt index efbc8b7..3fa07e7 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/tripinfo/SumoTripInfoXml.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/output/tripinfo/SumoTripInfoXml.kt @@ -7,6 +7,9 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement import java.nio.file.Path +/** + * Data class for SUMO [`tripinfo` output,](https://sumo.dlr.de/docs/Simulation/Output/TripInfo.html) + */ @JacksonXmlRootElement(localName = "tripinfos") @JsonIgnoreProperties(ignoreUnknown = true) data class SumoTripInfoXml( diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/sumocfg/SumoCfg.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/sumocfg/SumoCfg.kt index 78daf28..7174041 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/sumocfg/SumoCfg.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/model/sumocfg/SumoCfg.kt @@ -4,6 +4,9 @@ import app.urbanflo.urbanflosumoserver.simulation.SimulationId import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement import java.nio.file.Path +/** + * Data class for [SUMO configuration XML.](https://sumo.dlr.de/xsd/sumoConfiguration.xsd) + */ @JacksonXmlRootElement(localName = "configuration") class SumoCfg( simulationId: SimulationId, diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/netconvert/Netconvert.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/netconvert/Netconvert.kt index 801e102..d85073e 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/netconvert/Netconvert.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/netconvert/Netconvert.kt @@ -4,6 +4,17 @@ import app.urbanflo.urbanflosumoserver.simulation.SimulationId import java.io.IOException import java.nio.file.Path +/** + * Converts the node, edge and connection files to a network XML file using `netconvert`. + * + * @param simulationId Simulation ID + * @param simulationDir path to root directory where all simulation files are stored + * @param nodPath path to nodes XMl file + * @param edgPath path to edges XMl file + * @param conPath path to connections XMl file + * @return A path to the network XML file + * @throws NetconvertException if `netconvert` returns a non-zero exit status + */ fun runNetconvert(simulationId: SimulationId, simulationDir: Path, nodPath: Path, edgPath: Path, conPath: Path): Path { val netPath = simulationDir.resolve("$simulationId.net.xml").normalize().toAbsolutePath() val netconvertCmd = @@ -20,7 +31,9 @@ fun runNetconvert(simulationId: SimulationId, simulationDir: Path, nodPath: Path .redirectError(ProcessBuilder.Redirect.PIPE) .start() val statusCode = process.waitFor() - if (statusCode != 0) { + if (statusCode == 0) { + return netPath + } else { val stdout = try { process.inputStream.bufferedReader().readText() } catch (e: IOException) { @@ -32,7 +45,5 @@ fun runNetconvert(simulationId: SimulationId, simulationDir: Path, nodPath: Path "" } throw NetconvertException("netconvert exited with status code $statusCode\nstdout:\n$stdout\nstderr:\n$stderr") - } else { - return netPath } } \ No newline at end of file diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/netconvert/NetconvertException.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/netconvert/NetconvertException.kt index 81aa39b..902327d 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/netconvert/NetconvertException.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/netconvert/NetconvertException.kt @@ -1,5 +1,6 @@ package app.urbanflo.urbanflosumoserver.netconvert -class NetconvertException : Exception { - constructor(message: String) : super(message) -} \ No newline at end of file +/** + * Exception for errors in [runNetconvert], which is when `netconvert` returns a non-zero exit status. + */ +class NetconvertException(message: String) : Exception(message) \ No newline at end of file diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/simulation/SimulationInstance.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/simulation/SimulationInstance.kt index 3763e2d..3685af6 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/simulation/SimulationInstance.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/simulation/SimulationInstance.kt @@ -22,14 +22,38 @@ typealias SimulationId = @NotEmpty String private const val DEFAULT_NUM_RETRIES = 60 private val logger = KotlinLogging.logger {} +/** + * Class for running simulation instances. + */ class SimulationInstance( + /** + * Simulation ID + */ val simulationId: SimulationId, + /** + * TraCI label for this simulation. Can be anything unique but is currently the WebSocket session ID + */ val label: String, cfgPath: Path ) : Iterator { + /** + * Map of vehicle colours by vehicle ID + */ private val vehicleColors: MutableMap = mutableMapOf() + /** + * TraCI port for this simulation's connection + */ private val port: Int = getNextAvailablePort() + + /** + * Time frame for each simulation step. Note that this is only a target and it can vary depending on performance and + * other factors. + */ private var frameTime = setSimulationSpeed(1) + + /** + * [Flux] instance for simulation steps + */ var flux = Flux.create { sink -> while (hasNext()) { sink.next(next()) @@ -37,12 +61,23 @@ class SimulationInstance( sink.complete() } + /** + * If `true`, the simulation should be closed on next invocation of [hasNext] if it hasn't already closed. + */ @Volatile private var shouldStop = false + /** + * If `true`, the simulation has been closed + */ @Volatile private var connectionClosed = false + /** + * Calculate the frame time (in ms) for the given simulation speed. + * + * The framerate at 1x speed is 60fps, so its frame time would be around 16.7ms. + */ fun setSimulationSpeed(speed: Long): Duration = Duration.ofMillis(1000 / (60 * speed)) init { @@ -135,6 +170,9 @@ class SimulationInstance( return pairs } + /** + * Returns the vehicle colour (in HTML hex format) for the vehicle ID, or generates one if it's not yet assigned. + */ private fun getVehicleColor(vehicleId: String): String { // if let color = vehicleColors[vehicleId] { return color } else { assign random colour to vehicle and return } val color = vehicleColors[vehicleId] ?: run { @@ -145,10 +183,22 @@ class SimulationInstance( return color } + /** + * Signal the instance that simulation should be stopped. + * + * Note that this doesn't actually close the connection to TraCI, as this is naturally done on next invocation of + * [hasNext] by the WebSocket stream. + */ fun stopSimulation() { shouldStop = true } + /** + * Forcibly mark simulation to be stopped and close the connection to TraCI. + * + * Note that this should be only called for server shutdown. Bad things will happen when called in any other + * situations. + */ fun forceCloseConnectionOnServerShutdown() { try { lock.lock() @@ -162,6 +212,11 @@ class SimulationInstance( } } + /** + * Close the connection to TraCI. + * + * Note: there's no lock here as this method is assumed to only be called inside a locked section + */ private fun closeSimulation() { logger.info { "Closing connection with ID $simulationId and label: ${Simulation.getLabel()}" } Simulation.close() @@ -169,9 +224,17 @@ class SimulationInstance( } companion object { + /** + * Thread lock to prevent race conditions + */ private val lock = ReentrantLock() + + /** + * returns the next random port that is available for TraCI connection. + * + * [Source/adapted from](https://stackoverflow.com/a/2675416) + */ private fun getNextAvailablePort(): Int { - // https://stackoverflow.com/questions/2675362/how-to-find-an-available-port val socket = ServerSocket(0) val socketPort = socket.localPort socket.close() diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/FilesystemStorageService.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/FilesystemStorageService.kt index 47bf976..ac7a59e 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/FilesystemStorageService.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/FilesystemStorageService.kt @@ -37,10 +37,24 @@ import kotlin.io.path.listDirectoryEntries private val logger = KotlinLogging.logger {} +/** + * Storage service, using the file system as storage. + */ @Service class FilesystemStorageService @Autowired constructor(properties: StorageProperties) : StorageService { + /** + * Root directory for all uploads + */ private lateinit var uploadsDir: Path + + /** + * Jackson XMl mapper + */ private val xmlMapper = XmlMapper() + + /** + * Jackson JSON mapper + */ private val jsonMapper = jacksonObjectMapper() init { @@ -105,6 +119,9 @@ class FilesystemStorageService @Autowired constructor(properties: StoragePropert return newInfo } + /** + * Common function for writing XML files and converting network using `netconvert` + */ private fun writeFiles(simulationInfo: SimulationInfo, network: SumoNetwork, simulationDir: Path) { if (!simulationDir.exists()) { throw IllegalStateException("simulationDir does not exist") @@ -283,8 +300,14 @@ class FilesystemStorageService @Autowired constructor(properties: StoragePropert ) } + /** + * Returns current time as [OffsetDateTime] + */ private fun currentTime() = OffsetDateTime.now(ZoneOffset.UTC) + /** + * Common function for reading and parsing output XML files + */ private inline fun getOutputFile(simulationId: SimulationId, path: Path): T { if (!path.exists()) { throw StorageSimulationNotFoundException(simulationId, "Simulation hasn't started") @@ -307,6 +330,9 @@ class FilesystemStorageService @Autowired constructor(properties: StoragePropert } } + /** + * Get path to simulation directory for the given ID + */ private fun getSimulationDir(simulationId: SimulationId) = if (simulationId.isNotEmpty()) { // if simulationId is empty, it returns uploads dir which could be disastrous uploadsDir.resolve(Paths.get(simulationId).normalize()) } else { diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/StorageBadRequestException.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/StorageBadRequestException.kt index 0bc405f..c943180 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/StorageBadRequestException.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/StorageBadRequestException.kt @@ -1,5 +1,8 @@ package app.urbanflo.urbanflosumoserver.storage +/** + * Exception for invalid storage requests. + */ class StorageBadRequestException : StorageException { constructor(message: String) : super(message) constructor(message: String, cause: Throwable) : super(message, cause) diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/StorageException.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/StorageException.kt index 125c142..363a422 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/StorageException.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/StorageException.kt @@ -1,5 +1,9 @@ package app.urbanflo.urbanflosumoserver.storage +/** + * Exception for any storage errors. Some errors are covered by subclasses of this exception, such as + * [StorageBadRequestException]. + */ open class StorageException : RuntimeException { constructor(message: String) : super(message) constructor(message: String, cause: Throwable) : super(message, cause) diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/StorageProperties.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/StorageProperties.kt index 97b5ec2..885be25 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/StorageProperties.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/StorageProperties.kt @@ -3,6 +3,9 @@ package app.urbanflo.urbanflosumoserver.storage import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Component +/** + * Component which stores the root directory for all simulation files. + */ @Component class StorageProperties { @Value("\${urbanflo.storage.location:uploads}") diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/StorageService.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/StorageService.kt index 42b8361..2e01dc0 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/StorageService.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/StorageService.kt @@ -11,32 +11,94 @@ import app.urbanflo.urbanflosumoserver.model.output.tripinfo.SumoTripInfoXml import app.urbanflo.urbanflosumoserver.simulation.SimulationId import app.urbanflo.urbanflosumoserver.simulation.SimulationInstance +/** + * Interface for storage service classes. + */ interface StorageService { + /** + * Store a new network. + * + * @return New simulation info + * @throws StorageBadRequestException if network data is invalid + * @throws StorageException if simulation cannot be saved + */ fun store(network: SumoNetwork): SimulationInfo + /** + * Modify an existing network for the given simulation ID. + * + * @return Updated simulation info + * @throws StorageBadRequestException if network data is invalid + * @throws StorageException if simulation cannot be saved + */ fun store(simulationId: SimulationId, network: SumoNetwork): SimulationInfo + /** + * Load a simulation to be run. + * + * @param id Simulation ID + * @param label Any string used to uniquely identify the simulation instance to TraCI + * @throws StorageSimulationNotFoundException if simulation cannot be found + */ fun load(id: SimulationId, label: String): SimulationInstance + /** + * Delete a simulation by simulation ID. + * @throws StorageSimulationNotFoundException if simulation cannot be found + */ fun delete(id: SimulationId) + /** + * Get information about a simulation for the given ID. + * @throws StorageSimulationNotFoundException if simulation cannot be found + */ fun info(id: SimulationId): SimulationInfo + /** + * Export network data of a simulation for the given ID. + * @throws StorageSimulationNotFoundException if simulation cannot be found + */ fun export(simulationId: SimulationId): SumoNetwork + /** + * List all simulation information. + */ fun listAll(): List @Deprecated("Please use the individual getOutput() functions") fun getSimulationOutput(simulationId: SimulationId): SumoSimulationOutput + /** + * returns a SUMO [`tripinfo` output](https://sumo.dlr.de/docs/Simulation/Output/TripInfo.html) for the given ID. + * + * @throws StorageSimulationNotFoundException if simulation cannot be found, simulation hasn't started or simulation wasn't closed properly + */ fun getTripInfoOutput(simulationId: SimulationId): SumoTripInfoXml + /** + * returns a SUMO [`netstate` output](https://sumo.dlr.de/docs/Simulation/Output/RawDump.html) output for the given ID. + * + * @throws StorageSimulationNotFoundException if simulation cannot be found, simulation hasn't started or simulation wasn't closed properly + */ fun getNetStateOutput(simulationId: SimulationId): SumoNetstateXml + /** + * returns a SUMO [summary output](https://sumo.dlr.de/docs/Simulation/Output/Summary.html) for the given ID. + * + * @throws StorageSimulationNotFoundException if simulation cannot be found, simulation hasn't started or simulation wasn't closed properly + */ fun getSummaryOutput(simulationId: SimulationId): SumoSummaryXml + /** + * returns a SUMO [statistic output](https://sumo.dlr.de/docs/Simulation/Output/StatisticOutput.html) for the given ID. + * + * @throws StorageSimulationNotFoundException if simulation cannot be found, simulation hasn't started or simulation wasn't closed properly + */ fun getStatisticsOutput(simulationId: SimulationId): SumoStatisticsXml + /** + * Delete simulation output for the given ID. + */ fun deleteSimulationOutput(simulationId: SimulationId) @Deprecated("Please use getStatisticsOutput() as it's faster and gives more information") diff --git a/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/StorageSimulationNotFoundException.kt b/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/StorageSimulationNotFoundException.kt index e4e5c26..fe65fb4 100644 --- a/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/StorageSimulationNotFoundException.kt +++ b/src/main/kotlin/app/urbanflo/urbanflosumoserver/storage/StorageSimulationNotFoundException.kt @@ -2,6 +2,9 @@ package app.urbanflo.urbanflosumoserver.storage import app.urbanflo.urbanflosumoserver.simulation.SimulationId +/** + * Exception for when the simulation cannot be found. + */ class StorageSimulationNotFoundException : StorageException { constructor(simulationId: SimulationId): super("No such simulation with ID $simulationId") constructor(simulationId: SimulationId, cause: Throwable): super("No such simulation with ID $simulationId", cause) diff --git a/src/test/kotlin/app/urbanflo/urbanflosumoserver/ApiTests.kt b/src/test/kotlin/app/urbanflo/urbanflosumoserver/ApiTests.kt index b7d15bf..ec15e6a 100644 --- a/src/test/kotlin/app/urbanflo/urbanflosumoserver/ApiTests.kt +++ b/src/test/kotlin/app/urbanflo/urbanflosumoserver/ApiTests.kt @@ -26,6 +26,9 @@ import org.springframework.core.io.ClassPathResource import org.springframework.http.* import java.io.File +/** + * Tests for controller APIs. + */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class ApiTests(@Autowired private val restTemplate: TestRestTemplate) { @Value(value = "\${local.server.port}") diff --git a/src/test/kotlin/app/urbanflo/urbanflosumoserver/SimulationTests.kt b/src/test/kotlin/app/urbanflo/urbanflosumoserver/SimulationTests.kt index 3843f7d..52dfaad 100644 --- a/src/test/kotlin/app/urbanflo/urbanflosumoserver/SimulationTests.kt +++ b/src/test/kotlin/app/urbanflo/urbanflosumoserver/SimulationTests.kt @@ -28,6 +28,9 @@ import java.util.* import java.util.concurrent.CompletableFuture import java.util.concurrent.Future +/** + * Tests for simulation instances. + */ private val logger = KotlinLogging.logger {} private const val DOUBLE_CMP_TOLERANCE = 0.1 diff --git a/src/test/kotlin/app/urbanflo/urbanflosumoserver/StorageTests.kt b/src/test/kotlin/app/urbanflo/urbanflosumoserver/StorageTests.kt index d6bd3b5..d23636f 100644 --- a/src/test/kotlin/app/urbanflo/urbanflosumoserver/StorageTests.kt +++ b/src/test/kotlin/app/urbanflo/urbanflosumoserver/StorageTests.kt @@ -22,6 +22,9 @@ import org.springframework.boot.test.context.SpringBootTest import org.springframework.core.io.ClassPathResource import java.io.File +/** + * Tests for storage service. + */ @SpringBootTest class StorageTests(@Autowired private val storageService: FilesystemStorageService) { @Value("\${urbanflo.storage.location:uploads}")