diff --git a/doc/source/user_guide/sim_user_guide/components/spawner.rst b/doc/source/user_guide/sim_user_guide/components/spawner.rst index 622027c77b4ce2ae39e0d09a53b8e8ade4fe9487..1d2879efca7d70c27f5f62d4cb4ebed6d74918bf 100644 --- a/doc/source/user_guide/sim_user_guide/components/spawner.rst +++ b/doc/source/user_guide/sim_user_guide/components/spawner.rst @@ -54,7 +54,8 @@ The SpawnZones have the following parameters: ========= ============ ==== ======== ================================================================================== Roads StringVector no List of linked Roads on which to spawn Agents. Intermediate roads may be omitted. - If a road is not linked to the previous in the list, this road and the roads after it are ignored + If a road is not linked to the previous in the list (or doesn't exist), this road and the roads after it are ignored. + This means in particular, that if the first road in the list does not exist, the entire SpawnZone is ignored. Lanes IntVector yes The LaneIDs of the Lanes of the Road on which to spawn Agents (given on SStart). Inexistent lanes are ignored. If omitted all lanes are used. @@ -65,7 +66,13 @@ The SpawnZones have the following parameters: This is ignored if SEnd is explicitly defined. If neither is given the whole road is included ========= ============ ==== ======== ================================================================================== - + + .. note:: + + Two SpawnZones should not intersect each other. The behavior in this case is undefined. The PreRunSpawner is not required to fill the SpawnZones in the given order. + +SStart, SEnd and SLength may be out of range for the road. +In this case they are cropped such that the spawn range is maximum within the road's bounds. It is also possible to define the minimum gap in meters either as fixed parameter of type double or as stochastic distribution. If it isn't defined the default value of 5m is used. diff --git a/sim/src/core/slave/modules/Spawners/PreRunCommon/SpawnerPreRunCommonParameterExtractor.h b/sim/src/core/slave/modules/Spawners/PreRunCommon/SpawnerPreRunCommonParameterExtractor.h index abb84542a1d1264e1bd42b36981dbf56fb144ac1..571dc060109356f5cadc8f04c03c3301434fabd7 100644 --- a/sim/src/core/slave/modules/Spawners/PreRunCommon/SpawnerPreRunCommonParameterExtractor.h +++ b/sim/src/core/slave/modules/Spawners/PreRunCommon/SpawnerPreRunCommonParameterExtractor.h @@ -179,30 +179,36 @@ static std::vector ExtractSpawnAreas(const ParameterInterface ¶me { if (sStartElement.has_value()) { - sStartOnStream = roadStream->GetStreamPosition(GlobalRoadPosition{route.front().roadId, 0, sStartElement.value(), 0, 0}).s; + double sStart = std::clamp(sStartElement.value(), 0.0, world.GetRoadLength(route.front().roadId)); + sStartOnStream = roadStream->GetStreamPosition(GlobalRoadPosition{route.front().roadId, 0, sStart, 0, 0}).s; } if (sEndElement.has_value()) { - sEndOnStream = roadStream->GetStreamPosition(GlobalRoadPosition{route.back().roadId, 0, sEndElement.value(), 0, 0}).s; + double sEnd = std::clamp(sEndElement.value(), 0.0, world.GetRoadLength(route.back().roadId)); + sEndOnStream = roadStream->GetStreamPosition(GlobalRoadPosition{route.back().roadId, 0, sEnd, 0, 0}).s; } else if (sLengthElement.has_value()) { - sEndOnStream = sStartOnStream + sLengthElement.value(); + double sLength = std::clamp(sLengthElement.value(), 0.0, roadStream->GetLength() - sStartOnStream); + sEndOnStream = sStartOnStream + sLength; } } else { if (sStartElement.has_value()) { - sEndOnStream = roadStream->GetStreamPosition(GlobalRoadPosition{route.back().roadId, 0, sStartElement.value(), 0, 0}).s; + double sStart = std::clamp(sStartElement.value(), 0.0, world.GetRoadLength(route.back().roadId)); + sEndOnStream = roadStream->GetStreamPosition(GlobalRoadPosition{route.back().roadId, 0, sStart, 0, 0}).s; } if (sEndElement.has_value()) { - sStartOnStream = roadStream->GetStreamPosition(GlobalRoadPosition{route.front().roadId, 0, sEndElement.value(), 0, 0}).s; + double sEnd = std::clamp(sEndElement.value(), 0.0, world.GetRoadLength(route.front().roadId)); + sStartOnStream = roadStream->GetStreamPosition(GlobalRoadPosition{route.front().roadId, 0, sEnd, 0, 0}).s; } else if (sLengthElement.has_value()) { - sStartOnStream = sEndOnStream - sLengthElement.value(); + double sLength = std::clamp(sLengthElement.value(), 0.0, sEndOnStream); + sStartOnStream = sEndOnStream - sLength; } } SPAWNER_THROWIFFALSE(sStartOnStream < sEndOnStream, "Invalid range"); diff --git a/sim/tests/integrationTests/Spawner_IntegrationTests/Spawner_IntegrationTests.cpp b/sim/tests/integrationTests/Spawner_IntegrationTests/Spawner_IntegrationTests.cpp index d8f7e21b95396eeac535eb19f9c37c77a730ed73..cfeade6ba99d37d36974c2cd8b5eb4ac15602b75 100644 --- a/sim/tests/integrationTests/Spawner_IntegrationTests/Spawner_IntegrationTests.cpp +++ b/sim/tests/integrationTests/Spawner_IntegrationTests/Spawner_IntegrationTests.cpp @@ -335,6 +335,7 @@ TEST_F(SpawnerPreRun_IntegrationTests, ThreeContinuesLanes_SpawnWithCorrectTGapA }); ON_CALL(world, IsSValidOnLane(ROADID, AllOf(Le(-1),Ge(-3)),AllOf(Ge(0),Le(2000)))).WillByDefault(Return(true)); ON_CALL(world, GetLaneWidth(_,_,_)).WillByDefault(Return(3.0)); + ON_CALL(world, GetRoadLength(_)).WillByDefault(Return(10000.)); RouteQueryResult> noObjects{{0, {}}}; ON_CALL(world, GetObjectsInRange(_,_,_,_,_,_)).WillByDefault(Return(noObjects)); @@ -382,6 +383,7 @@ TEST_F(SpawnerPreRun_IntegrationTests, IncreasingLaneNumber_SpawnWithCorrectTGap ON_CALL(world, IsSValidOnLane(ROADID, AllOf(Le(-1),Ge(-2)),AllOf(Ge(0),Le(2000)))).WillByDefault(Return(true)); ON_CALL(world, IsSValidOnLane(ROADID, -3,AllOf(Ge(1400),Le(2000)))).WillByDefault(Return(true)); ON_CALL(world, GetLaneWidth(_,_,_)).WillByDefault(Return(3.0)); + ON_CALL(world, GetRoadLength(_)).WillByDefault(Return(10000.)); RouteQueryResult> noObjects{{0, {}}}; ON_CALL(world, GetObjectsInRange(_,_,_,_,_,_)).WillByDefault(Return(noObjects)); @@ -440,6 +442,7 @@ TEST_F(SpawnerPreRun_IntegrationTests, DecreasingLaneNumber_SpawnWithCorrectTGap ON_CALL(world, IsSValidOnLane(ROADID, AllOf(Le(-1),Ge(-2)),AllOf(Ge(0),Le(2000)))).WillByDefault(Return(true)); ON_CALL(world, IsSValidOnLane(ROADID, -3,AllOf(Ge(0),Le(1200)))).WillByDefault(Return(true)); ON_CALL(world, GetLaneWidth(_,_,_)).WillByDefault(Return(3.0)); + ON_CALL(world, GetRoadLength(_)).WillByDefault(Return(10000.)); RouteQueryResult> noObjects{{0, {}}}; ON_CALL(world, GetObjectsInRange(_,_,_,_,_,_)).WillByDefault(Return(noObjects)); @@ -498,6 +501,7 @@ TEST_F(SpawnerPreRun_IntegrationTests, RightLaneStartsAndEndsWithinRange_SpawnWi ON_CALL(world, IsSValidOnLane(ROADID, AllOf(Le(-1),Ge(-2)),AllOf(Ge(0),Le(2000)))).WillByDefault(Return(true)); ON_CALL(world, IsSValidOnLane(ROADID, -3,AllOf(Ge(1200),Le(1400)))).WillByDefault(Return(true)); ON_CALL(world, GetLaneWidth(_,_,_)).WillByDefault(Return(3.0)); + ON_CALL(world, GetRoadLength(_)).WillByDefault(Return(10000.)); RouteQueryResult> noObjects{{0, {}}}; ON_CALL(world, GetObjectsInRange(_,_,_,_,_,_)).WillByDefault(Return(noObjects)); diff --git a/sim/tests/unitTests/core/slave/modules/SpawnerPreRunCommon/spawnerPreRunCommon_Tests.cpp b/sim/tests/unitTests/core/slave/modules/SpawnerPreRunCommon/spawnerPreRunCommon_Tests.cpp index 4dfb7fcd9a462669080fbbe5f2fcde7907c969c7..9de0c8e9fe86cb3fdff6fda08f247aa6790544f0 100644 --- a/sim/tests/unitTests/core/slave/modules/SpawnerPreRunCommon/spawnerPreRunCommon_Tests.cpp +++ b/sim/tests/unitTests/core/slave/modules/SpawnerPreRunCommon/spawnerPreRunCommon_Tests.cpp @@ -124,6 +124,7 @@ TEST(SpawnerPreRunCommonParameterExtractor, ExtractSpawnAreas_AllOptionalParamet ON_CALL(*roadStreamLong, GetLaneStream(_,-1)).WillByDefault(Return(ByMove(std::unique_ptr(laneStream1)))); LaneStreamInterface* laneStream2 = new FakeLaneStream; ON_CALL(*roadStreamLong, GetLaneStream(_,-2)).WillByDefault(Return(ByMove(std::unique_ptr(laneStream2)))); + ON_CALL(*roadStreamLong, GetLength()).WillByDefault(Return(1500.0)); ON_CALL(*roadStreamLong, GetStreamPosition(_)).WillByDefault([](GlobalRoadPosition position) {if(position.roadId == "RoadA") {return StreamPosition{position.roadPosition.s, 0};} @@ -138,12 +139,14 @@ TEST(SpawnerPreRunCommonParameterExtractor, ExtractSpawnAreas_AllOptionalParamet ON_CALL(*roadStreamShort, GetLaneStream(_,-3)).WillByDefault(Return(ByMove(std::unique_ptr(laneStream3)))); LaneStreamInterface* laneStream4 = new FakeLaneStream; ON_CALL(*roadStreamShort, GetLaneStream(_,-4)).WillByDefault(Return(ByMove(std::unique_ptr(laneStream4)))); + ON_CALL(*roadStreamShort, GetLength()).WillByDefault(Return(1000.0)); ON_CALL(*roadStreamShort, GetStreamPosition(_)).WillByDefault([](GlobalRoadPosition position) {if(position.roadId == "RoadA") {return StreamPosition{position.roadPosition.s, 0};} return StreamPosition{-1, 0};}); ON_CALL(fakeWorld, GetRoadStream(std::vector{{"RoadA", true}})). WillByDefault(Return(ByMove(std::move(roadStreamShort)))); + ON_CALL(fakeWorld, GetRoadLength(_)).WillByDefault(Return(1000.)); auto result = ExtractSpawnAreas(parameter, fakeWorld, &callbacks); @@ -222,6 +225,7 @@ TEST(SpawnerPreRunCommonParameterExtractor, ExtractSpawnAreas_NoOptionalParamete ON_CALL(*roadStreamShort, GetLength()).WillByDefault(Return(1000.0)); ON_CALL(fakeWorld, GetRoadStream(std::vector{{"RoadA", true}})). WillByDefault(Return(ByMove(std::move(roadStreamShort)))); + ON_CALL(fakeWorld, GetRoadLength(_)).WillByDefault(Return(1000.)); auto result = ExtractSpawnAreas(parameter, fakeWorld, &callbacks); @@ -240,7 +244,96 @@ TEST(SpawnerPreRunCommonParameterExtractor, ExtractSpawnAreas_NoOptionalParamete EXPECT_THAT(result.at(3).sEnd, Eq(1000.0)); } -TEST(SpawnerPreRunCommonParameterExtractor, ExtractSpawnAreas_AgaintOdDirection) +TEST(SpawnerPreRunCommonParameterExtractor, ExtractSpawnAreasWithSOutOfRange_ReturnsValidS) +{ + FakeCallback callbacks; + + FakeParameter parameter; + auto spawnPoint1 = std::make_shared(); + auto spawnPoint2 = std::make_shared(); + ParameterInterface::ParameterLists spawnPoints {{spawnPoint1, spawnPoint2}}; + + std::map> strings1{{"Roads", {"RoadA", "RoadB"}}}; + ON_CALL(*spawnPoint1, GetParametersStringVector()).WillByDefault(ReturnRef(strings1)); + std::map> intVectors1{{"Lanes", {-1,-2}}}; + ON_CALL(*spawnPoint1, GetParametersIntVector()).WillByDefault(ReturnRef(intVectors1)); + std::map doubles1{{"SStart", -10.0}, {"SEnd", 750.0}}; + ON_CALL(*spawnPoint1, GetParametersDouble()).WillByDefault(ReturnRef(doubles1)); + + std::map> strings2{{"Roads", {"RoadA"}}}; + ON_CALL(*spawnPoint2, GetParametersStringVector()).WillByDefault(ReturnRef(strings2)); + std::map> intVectors2{}; + ON_CALL(*spawnPoint2, GetParametersIntVector()).WillByDefault(ReturnRef(intVectors2)); + std::map doubles2{{"SStart", 10.0}, {"SLength", 1500.0}}; + ON_CALL(*spawnPoint2, GetParametersDouble()).WillByDefault(ReturnRef(doubles2)); + + std::map parameterLists{{"SpawnZones", spawnPoints}}; + ON_CALL(parameter, GetParameterLists()).WillByDefault(ReturnRef(parameterLists)); + + FakeWorld fakeWorld; + ON_CALL(fakeWorld, IsDirectionalRoadExisting(_,true)).WillByDefault(Return(true)); + ON_CALL(fakeWorld, IsDirectionalRoadExisting(_,false)).WillByDefault(Return(false)); + + RoadGraph roadGraphIn; + auto nodeA = add_vertex(RouteElement{"RoadA", true}, roadGraphIn); + auto nodeB = add_vertex(RouteElement{"RoadB", true}, roadGraphIn); + add_edge(nodeA, nodeB, roadGraphIn); + RoadGraph roadGraphAgainst; + auto nodeAAgainst = add_vertex(RouteElement{"RoadA", false}, roadGraphAgainst); + ON_CALL(fakeWorld, GetRoadGraph(RouteElement{"RoadA", true}, _)).WillByDefault(Return(std::make_pair(roadGraphIn, nodeA))); + ON_CALL(fakeWorld, GetRoadGraph(RouteElement{"RoadA", false}, _)).WillByDefault(Return(std::make_pair(roadGraphAgainst, nodeAAgainst))); + + std::unique_ptr roadStreamLong = std::make_unique(); + LaneStreamInterface* laneStream1 = new FakeLaneStream; + ON_CALL(*roadStreamLong, GetLaneStream(_,-1)).WillByDefault(Return(ByMove(std::unique_ptr(laneStream1)))); + LaneStreamInterface* laneStream2 = new FakeLaneStream; + ON_CALL(*roadStreamLong, GetLaneStream(_,-2)).WillByDefault(Return(ByMove(std::unique_ptr(laneStream2)))); + ON_CALL(*roadStreamLong, GetLength()).WillByDefault(Return(1500.0)); + ON_CALL(*roadStreamLong, GetStreamPosition(_)).WillByDefault([](GlobalRoadPosition position) + {if(position.roadId == "RoadA") + {return StreamPosition{position.roadPosition.s, 0};} + if(position.roadId == "RoadB") + {return StreamPosition{position.roadPosition.s + 1000.0, 0};} + return StreamPosition{-1, 0};}); + ON_CALL(fakeWorld, GetRoadStream(std::vector{{"RoadA", true}, {"RoadB", true}})). + WillByDefault(Return(ByMove(std::move(roadStreamLong)))); + + std::unique_ptr roadStreamShort = std::make_unique(); + LaneStreamInterface* laneStream3 = new FakeLaneStream; + LaneStreamInterface* laneStream4 = new FakeLaneStream; + std::vector> laneStreamsB{}; + laneStreamsB.emplace_back(laneStream3); + laneStreamsB.emplace_back(laneStream4); + ON_CALL(*roadStreamShort, GetAllLaneStreams()).WillByDefault(Return(ByMove(std::move(laneStreamsB)))); + ON_CALL(*roadStreamShort, GetLength()).WillByDefault(Return(1000.0)); + ON_CALL(*roadStreamShort, GetStreamPosition(_)).WillByDefault([](GlobalRoadPosition position) + {if(position.roadId == "RoadA") + {return StreamPosition{position.roadPosition.s, 0};} + return StreamPosition{-1, 0};}); + ON_CALL(fakeWorld, GetRoadStream(std::vector{{"RoadA", true}})). + WillByDefault(Return(ByMove(std::move(roadStreamShort)))); + + ON_CALL(fakeWorld, GetRoadLength("RoadA")).WillByDefault(Return(1000.)); + ON_CALL(fakeWorld, GetRoadLength("RoadB")).WillByDefault(Return(500.)); + + auto result = ExtractSpawnAreas(parameter, fakeWorld, &callbacks); + + ASSERT_THAT(result, SizeIs(4)); + EXPECT_THAT(result.at(0).laneStream.get(), Eq(laneStream1)); + EXPECT_THAT(result.at(0).sStart, Eq(0.0)); + EXPECT_THAT(result.at(0).sEnd, Eq(1500.0)); + EXPECT_THAT(result.at(1).laneStream.get(), Eq(laneStream2)); + EXPECT_THAT(result.at(1).sStart, Eq(0.0)); + EXPECT_THAT(result.at(1).sEnd, Eq(1500.0)); + EXPECT_THAT(result.at(2).laneStream.get(), Eq(laneStream3)); + EXPECT_THAT(result.at(2).sStart, Eq(10.0)); + EXPECT_THAT(result.at(2).sEnd, Eq(1000.0)); + EXPECT_THAT(result.at(3).laneStream.get(), Eq(laneStream4)); + EXPECT_THAT(result.at(3).sStart, Eq(10.0)); + EXPECT_THAT(result.at(3).sEnd, Eq(1000.0)); +} + +TEST(SpawnerPreRunCommonParameterExtractor, ExtractSpawnAreas_AgainstOdDirection) { FakeCallback callbacks; @@ -273,6 +366,7 @@ TEST(SpawnerPreRunCommonParameterExtractor, ExtractSpawnAreas_AgaintOdDirection) ON_CALL(*roadStream, GetLaneStream(_,1)).WillByDefault(Return(ByMove(std::unique_ptr(laneStream1)))); LaneStreamInterface* laneStream2 = new FakeLaneStream; ON_CALL(*roadStream, GetLaneStream(_,2)).WillByDefault(Return(ByMove(std::unique_ptr(laneStream2)))); + ON_CALL(*roadStream, GetLength()).WillByDefault(Return(1500.0)); ON_CALL(*roadStream, GetStreamPosition(_)).WillByDefault([](GlobalRoadPosition position) {if(position.roadId == "RoadA") {return StreamPosition{1000. - position.roadPosition.s, 0};} @@ -281,6 +375,8 @@ TEST(SpawnerPreRunCommonParameterExtractor, ExtractSpawnAreas_AgaintOdDirection) return StreamPosition{-1, 0};}); ON_CALL(fakeWorld, GetRoadStream(std::vector{{"RoadA", false}, {"RoadB", false}})). WillByDefault(Return(ByMove(std::move(roadStream)))); + ON_CALL(fakeWorld, GetRoadLength("RoadA")).WillByDefault(Return(1000.)); + ON_CALL(fakeWorld, GetRoadLength("RoadB")).WillByDefault(Return(500.)); auto result = ExtractSpawnAreas(parameter, fakeWorld, &callbacks); @@ -334,6 +430,7 @@ TEST(SpawnerPreRunCommonParameterExtractor, ExtractSpawnAreas_AllLanesBothDirect std::vector> laneStreamsIn{}; laneStreamsIn.emplace_back(laneStreamIn); ON_CALL(*roadStreamIn, GetAllLaneStreams()).WillByDefault(Return(ByMove(std::move(laneStreamsIn)))); + ON_CALL(*roadStreamIn, GetLength()).WillByDefault(Return(1500.0)); ON_CALL(*roadStreamIn, GetStreamPosition(_)).WillByDefault([](GlobalRoadPosition position) {if(position.roadId == "RoadA") {return StreamPosition{position.roadPosition.s, 0};} @@ -348,6 +445,7 @@ TEST(SpawnerPreRunCommonParameterExtractor, ExtractSpawnAreas_AllLanesBothDirect std::vector> laneStreamsAgainst{}; laneStreamsAgainst.emplace_back(laneStreamAgainst); ON_CALL(*roadStreamAgainst, GetAllLaneStreams()).WillByDefault(Return(ByMove(std::move(laneStreamsAgainst)))); + ON_CALL(*roadStreamAgainst, GetLength()).WillByDefault(Return(1500.0)); ON_CALL(*roadStreamAgainst, GetStreamPosition(_)).WillByDefault([](GlobalRoadPosition position) {if(position.roadId == "RoadA") {return StreamPosition{1500. - position.roadPosition.s, 0};} @@ -356,6 +454,8 @@ TEST(SpawnerPreRunCommonParameterExtractor, ExtractSpawnAreas_AllLanesBothDirect return StreamPosition{-1, 0};}); ON_CALL(fakeWorld, GetRoadStream(std::vector{{"RoadB", false}, {"RoadA", false}})). WillByDefault(Return(ByMove(std::move(roadStreamAgainst)))); + ON_CALL(fakeWorld, GetRoadLength("RoadA")).WillByDefault(Return(1000.)); + ON_CALL(fakeWorld, GetRoadLength("RoadB")).WillByDefault(Return(500.)); auto result = ExtractSpawnAreas(parameter, fakeWorld, &callbacks);