Skip to content

Commit

Permalink
Ajoute et execute l'outil d'import des aires de stockage
Browse files Browse the repository at this point in the history
  • Loading branch information
florimondmanca committed Jan 28, 2025
1 parent 927be14 commit 14e26e3
Show file tree
Hide file tree
Showing 11 changed files with 524 additions and 0 deletions.
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.PHONY: assets
.SILENT: data/aires_de_stockage.php

# Allow non-Docker overrides for CI.
BIN_SHELL = docker compose exec php
Expand Down Expand Up @@ -144,6 +145,11 @@ data_bac_idf_import: # Import BAC-IDF decrees as regulation orders
data/bac_idf/decrees.json: ## Create BAC-IDF decrees file
./tools/bacidfinstall

data/aires_de_stockage.php:
make --silent console CMD="app:storage_area:generate data/aires_de_stockage.csv" > data/aires_de_stockage.php
make console CMD="doctrine:migrations:generate -n"
@echo "Ajoutez le contenu du fichier PHP data/aires_de_stockage.php ci-dessus dans le up() de la nouvelle migration"

##
## ----------------
## Executable
Expand Down
1 change: 1 addition & 0 deletions data/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
communes.json
aires_de_stockage.php
22 changes: 22 additions & 0 deletions docs/tools/storage_areas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Aires de stockage

Pour les arrêtés de [viabilité hivernale](https://github.com/MTES-MCT/dialog/issues/990), DiaLog a besoin des données sur l'emplacement des aires de stockage où les poids lourds doivent attendre que la barrière de dégel soit levée.

L'application DiaLog récupère ces données dans la table `storage_area`.

Cette table est remplie avec des données issues d'un fichier CSV fourni par la DGITM.

## Chargement des données

Il n'y a rien à faire, les données sont présentes dans une migration et sont donc importées dans `storage_area` lors d'un `make install`.

## Mise à jour des données

S'il y a lieu de mettre à jour la base d'aires de stockage, alors :

1. Remplacez `data/aires_de_stockage.csv` par le nouveau fichier CSV
2. Lancez `make -B data/aires_de_stockage.php`. Cela génère un fichier `data/aires_de_stockage.php` et une migration vide.
3. Copiez-collez la ligne de PHP présente dans `data/aires_de_stockage.php` dans le `up()` de la nouvelle migration.
> Le SQL généré fait des upserts (`INSERT ... ON CONFLICT (source_id) DO UPDATE ...`) pour préserver les associations entre les `Location` et les `StorageArea` existants.
4. Faites une PR avec la nouvelle migration.

36 changes: 36 additions & 0 deletions src/Infrastructure/Adapter/CsvParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace App\Infrastructure\Adapter;

class CsvParser
{
public function parseAssociative(string $csv): array
{
$csv = trim($csv);

if (!$csv) {
return [];
}

$rows = [];

$lines = array_map(fn ($t) => trim($t), explode(PHP_EOL, $csv));

$fieldNames = str_getcsv($lines[0], ',');

foreach (\array_slice($lines, 1) as $line) {
$values = str_getcsv($line, ',');
$row = [];

foreach ($fieldNames as $index => $name) {
$row[$name] = $values[$index];
}

$rows[] = $row;
}

return $rows;
}
}
157 changes: 157 additions & 0 deletions src/Infrastructure/Data/StorageArea/StorageAreaMigrationGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<?php

declare(strict_types=1);

namespace App\Infrastructure\Data\StorageArea;

use App\Application\Exception\GeocodingFailureException;
use App\Application\RoadGeocoderInterface;
use App\Application\RoadSectionMakerInterface;
use App\Domain\Regulation\Enum\DirectionEnum;
use Doctrine\DBAL\Connection;

class StorageAreaMigrationGenerator
{
public function __construct(
private readonly Connection $bdtopoConnection,
private RoadGeocoderInterface $roadGeocoder,
private readonly RoadSectionMakerInterface $roadSectionMaker,
) {
}

private function findAdministrator(string $roadNumber): string
{
// La colonne 'code_gestionnaire' n'est pas standard, il n'y a pas d'équivalent dans la BDTOPO.
// Tous les gestionnaires dans le CSV sont des DIR. Et dans la BDTOPO, il n'existe qu'un gestionnaire DIR par nationale.
// On retrouve donc le gestionnaire BDTOPO à partir du numéro de nationale.

$row = $this->bdtopoConnection->fetchAssociative(
'SELECT gestionnaire FROM route_numerotee_ou_nommee WHERE numero = :numero AND gestionnaire LIKE \'DIR%\'',
['numero' => $roadNumber],
);

return $row['gestionnaire'];
}

private function parseRoadNumber(string $value): string
{
// Examples:
// N0001 -> N1
// N0109 -> N109

if (!preg_match('/^N0*(?P<number>\d+)$/', $value, $matches)) {
throw new \RuntimeException(\sprintf('Unexpected id_route: bad format: "%s"', $value));
}

return \sprintf('N%s', $matches['number']);
}

private function parsePointNumberAndSide(string $value): array
{
// Examples:
// 61PR2D -> ['2', 'D']
// 10PR24U -> ['24', 'U']

if (!preg_match('/\d+PR(?P<number>\d+)(?P<side>[DGU])$/', $value, $matches)) {
throw new \RuntimeException(\sprintf('Unexpected nom_plo: bad format: "%s"', $value));
}

return [$matches['number'], $matches['side']];
}

public function makeMigrationSql(array $rows): string
{
if (empty($rows)) {
return '';
}

$valuesList = [];

foreach ($rows as $row) {
if (!str_starts_with($row['id_route'], 'N')) {
continue;
}

// Parfois 'id_route_fin', 'nom_plo_fin' et 'abscisse_fin' ne sont pas remplis, mais on a la 'longueur'.
// On utilise le même PR/côté et on utilise abscisse + longueur comme abscisse de fin.
if (!$row['nom_plo_fin'] || !$row['abscisse_fin']) {
$row['nom_plo_fin'] = $row['nom_plo'];
$row['abscisse_fin'] = (string) ((int) $row['abscisse'] + (int) $row['longueur']);
}

$sourceId = $row['id2'];
$description = $row['description_infobulle'];
$roadNumber = $this->parseRoadNumber($row['id_route']);
$administrator = $this->findAdministrator($roadNumber);
[$fromPointNumber, $fromSide] = $this->parsePointNumberAndSide($row['nom_plo']);
$fromAbscissa = (int) $row['abscisse'];
[$toPointNumber, $toSide] = $this->parsePointNumberAndSide($row['nom_plo_fin']);
$toAbscissa = (int) $row['abscisse_fin'];

$fullRoadGeometry = $this->roadGeocoder->computeRoad('Nationale', $administrator, $roadNumber);

try {
$geometry = $this->roadSectionMaker->computeSection(
$fullRoadGeometry,
'Nationale',
$administrator,
$roadNumber,
$fromPointNumber,
$fromSide,
$fromAbscissa,
$toPointNumber,
$toSide,
$toAbscissa,
DirectionEnum::BOTH->value,
);
} catch (GeocodingFailureException $exc) {
// TODO
continue;
}

$values = [
'uuid' => 'uuid_generate_v4()',
'source_id' => \sprintf("'%s'", $sourceId),
'description' => \sprintf("'%s'", $description),
'administrator' => \sprintf("'%s'", $administrator),
'road_number' => \sprintf("'%s'", $roadNumber),
'from_point_number' => \sprintf("'%s'", $fromPointNumber),
'from_side' => \sprintf("'%s'", $fromSide),
'from_abscissa' => $fromAbscissa,
'to_point_number' => \sprintf("'%s'", $toPointNumber),
'to_side' => \sprintf("'%s'", $toSide),
'to_abscissa' => $toAbscissa,
'geometry' => \sprintf("ST_GeomFromGeoJSON('%s')", $geometry),
];

$valuesList[] = \sprintf('(%s)', implode(', ', $values));
}

$columns = [
'uuid',
'source_id',
'description',
'administrator',
'road_number',
'from_point_number',
'from_side',
'from_abscissa',
'to_point_number',
'to_side',
'to_abscissa',
'geometry',
];

$updateColumns = array_filter($columns, fn ($col) => !\in_array($col, ['uuid', 'source_id']));

return \sprintf(
'INSERT INTO storage_area (%s) VALUES
%s
ON CONFLICT (source_id) DO UPDATE
SET %s;',
implode(', ', $columns),
implode(\sprintf(',%s', PHP_EOL), $valuesList),
implode(', ', array_map(fn ($col) => \sprintf('%s = EXCLUDED.%s', $col, $col), $updateColumns)),
);
}
}
Loading

0 comments on commit 14e26e3

Please sign in to comment.