pretraitement_dory/script_pre_traitement_dory.py
David Castex 63c7317e86 Ajout fonctionnalité et modif README
- Insertion de la date du jour dans le visa lors de sa création
 - Mode d'emploi simplifié
2025-03-10 16:32:47 +01:00

493 lines
19 KiB
Python

# ~~ Script DORY Digitanie ~~
# Voir algorithme.txt pour plus de détails et commentaires
# ----- INSTALLATION DE PIP ET CONTROLE MODULES -----
# Repris depuis script Banbou
import subprocess
def check_and_update_pip():
# Vérifier s'il existe une version plus récente de pip
try:
result = subprocess.run(['pip', 'install', '--upgrade', 'pip', '--disable-pip-version-check'], capture_output=True, text=True)
if result.returncode == 0:
print("pip a été mis à jour avec succès.") #TODO bizarre, c'est affiché alors qu'il n'y avait pas d'internet
except subprocess.CalledProcessError:
print("La mise à jour de pip a échoué.")
# Lancer le code deux fois
for _ in range(2):
check_and_update_pip()
# Liste des bibliothèques à vérifier et installer
bibliotheques = ['csv', 'os', 're', 'shutil', 'sys', 'openpyxl']
for bibliotheque in bibliotheques:
try:
__import__(bibliotheque)
except ImportError:
# Installer la bibliothèque
print(f"{bibliotheque} n'est pas installée. Installation en cours...")
subprocess.check_call(['pip', 'install', bibliotheque])
print(f"{bibliotheque} a été installée avec succès.")
import os # getcwd(), walk()
import openpyxl # gestion et manipulation de fichier Open XML
import re # RegEx
from shutil import copyfile # copie de fichiers
from datetime import datetime # obj date du jour
class _Fichier:
def __init__(self,
nom_original="Pas de nom original",
chemin="Pas de chemin",
extension="Pas d'extension",
nom= "Pas de nom",
implication="Non-conforme"):
self.nom_original = nom_original # - son nom original - string
self.chemin = chemin # - son chemin - string
self.extension = extension # - son extension - string
self.nom = nom # - son nom formaté - string
self.implication = implication # - son implication - enum
# {"Nécessaire",
# "Auxiliaire",
# "Non-conforme",
# "A-ignorer"}
def afficher(self):
"""Affiche dans la sortie standard les éléments du fichier
"""
print("nom : ".center(16), self.nom)
print("nom orig : ".center(16), self.nom_original)
print("ext : ".center(16), self.extension)
print("chemin : ".center(16), self.chemin)
print("implication :".center(16), self.implication)
def impliquer(self):
"""Définir l'implication d'un fichier
Les fichiers nécessaires sont indispensables sinon l'importation
de la couche échoue.
Les fichiers optionels (ou auxiliaires) contiennent des données
supplémentaires lors de l'importation.
Les fichiers non conformes pourraient être importer dans QGIS. Mais,
ne sont pas conformes au cahier des charges.
Les fichiers ignorés ne fonctionnent pas dans QGIS.
L'implication est définie en fonction de l'extension du fichier.
"""
match self.extension:
case "dbf" | "cpg" | "prj" | "shx" | "shp":
self.implication = "Necessaire"
case "qix" | "qmd" | "qml":
self.implication = "Auxiliaire"
case "gpkg" | "sqlite":
self.implication = "Non-conforme"
case _:
self.implication = "A-ignorer"
# TODO : gerer la casse des extensions pour que le controle se fasse
class _Projet:
def __init__(self,
nom="Pas de nom",
necessaires = {},
auxiliaires = {},
non_conformes = {},
nb_fichiers=0,
conformite="Non conforme",
projection="Inconnu",
encodage="Inconnu"):
self.nom = nom # - son nom - string
self.necessaires = necessaires # - son dictionnaire de fichiers nécessaires avec pour clé l'extention
# de chaque fichier - dico de paire string : _Fichier
self.auxiliaires = auxiliaires # - son dictionnaire de fichiers nécessaires avec pour clé l'extention
# de chaque fichier - dico de paire string : _Fichier
self.non_conformes = non_conformes # - son dictionnaire de fichiers nécessaires avec pour clé l'extention
# de chaque fichier - dico de paire string : _Fichier
self.nb_fichiers = nb_fichiers # - son nombre de fichiers - entier
self.conformite = conformite # - sa conformité - string
self.projection = projection # - sa projection géodésique - string // ou énumération --> voir Constantes
self.encodage = encodage # - son type d'encodage prévu dans QGIS string
# /* pour ce script, ce devrait être forcement "UTF8" */
def afficher(self):
"""Affiche dans la sortie standard le contenu d'un projet
"""
print("nom : ".center(16), self.nom)
print("nb fichiers :".center(16), self.nb_fichiers)
print("conformité :".center(16), self.conformite)
print("projection :".center(16), self.projection)
print("encodage :".center(16), self.encodage)
print("necessaires :".center(16), self.necessaires)
print("auxiliaires :".center(16), self.auxiliaires)
print("non_conformes :".center(16), self.non_conformes)
def cataloguer(self, fichier):
"""Catalogue le fichier dans le projet
La méthode ajoute le fichier suivant son extension
dans un des dictionnaires du projet
"""
match fichier.implication:
case "Necessaire":
self.necessaires[fichier.extension] = fichier
case "Auxiliaire":
self.auxiliaires[fichier.extension] = fichier
case "Non-conforme":
self.non_conformes[fichier.extension] = fichier
def lire_projection(self):
"""Récupère la projection des données géomatiques définie.
Lis la première ligne du fichier *.PRJ du projet
et controle la présence de la chaine "Lambert_93"
"""
if "prj" in self.necessaires:
df = open(self.necessaires["prj"].chemin)
chaine = df.readline()
df.close()
#cherche le motif : "Ligne qui commence par PROJCS["
# puis suivi de 0 ou plusieurs caracteres
#puis suivi de Lambert_93"
motif = re.findall('^PROJCS[[]".*Lambert_93"', chaine)
print("motif = ", motif)
if motif:
self.projection = "LAMBERT93"
else:
print(projet.nom, " : pas de PRJ trouvé --> pas de projection définie")
def lire_encodage(self):
"""Récupère l'encodage prévu pour les fichiers dans QGIS
Lis la première ligne du fichier *.CPG du projet
et controle la présence de la chaine "UTF-8"
"""
if "cpg" in self.necessaires:
df = open(self.necessaires["cpg"].chemin)
chaine = df.readline()
df.close()
if "UTF-8" in chaine:
self.encodage = "UTF8"
else:
print(projet.nom, " : pas de CPG trouvé --> pas d'encodage définie")
def valider(self):
"""Valide la conformité du projet
Controle si les 5 fichiers differents nécessaires sont présents.
Controle si la projection est correcte (pour le moment Lambert93).
Controle si l'encodage défini pour l'import dans QGIS est bien UTF8.
Si ces 3 controles sont OK, alors le projet est défini comme valide.
"""
nb_controles_OK = 0
# Controle présences fichiers
valide = [False, False, False, False, False]
for extension in self.necessaires.keys():
match extension:
case "dbf":
valide[0] = True
case "cpg":
valide[1] = True
case "prj" :
valide[2] = True
case "shx" :
valide[3] = True
case "shp" :
valide[4] = True
if valide[0] and valide[1] and valide[2] and valide[3] and valide[4]:
nb_controles_OK += 1
# Controle projection
if self.projection == "LAMBERT93":
nb_controles_OK += 1
# Controle encodage
if self.encodage == "UTF8":
nb_controles_OK += 1
# Si les 3 controles sont OK ALors le projet est conforme
if nb_controles_OK == 3:
self.conformite = "Conforme"
def copier(self):
"""Copie les fichiers du projets
"""
pass
# TODO :
def formatter(chaine):
"""Formate selon nomenclature.
Formate la chaine de caractères passée en paramètre :
Enlève tous les accents français. Enlève la cédille du C.
Remplace les espaces ' ' et les traits d'union '-' par
des tirets bas '_'.
Ne traite pas pour le moment le AE et OE ligaturé.
"""
#TODO : gérer les accents sur majuscules
#TODO : amélioration ou exercice, utiliser la méthode str.translate() et maketrans
resultat = ""
for c in chaine:
match c:
case "à" | "â" | "ä":
resultat+= "a"
case "é" | "è" | "ê" | "ë":
resultat+= "e"
case "î" | "ï":
resultat+= "i"
case "ô" | "ö":
resultat+= "o"
case "ù" | "û" | "ü":
resultat+= "u"
case "ÿ":
resultat+= "y"
case "ç":
resultat+= "c"
case " " | "-":
resultat+= "_"
case _:
resultat+= c
return resultat
# DEBUT DU PROGRAMME
liste = [] # - une liste des fichiers list _Fichier
dico = {} # - un dictionnaire des projets avec pour clé le nom de chaque projet
# dico de paire string : Entité Projet
racine = os.getcwd() # - un chemin absolue vers le repertoire racine à traiter string
print("Racine : ".center(18), racine)
date = datetime.today().strftime("%d/%m/%Y") # objet contenant la date du jour
# --recupérer le chemin du dossier à traiter
# Si il y a PAS un dossier de traitement dans le working directory Alors
pas_de_dossier = True
for a in os.scandir():
#si 'a' est un dossier et n'est pas un dossier de la liste ci dessous
if a.is_dir() and a.name not in ["Travail", "Livraison", ".git"]:
#il y a un dossier
pas_de_dossier = False
print(f"Dossier à Traiter trouvé : {a.name}")
racine = a
if pas_de_dossier:
print("Pas de dossier trouvé...\nFin de programme\n")
# fin du programme
exit()
# --analyser le dossier
# initialiser une liste de _Fichiers
liste = []
# Pour Chaque fichier des dossiers et sous dossiers à explorer Faire
for dossier_courant, list_sousdossiers, list_fichiers in os.walk(racine): ##TODO voir la fonction fwalk qui renvoie un 4-tuple(dirpath, dirnames, filenames, dirfd), and it supports dir_fd
for fichier_courant in list_fichiers:
# --lire, analyser chaque fichier et peupler la liste
# initialiser un _Fichier
ce_Fichier = _Fichier()
# lire son chemin
ce_Fichier.chemin = dossier_courant + "\\" + fichier_courant
# déterminer son nom original et son extension
# lire le nom original
# lire son extension
ce_Fichier.nom_original, ce_Fichier.extension = fichier_courant.split(".")
# formatter et écrire le nom
ce_Fichier.nom = formatter(ce_Fichier.nom_original)
# déterminer son implication
ce_Fichier.impliquer()
# ajouter le _Fichier à la liste
liste.append(ce_Fichier)
# Fin Pour
# /* Ici la liste des _Fichiers doit être correctement remplie */
# initialiser un dictionnaire de _Projets avec comme clé leurs noms respectifs
print("Init Dictionnaire des Projets\n")
dico = {}
# Pour Chaque fichier de la liste Faire
for fichier_courant in liste:
if fichier_courant.implication not in "A-ignorer":
# --associer le fichier courant à un projet du dictionnaire
# /* Vérifier que le projet existe déjà dans le dico */
# Si le nom du fichier courant n'est pas dans le dico Alors
if fichier_courant.nom not in dico:
# initialiser un _Projet
projet = _Projet()
projet.necessaires = {}
projet.auxiliaires = {}
projet.non_conformes = {}
projet.nom = fichier_courant.nom
print("création clé : ", projet.nom)
# ajouter le nom comme clé dans le dictionnaire
# associer le projet à cette clé
dico[projet.nom] = projet
# Fin Si
# cataloguer le fichier dans le projet concerné
projet.cataloguer(fichier_courant)
# il y a un fichier de plus au projet
projet.nb_fichiers += 1
else:
# sinon le fichier est a ignorer
print("Ignoré : ".center(16), fichier_courant.nom_original)
# Fin Pour
# Pour Chaque projet du dictionnaire Faire
for projet in dico.values():
# --analyser et completer les infos du projet courant
# /* conformité, projection, encodage etc */
# lire la projection du projet courant
projet.lire_projection()
# lire l'encodage du projet courant
projet.lire_encodage()
# Si tous les fichiers nécessaires sont présents ET
# la projection est correcte ET
# l'encodage est correct Alors
# passer le projet à conforme
projet.valider()
# Fin Si
# Fin Pour
# /* Ici le dictionnaire des _Projets doit être correctement remplie */
# --produire le rapport
# créer un classeur Excel
# TODO pour le moment je vais partir sur un modele deja ecrit que j'ouvre
# Contrainte : l'opérateur doit placer le modèle dans le même dossier que le script
classeur = openpyxl.load_workbook("modele_VISA_DORY.xlsx")
feuille = classeur.active
cell = feuille["A2"]
cell.value = "Dossier : "+ racine.name
# Pour chaque projet du dictionnaire Faire
ligne_courante = 4
for nom_projet, projet in dico.items():
# --completer la feuille avec les infos du projet courant
# /* Note : Chaque ligne de la feuille correspond à un projet */
# écrire le nom du projet
cell = feuille["A"+str(ligne_courante)]
cell.value = nom_projet
#print(cell.value)
# écrire le type d'extention
cell = feuille["B"+str(ligne_courante)]
if "shp" in projet.necessaires:
# /* Note : ce sera "SHP" pour les projets conformes et le type
# natif pour les non-conformes */
cell.value = "SHP"
else:
for extension in projet.non_conformes:
cell.value = extension.upper()
print(cell.value)
# écrire le nombre de fichiers du projet
cell = feuille["C"+str(ligne_courante)]
cell.value = projet.nb_fichiers
# écrire l'encodage
cell = feuille["D"+str(ligne_courante)]
cell.value = projet.encodage
# écrire la projection
cell = feuille["E"+str(ligne_courante)]
cell.value = projet.projection
# écrire la validité (conformité)
cell = feuille["F"+str(ligne_courante)]
if projet.conformite in "Conforme":
cell.value = "OK"
else:
cell.value = "FAIL"
ligne_courante+= 1
# encadrer le "tout" pour délimiter le tableau de la feuille
# Fin Pour
cell = feuille["B2"]
cell.value = "Créé : " + date
# /* Ici le rapport doit être correctement produit et remplie */
# --produire le dossier de mise à disposition
# créer un dossier "Travail" et un dossier "Livraison"
try:
os.mkdir("Travail")
except FileExistsError as erreur:
print(erreur)
#os.mkdir("Livraison")
# --créer l'arborescence du dossier "Travail"
# le dossier Travail devient le working directory
# créer un dossier "00-Cadastre"
# On va plutot sauvegarder le classeur dans "Travail" si le dossier existe
classeur.save("Travail\\VISA_DORY_PRE.xlsx")
#print("ligne_courante = ", ligne_courante )
try:
os.mkdir("Travail\\00-Cadastre")
except FileExistsError as erreur:
print(erreur)
# créer un dossier "01-Donnees_Valides"
try:
os.mkdir("Travail\\01-Donnees_Valides")
except FileExistsError as erreur:
print(erreur)
# créer un dossier "02-Donnees_Invalides
try:
os.mkdir("Travail\\02-Donnees_Invalides")
except FileExistsError as erreur:
print(erreur)
# créer un dossier "03-Production"
try:
os.mkdir("Travail\\03-Production")
except FileExistsError as erreur:
print(erreur)
# créer un dossier "04-Verif_Geometrie"
try:
os.mkdir("Travail\\04-Verif_Geometrie")
except FileExistsError as erreur:
print(erreur)
# --peupler le dossier "Travail"
# Pour Chaque projet du dictionnaire Faire
dossier_cible = "Travail"
for projet in dico.values():
# copier les fichiers des projets conforme dans le dossier "01"
if projet.conformite in "Conforme":
for fichier in projet.necessaires.values():
#print("chemin : ".center(30), fichier.chemin)
#print("nom : ".center(30), fichier.nom + "." + fichier.extension)
copyfile(fichier.chemin, "Travail\\01-Donnees_Valides\\"+fichier.nom+"."+fichier.extension)
for fichier in projet.auxiliaires.values():
copyfile(fichier.chemin, "Travail\\01-Donnees_Valides\\"+fichier.nom+"."+fichier.extension)
else:
for fichier in projet.necessaires.values():
copyfile(fichier.chemin, "Travail\\02-Donnees_Invalides\\"+fichier.nom+"."+fichier.extension)
for fichier in projet.auxiliaires.values():
copyfile(fichier.chemin, "Travail\\02-Donnees_Invalides\\"+fichier.nom+"."+fichier.extension)
for fichier in projet.non_conformes.values():
copyfile(fichier.chemin, "Travail\\02-Donnees_Invalides\\"+fichier.nom+"."+fichier.extension)
# Fin Pour
# /* Ici les dossiers doivent être correctement produit et remplie */
# ARRET DU PROGRAMME