diff --git a/src/Application/Exception/BothDirectionsNotSupportedAtPointNumbers.php b/src/Application/Exception/BothDirectionsNotSupportedAtPointNumbers.php new file mode 100644 index 000000000..7e5d328fb --- /dev/null +++ b/src/Application/Exception/BothDirectionsNotSupportedAtPointNumbers.php @@ -0,0 +1,9 @@ +command; + + [$command->fromSide, $command->toSide] = $this->pointNumberSideDetector->detect( + $command->direction, + $command->administrator, + $command->roadNumber, + $command->fromPointNumber, + $command->fromAbscissa ?? 0, + $command->toPointNumber, + $command->toAbscissa ?? 0, + ); + if ($query->geometry) { return $query->geometry; } diff --git a/src/Application/RoadGeocoderInterface.php b/src/Application/RoadGeocoderInterface.php index f7738844c..d00ef5fae 100644 --- a/src/Application/RoadGeocoderInterface.php +++ b/src/Application/RoadGeocoderInterface.php @@ -30,4 +30,10 @@ public function findRoadNames(string $search, string $cityCode): array; public function findSectionsInArea(string $areaGeometry, array $excludeTypes = [], ?bool $clipToArea = false): string; public function convertPolygonRoadToLines(string $geometry): string; + + public function getAvailableSidesAtPointNumber( + string $administrator, + string $roadNumber, + string $pointNumber, + ): array; } diff --git a/src/Domain/Regulation/Location/NumberedRoad.php b/src/Domain/Regulation/Location/NumberedRoad.php index 8276ac14a..ba47ee9b1 100644 --- a/src/Domain/Regulation/Location/NumberedRoad.php +++ b/src/Domain/Regulation/Location/NumberedRoad.php @@ -97,4 +97,17 @@ public function update( $this->toSide = $toSide; $this->direction = $direction; } + + /** + * Compare two PR+abs. + * Return -1 if A < B, 0 if A === B, or 1 if A > B + */ + public static function comparePointNumber( + string $pointNumberA, + int $abscissaA, + string $pointNumberB, + int $abscissaB, + ): int { + return 0; + } } diff --git a/src/Infrastructure/Adapter/BdTopoRoadGeocoder.php b/src/Infrastructure/Adapter/BdTopoRoadGeocoder.php index 64f77ceb6..88d75d810 100644 --- a/src/Infrastructure/Adapter/BdTopoRoadGeocoder.php +++ b/src/Infrastructure/Adapter/BdTopoRoadGeocoder.php @@ -414,4 +414,34 @@ public function convertPolygonRoadToLines(string $geometry): string return $row['geom']; } + + public function getAvailableSidesAtPointNumber( + string $administrator, + string $roadNumber, + string $pointNumber, + ): array { + $rows = $this->bdtopoConnection->fetchAllAssociative( + \sprintf( + 'SELECT p.cote AS side + FROM point_de_repere AS p + WHERE p.gestionnaire = :administrator + AND p.route = :roadNumber + AND p.numero = :pointNumber + ', + ), + [ + 'administrator' => $administrator, + 'roadNumber' => $roadNumber, + 'pointNumber' => $pointNumber, + ], + ); + + $sides = []; + + foreach ($rows as $row) { + $sides[] = $row['side']; + } + + return $sides; + } } diff --git a/src/Infrastructure/Adapter/PointNumberSideDetector.php b/src/Infrastructure/Adapter/PointNumberSideDetector.php new file mode 100644 index 000000000..e1ea2d8fa --- /dev/null +++ b/src/Infrastructure/Adapter/PointNumberSideDetector.php @@ -0,0 +1,80 @@ +roadGeocoder->getAvailableSidesAtPointNumber( + $administrator, + $roadNumber, + $fromPointNumber, + ); + + $sidesAtToPoint = $this->roadGeocoder->getAvailableSidesAtPointNumber( + $administrator, + $roadNumber, + $toPointNumber, + ); + + $isSingleWayAtFromPoint = \in_array(RoadSideEnum::U->value, $sidesAtFromPoint); + $isSingleWayAtToPoint = \in_array(RoadSideEnum::U->value, $sidesAtToPoint); + + if ($isSingleWayAtFromPoint || $isSingleWayAtToPoint) { + // L'un des deux PR au moins est sur une chaussée unique + // On doit forcément utiliser des PR de type U. + return [RoadSideEnum::U->value, RoadSideEnum::U->value]; + } + + // Les deux PR se trouvent sur une section à chaussée séparée. + // A priori, les deux côtés G ou D sont possibles au niveau de chaque PR. + // On choisit le côté adéquat en fonction de la direction demandée et de l'ordre + // des PR dans le sens des PR croissants. + // Le "Double sens" n'est pas supporté pour l'instant, il faut saisir deux localisations, + // une dans chaque sens. + + if ($direction === DirectionEnum::BOTH->value) { + // TODO: handle in controller + throw new BothDirectionsNotSupportedAtPointNumbers(); + } + + if (NumberedRoad::comparePointNumber($fromPointNumber, $fromAbscissa, $toPointNumber, $toAbscissa) <= 0) { + // Le PR A est situé avant le PR B, dans l'ordre des PR croissants. + // On doit choisir le côté D si le sens A -> B est demandé, et le côté G sinon. + $side = $direction === DirectionEnum::A_TO_B->value + ? RoadSideEnum::D->value + : RoadSideEnum::G->value; + + return [$side, $side]; + } + + // Le PR A est situé après le PR B, donc on choisit le côté G si A->B est demandé, et le côté D sinon. + $side = $direction === DirectionEnum::A_TO_B->value + ? RoadSideEnum::G->value + : RoadSideEnum::D->value; + + return [$side, $side]; + } +} diff --git a/src/Infrastructure/Form/Regulation/NumberedRoadFormType.php b/src/Infrastructure/Form/Regulation/NumberedRoadFormType.php index 3cc84c180..b2840e9b3 100644 --- a/src/Infrastructure/Form/Regulation/NumberedRoadFormType.php +++ b/src/Infrastructure/Form/Regulation/NumberedRoadFormType.php @@ -6,7 +6,6 @@ use App\Application\Regulation\Command\Location\SaveNumberedRoadCommand; use App\Domain\Regulation\Enum\DirectionEnum; -use App\Domain\Regulation\Enum\RoadSideEnum; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\HiddenType; @@ -43,11 +42,6 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'help' => 'regulation.location.referencePoint.pointNumber.help', ], ) - ->add( - 'fromSide', - ChoiceType::class, - options: $this->getRoadSideOptions(), - ) ->add( 'toPointNumber', TextType::class, @@ -56,11 +50,6 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'help' => 'regulation.location.referencePoint.pointNumber.help', ], ) - ->add( - 'toSide', - ChoiceType::class, - options: $this->getRoadSideOptions(), - ) ->add( 'fromAbscissa', IntegerType::class, @@ -92,23 +81,6 @@ public function buildForm(FormBuilderInterface $builder, array $options): void }); } - private function getRoadSideOptions(): array - { - $choices = []; - - foreach (RoadSideEnum::cases() as $case) { - $choices[\sprintf('regulation.location.road.side.%s', $case->value)] = $case->value; - } - - return [ - 'choices' => array_merge( - $choices, - ), - 'label' => 'regulation.location.road.side', - 'help' => 'regulation.location.road.side.help', - ]; - } - private function getAdministratorOptions(array $administrators, string $roadType): array { $choices = []; diff --git a/templates/regulation/fragments/_measure_form.html.twig b/templates/regulation/fragments/_measure_form.html.twig index 394ef6d06..2c7e32ffd 100644 --- a/templates/regulation/fragments/_measure_form.html.twig +++ b/templates/regulation/fragments/_measure_form.html.twig @@ -238,7 +238,6 @@
{{ form_row(form[roadType].fromPointNumber, { row_attr: {class:'fr-col-12 fr-col-md-4'}, attr: {class: 'fr-input'}}) }} - {{ form_row(form[roadType].fromSide, { row_attr: {class:'fr-col-12 fr-col-md-4'}, attr: {class: 'fr-select'}}) }} {{ form_row(form[roadType].fromAbscissa, { row_attr: {class:'fr-col-12 fr-col-md-4'}, attr: {class: 'fr-input'}}) }}
@@ -251,7 +250,6 @@
{{ form_row(form[roadType].toPointNumber, { row_attr: {class:'fr-col-12 fr-col-md-4'}, attr: {class: 'fr-input'}}) }} - {{ form_row(form[roadType].toSide, { row_attr: {class:'fr-col-12 fr-col-md-4'}, attr: {class: 'fr-select'}}) }} {{ form_row(form[roadType].toAbscissa, { row_attr: {class:'fr-col-12 fr-col-md-4'}, attr: {class: 'fr-input'}}) }}
diff --git a/tests/Unit/Application/Regulation/Command/Location/SaveNumberedRoadCommandHandlerTest.php b/tests/Unit/Application/Regulation/Command/Location/SaveNumberedRoadCommandHandlerTest.php index 96bbc13b4..79acf23a4 100644 --- a/tests/Unit/Application/Regulation/Command/Location/SaveNumberedRoadCommandHandlerTest.php +++ b/tests/Unit/Application/Regulation/Command/Location/SaveNumberedRoadCommandHandlerTest.php @@ -5,6 +5,7 @@ namespace App\Tests\Unit\Application\Regulation\Command\Location; use App\Application\IdFactoryInterface; +use App\Application\PointNumberSideDetectorInterface; use App\Application\Regulation\Command\Location\SaveNumberedRoadCommand; use App\Application\Regulation\Command\Location\SaveNumberedRoadCommandHandler; use App\Domain\Geography\Coordinates; @@ -31,11 +32,13 @@ final class SaveNumberedRoadCommandHandlerTest extends TestCase private MockObject $idFactory; private MockObject $numberedRoadRepository; + private $pointNumberSideDetector; public function setUp(): void { $this->idFactory = $this->createMock(IdFactoryInterface::class); $this->numberedRoadRepository = $this->createMock(NumberedRoadRepositoryInterface::class); + $this->pointNumberSideDetector = $this->createMock(PointNumberSideDetectorInterface::class); $this->administrator = 'Département de Loire-Atlantique'; $this->roadNumber = 'D12'; @@ -90,9 +93,24 @@ public function testCreate(): void ) ->willReturn($createdNumberedRoad); + $this->pointNumberSideDetector + ->expects(self::once()) + ->method('detect') + ->with( + $this->direction, + $this->administrator, + $this->roadNumber, + $this->fromPointNumber, + $this->fromAbscissa, + $this->toPointNumber, + $this->toAbscissa, + ) + ->willReturn([$this->fromSide, $this->toSide]); + $handler = new SaveNumberedRoadCommandHandler( $this->idFactory, $this->numberedRoadRepository, + $this->pointNumberSideDetector, ); $command = new SaveNumberedRoadCommand(); @@ -101,10 +119,8 @@ public function testCreate(): void $command->administrator = $this->administrator; $command->roadNumber = $this->roadNumber; $command->fromPointNumber = $this->fromPointNumber; - $command->fromSide = $this->fromSide; $command->fromAbscissa = $this->fromAbscissa; $command->toPointNumber = $this->toPointNumber; - $command->toSide = $this->toSide; $command->toAbscissa = $this->toAbscissa; $result = $handler($command); @@ -138,9 +154,24 @@ public function testUpdate(): void ->expects(self::never()) ->method('add'); + $this->pointNumberSideDetector + ->expects(self::once()) + ->method('detect') + ->with( + $this->direction, + $this->administrator, + $this->roadNumber, + $this->fromPointNumber, + $this->fromAbscissa, + $this->toPointNumber, + $this->toAbscissa, + ) + ->willReturn([$this->fromSide, $this->toSide]); + $handler = new SaveNumberedRoadCommandHandler( $this->idFactory, $this->numberedRoadRepository, + $this->pointNumberSideDetector, ); $command = new SaveNumberedRoadCommand($numberedRoad); diff --git a/translations/messages.fr.xlf b/translations/messages.fr.xlf index 868e7be03..4115511c1 100644 --- a/translations/messages.fr.xlf +++ b/translations/messages.fr.xlf @@ -553,14 +553,6 @@ regulation.location.referencePoint.to Point de fin - - regulation.location.road.side - Côté - - - regulation.location.road.side.help - Côté de la route où se situe le PR - regulation.location.referencePoint Section concernée @@ -569,18 +561,6 @@ regulation.location.referencePoint.help Indiquez la section de route concernée par la mesure. La restriction s'applique à toutes les voies de la chaussée - - regulation.location.road.side.D - Chaussée droite (D) - - - regulation.location.road.side.G - Chaussée gauche (G) - - - regulation.location.road.side.U - Chaussée unique (U) - regulation.location.referencePoint.pointNumber PR