Prends en compte l'intersection des valeurs dans les projections CC47 et Lambert93
569 lines
20 KiB
Python
569 lines
20 KiB
Python
## Script Banbou pour le prétraitement des données des dossiers de recollement
|
|
## de fibre optique.
|
|
|
|
import os, shutil, datetime
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# DEFINITIONS DE FONCTIONS
|
|
|
|
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é.
|
|
Si plusieurs '_' se suivent, les réduire à un seul.
|
|
"""
|
|
#TODO : gérer les accents sur majuscules
|
|
#TODO : amélioration ou exercice, utiliser la méthode str.translate() et maketrans
|
|
resultat = ""
|
|
precedent = None
|
|
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 " " | "-" | "_":
|
|
if('_' not in precedent ):
|
|
resultat+= "_"
|
|
c = "_"
|
|
case _:
|
|
resultat+= c
|
|
precedent = c
|
|
return resultat
|
|
|
|
|
|
|
|
def creer_liste(dossier):
|
|
"""
|
|
Construit une liste avec les fichiers de 'dossier'
|
|
|
|
Parcourt le dossier et ses sous-dossier, ajoute TOUS les fichiers
|
|
dans une liste de _Fichier.
|
|
Chaque _Fichier ajouté a ses attributs mis a jour.
|
|
Retourne la liste.
|
|
:param user_input: nom complet du dossier
|
|
:return: liste d'élements de type _Fichier
|
|
"""
|
|
liste = []
|
|
|
|
for dossier_courant, list_sousdossiers, list_fichiers in os.walk(dossier):
|
|
for fichier_courant in list_fichiers:
|
|
print(f"\nfichier courant : {fichier_courant}")
|
|
ce_Fichier = _Fichier.lire(dossier_courant, fichier_courant)
|
|
liste.append(ce_Fichier)
|
|
|
|
return liste
|
|
|
|
|
|
# TODO surcharger la fonction print native pour cet affichage
|
|
def afficher(liste):
|
|
"""
|
|
Affiche le nom des fichiers de la liste des _Fichiers
|
|
"""
|
|
for courant in liste:
|
|
print("\n" + courant.nom + "." + courant.extension)
|
|
|
|
|
|
|
|
|
|
|
|
def controller_projection(id_point, point_x, point_y):
|
|
"""
|
|
regarde si les coordonnées d'un point sont bien dans une des projections
|
|
autorisées
|
|
|
|
Contrainte :
|
|
- Pour le moment, ne peut reconnaitre que des projections
|
|
de points situés en France métropolitaine (Lambert93 et les 9 zones CC).
|
|
- Ce sont toutes des projections coniques depuis le pole nord --> la longitude (axe Ouest Est) n'est pas déformé par ces projections et donc reste valide.
|
|
Mais je vais aussi contraindre la longitude à la France métropolitaine, pour pas qu'un point situé en Russie par exemple apparaisse comme valide.
|
|
|
|
param user_input: un point de type (int, float, float) TODO: avoir si conversion nécessaire ou faire en chaine de carac
|
|
return: Une chaine nommant la projection trouvé sinon "Mauvaise projection"
|
|
"""
|
|
# Valeurs constantes définie par IGN
|
|
projections_conformes = [
|
|
{
|
|
"nom" : "GPS",
|
|
"E0" : 0,
|
|
"N0" : 0,
|
|
"fenetreE0" : 180,
|
|
"fenetreN0" : 90,
|
|
"EPSG" : 4326 # Correspond à la projection des GPS
|
|
},
|
|
{
|
|
"nom" : "Lambert93",
|
|
"E0" : 700000,
|
|
"N0" : 6600000,
|
|
"fenetreE0" : 600000,
|
|
"fenetreN0" : 600000,
|
|
"EPSG" : 2154 # Attention la plus recente est 9794
|
|
},
|
|
{
|
|
"nom" : "CC42",
|
|
"E0" : 1700000,
|
|
"N0" : 1200000,
|
|
"fenetreE0" : 600000,
|
|
"fenetreN0" : 111000,
|
|
"EPSG" : 3942
|
|
},
|
|
{
|
|
"nom" : "CC43",
|
|
"E0" : 1700000,
|
|
"N0" : 2200000,
|
|
"fenetreE0" : 600000,
|
|
"fenetreN0" : 111000,
|
|
"EPSG" : 3943
|
|
},
|
|
{
|
|
"nom" : "CC44",
|
|
"E0" : 1700000,
|
|
"N0" : 3200000,
|
|
"fenetreE0" : 600000,
|
|
"fenetreN0" : 111000,
|
|
"EPSG" : 3944
|
|
},
|
|
{
|
|
"nom" : "CC45",
|
|
"E0" : 1700000,
|
|
"N0" : 4200000,
|
|
"fenetreE0" : 600000,
|
|
"fenetreN0" : 111000,
|
|
"EPSG" : 3945
|
|
},
|
|
{
|
|
"nom" : "CC46",
|
|
"E0" : 1700000,
|
|
"N0" : 5200000,
|
|
"fenetreE0" : 600000,
|
|
"fenetreN0" : 111000,
|
|
"EPSG" : 3946
|
|
},
|
|
{
|
|
"nom" : "CC47",
|
|
"E0" : 1700000,
|
|
"N0" : 6200000,
|
|
"fenetreE0" : 600000,
|
|
"fenetreN0" : 111000,
|
|
"EPSG" : 3947
|
|
},
|
|
{
|
|
"nom" : "CC48",
|
|
"E0" : 1700000,
|
|
"N0" : 7200000,
|
|
"fenetreE0" : 600000,
|
|
"fenetreN0" : 111000,
|
|
"EPSG" : 3948
|
|
},
|
|
{
|
|
"nom" : "CC49",
|
|
"E0" : 1700000,
|
|
"N0" : 8200000,
|
|
"fenetreE0" : 600000,
|
|
"fenetreN0" : 111000,
|
|
"EPSG" : 3949
|
|
},
|
|
{
|
|
"nom" : "CC50",
|
|
"E0" : 1700000,
|
|
"N0" : 9200000,
|
|
"fenetreE0" : 600000,
|
|
"fenetreN0" : 111000,
|
|
"EPSG" : 3950
|
|
}
|
|
# TODO : ajouter les projections DOMTOMs
|
|
]
|
|
|
|
# projection à retourner
|
|
projection = "Mauvaise projection"
|
|
|
|
# NOTE : Une partie des ensembles de coordonnées de CC47 et de Lambert93 s'intersectionne :
|
|
# Lorsqu'un point de coordonnées (X, Y) se situe dans la plage 1100000, 1300000 pour X et
|
|
# la plage 3089000, 3311000 pour Y, alors ce point est à la fois valide en CC47 et en Lambert93
|
|
# Avant de déterminer la projection, je vais donc vérifier si le point à controler n'est pas dans cette plage
|
|
# Et notifier si necessaire
|
|
# TODO : exporter la notif
|
|
if 6089000 < point_y < 6311000:
|
|
if 1100000 < point_x < 1300000:
|
|
print("\nAvertissement : ces coordonnées peuvent être interpréter correctement comme du CC47 et du Lambert93")
|
|
print("Normalement vous devrez projeter la Table de Point en CC47")
|
|
|
|
|
|
# définie la projection conique Nord en regardant dans quelle intervalle la valeur se situe
|
|
for elem in projections_conformes:
|
|
borne_basse = elem["N0"] - elem["fenetreN0"]
|
|
borne_haute = elem["N0"] + elem["fenetreN0"]
|
|
if borne_basse < point_y < borne_haute :
|
|
projection = elem["nom"]
|
|
|
|
print(f"\nProj. trouvé : {projection}")
|
|
|
|
# Controle la longitude
|
|
# TODO: refaire cette partie avec des variables et non des entiers literraux
|
|
longitude_correcte = False
|
|
match projection:
|
|
case "Lambert93":
|
|
if 100000 < point_x < 1300000 :
|
|
longitude_correcte = True
|
|
case "CC42" | "CC43" | "CC44" | "CC45" | "CC46" | "CC47" | "CC48" | "CC49" | "CC50":
|
|
if 1100000 < point_x < 2300000 :
|
|
longitude_correcte = True
|
|
case "GPS":
|
|
if -180 < point_x < 180:
|
|
longitude_correcte = True
|
|
case _:
|
|
pass
|
|
|
|
if not longitude_correcte :
|
|
projection = "Mauvaise projection"
|
|
print(f"\nAvertissement : Longitude du point id {id_point} pas en métropole.")
|
|
|
|
return projection
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# REPRÉSENTATION DES DONNÉES
|
|
|
|
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",
|
|
taille=0):
|
|
self.nom_original = nom_original # - son nom original
|
|
self.chemin = chemin # - son chemin absolue (dossier+fichier)
|
|
self.extension = extension # - son extension
|
|
self.nom = nom # - son nom formaté
|
|
self.implication = implication # - son implication dans le projet
|
|
self.taille = taille
|
|
|
|
|
|
def afficher(self):
|
|
"""
|
|
Affiche dans la sortie standard les éléments du fichier
|
|
"""
|
|
print("")
|
|
print("nom :".ljust(16) + self.nom)
|
|
print("nom orig :".ljust(16) + self.nom_original)
|
|
print("ext :".ljust(16) + self.extension)
|
|
print("chemin :".ljust(16) + self.chemin)
|
|
print("implication :".ljust(16) + self.implication)
|
|
print("taille :".ljust(16) + str(self.taille) )
|
|
|
|
|
|
def lire(dossier, fichier):
|
|
"""
|
|
Lit le nom du fichier et du dossier
|
|
|
|
Construit un élement _Fichier et met à jour tous ses attributs
|
|
:param user_input: chemin absolue du dossier, nom du fichier
|
|
:return: un element _Fichier
|
|
"""
|
|
# initialiser un _Fichier
|
|
ce_Fichier = _Fichier()
|
|
# lire son chemin
|
|
ce_Fichier.chemin = dossier + "\\" + fichier
|
|
# déterminer son nom original et son extension
|
|
ce_Fichier.nom_original, ce_Fichier.extension = fichier.split(".", maxsplit=1) # maxsplit permet de spliter qu'une fois au cas ou on a plusieurs . dans le nom de fichier)
|
|
# formatter et écrire le nom
|
|
ce_Fichier.nom = formatter(ce_Fichier.nom_original)
|
|
# déterminer son implication
|
|
ce_Fichier.impliquer()
|
|
# calculer sa taille
|
|
ce_Fichier.taille = os.path.getsize(ce_Fichier.chemin)
|
|
|
|
return ce_Fichier
|
|
|
|
|
|
def impliquer(self):
|
|
"""Définir l'implication d'un fichier
|
|
|
|
Les fichiers nécessaires seront copiés dans le répertoire "Travail"
|
|
L'implication est définie en fonction de l'extension du fichier.
|
|
Les fichiers nécessaires sont les DWGs, les CSVs pour les datas.
|
|
Pour les shémas et relevés de topo :
|
|
- PDFs, DOCs, ODTs (Doc LibreOffice),
|
|
"""
|
|
match self.extension:
|
|
case "pdf" | "dwg" | "csv" | "doc" | "docs" | "odt" | "PDF" | "DWG" | "CSV" | "DOC" | "ODT" | "DOCS":
|
|
self.implication = "Necessaire"
|
|
case _:
|
|
self.implication = "A-ignorer"
|
|
|
|
|
|
|
|
|
|
|
|
class _Projet:
|
|
|
|
|
|
def __init__(self,
|
|
nom="Pas de nom",
|
|
date="Pas de date",
|
|
racine="Pas de chemin",
|
|
fichiers=[],
|
|
nb_fichiers=0,
|
|
taille=0,
|
|
rapport="Pas de fichier",
|
|
nb_shemas=0,
|
|
nb_releves=0,
|
|
nb_csv=0,
|
|
nb_dwgs=0,
|
|
nb_points = 0,
|
|
notifs=[]):
|
|
self.nom = nom # nom du projet
|
|
self.date = date # date du traitement
|
|
self.racine = racine # chemin racine du projet
|
|
self.fichiers = fichiers # liste de _Fichier
|
|
self.nb_fichiers = nb_fichiers # nb de fichiers dans "Travail"
|
|
self.taille = taille # taille des fichiers dans "Travail"
|
|
self.rapport = rapport # chemin vers le visa
|
|
self.nb_shemas = nb_shemas # nb de plans de la mise en place
|
|
self.nb_releves = nb_releves # nb de rapports de relevés topo
|
|
self.nb_csv = nb_csv # nb de fichiers CSV
|
|
self.nb_dwgs = nb_dwgs # nb de fichiers DWG
|
|
self.nb_pdfs = nb_pdfs # nb de fichiers PDF
|
|
self.nb_points = nb_points # nb de points des CSVs
|
|
self.notifs = notifs # liste contenant les notifs du VISA
|
|
|
|
|
|
def enraciner(self):
|
|
"""
|
|
récupère le repertoire de travail (working directory) courant
|
|
met à jour l'attribut 'racine'
|
|
"""
|
|
self.racine = os.getcwd()
|
|
print("\nRacine : ".ljust(16), f"{self.racine}\n")
|
|
|
|
|
|
def calculer_taille(self):
|
|
"""
|
|
calcule la taille des fichiers necessaires d'un liste
|
|
d'élements _Fichier.
|
|
Met à jour l'attribut 'taille' dans le projet
|
|
"""
|
|
taille = 0.0
|
|
for courant in self.fichiers:
|
|
if courant.implication in "Necessaire":
|
|
taille += courant.taille
|
|
self.taille = taille
|
|
# Affichage adapté à la bonne unité
|
|
if taille < 1024 :
|
|
unite = "octets"
|
|
elif taille < 1024**2 :
|
|
unite = "Ko"
|
|
taille /= 1024
|
|
else :
|
|
unite = "Mo"
|
|
taille /= 1024**2
|
|
print(f"\nTaille totale : {taille:.2f} {unite}.\n")
|
|
|
|
def dater(self):
|
|
"""
|
|
recupère la date du jour
|
|
met à jour l'attribut 'date' du projet
|
|
"""
|
|
self.date = datetime.datetime.today().strftime('%Y%m%d')
|
|
|
|
|
|
def nommer(self):
|
|
"""
|
|
met à jour l'attribut 'nom' en composant un nom.
|
|
Le nom est constitué du nom du dossier racine et de la date
|
|
courante formatté.
|
|
"""
|
|
self.nom = f"{os.path.basename(os.getcwd())}_{self.date}"
|
|
|
|
|
|
|
|
def preparer_dossier_travail(self):
|
|
"""
|
|
Créer un dossier "Travail" dans la racine du working directory et
|
|
le peuple des fichiers nécessaires
|
|
|
|
Met à jour le nombre de fichiers CSVs, DWGs et
|
|
"""
|
|
travail = "Travail"
|
|
|
|
# création du dossier "Travail et dossier"
|
|
chemin = f"{self.racine}\\{travail}"
|
|
print(f"\nCHEMIN FABRIQUE : {chemin}")
|
|
try:
|
|
os.mkdir(chemin)
|
|
print (f'\nDossier "{travail}" créé.')
|
|
except FileExistsError as erreur:
|
|
print(f'\nAvertissement: Le dossier "{travail}" existe déja.')
|
|
except OSError as err:
|
|
print(f"\nFichier non trouvé. Surement un pb de chemin en amont.")
|
|
|
|
# peuplement du dossier Travail avec les fichiers necessaires
|
|
for fichier in self.fichiers:
|
|
source = fichier.chemin
|
|
print(f"\nsource : {source}")
|
|
# lors du peuplement préfixer et suffixer les noms des fichiers concernés comme suit : "Plan_nomfichierdwg.dwg" et "Point_nomfichiercsv_IN.csv"
|
|
# Confirmation par Audrey qu'il n'y a nécessairement qu'un fichier CSV par projet --> Le script s'occupe de le renommer.
|
|
# Mais parfois il peut y avoir plusieurs DWGs dont un seul est utile --> PASS -- Je laisse l'opérateur choisir lequel utiliser et le renommer manuellement.
|
|
match fichier.extension:
|
|
case "csv":
|
|
dest = self.racine + "\\" + travail + "\\" + "Point_" + fichier.nom + "_IN" + "." + fichier.extension
|
|
|
|
case _:
|
|
dest = self.racine + "\\" + travail + "\\" + fichier.nom + "." + fichier.extension
|
|
|
|
print(f"dest : {dest}")
|
|
if fichier.implication in "Necessaire":
|
|
try:
|
|
shutil.copyfile( source , dest)
|
|
print("\n" + fichier.nom.ljust(40,".") + "copié")
|
|
except shutil.SameFileError as err :
|
|
print(f"\nLe fichier {fichier.nom + fichier.extension} existe déjà.")
|
|
#TODO :
|
|
# Confirmation par Audrey qu'il n'y a necessairement qu'un fichier CSV par projet --> On peut le renommer.
|
|
# Mais parfois il peut y avoir plusieurs DWGs dont un seul est utile --> PASS -- Je laisse l'opérateur choisir lequel utiliser et le renommer manuellement.
|
|
#TODO : verifier aussi la longueur des noms de fichiers. Notifier si nécessaire
|
|
|
|
|
|
|
|
def formatter_vers_ArcGIS(self, fichier):
|
|
"""
|
|
Lit et formatte un fichier CSV pour son importation dans ArcGIS.
|
|
|
|
Lit la 1ère ligne et s'assure de la présence des titres de colonnes,
|
|
formatte cette 1ère ligne, supprime les lignes vides puis change le séparateur d'élements en
|
|
',' -- séparateur adapté pour ArcGIS --
|
|
Notifie si nécessaire les points de controles qui ne passent pas.
|
|
Renomme le fichier formatté en "Point_fichier_IN.csv"
|
|
Contraintes :
|
|
- Le fichier CSV NÉCESSITE des ';' comme séparateur d'élements.
|
|
- Le fichier doit avoir exactement 5 colonnes.
|
|
param user_input: nom complet d'un fichier csv
|
|
return: Le nombre de points du csv
|
|
"""
|
|
# titres des colonnes correctement formattés.
|
|
titres = "id_point;TYPE;X;Y;Z"
|
|
|
|
# On va traiter ligne par ligne le fichier, puis cette ligne sera ajouté
|
|
# a une liste. Finalement, on écrasera chaque ligne du fichier avec cette liste
|
|
df = open(fichier)
|
|
sortie = []
|
|
|
|
# analyser la premier ligne et formatter cette ligne
|
|
ligne = df.readline()
|
|
# Si le premier mot est un nombre Alors il manque les titres, dans
|
|
# ce cas insérer une ligne.
|
|
mots = ligne.split(sep=";", maxsplit=1)
|
|
if mots[0].isalpha():
|
|
#insérer ligne
|
|
sortie.append(titres)
|
|
else:
|
|
sortie.append(ligne)
|
|
|
|
# la tête de lecture du descripteur de fichier ne reset pas sa position
|
|
# donc on peut continuer le parcours des lignes directement
|
|
for ligne_courante in df :
|
|
# enlever les espaces
|
|
ligne_courante = ligne_courante.replace(" ", "")
|
|
# si la ligne n'est pas vides alors
|
|
# TODO : changer le match en if ligne_courante not in ["", ";;;;"]:
|
|
match ligne_courante:
|
|
case "" | ";;;;":
|
|
pass
|
|
case _:
|
|
# controler la projection de ce point
|
|
id_point, type_, point_x, point_y, *autres = ligne_courante.split(sep=";")
|
|
projection = controller_projection(id_point, float(point_x), float(point_y))
|
|
#TODO: notifier si pas bonne projection
|
|
|
|
# puis ajouter la ligne a la liste
|
|
sortie.append(ligne_courante)
|
|
# compter ce point
|
|
self.nb_points += 1
|
|
df.close()
|
|
|
|
# je reouvre le descripteur en mode w only pour ecrire le fichier
|
|
|
|
df
|
|
|
|
|
|
|
|
|
|
# structure représetant des messages à mentionner dans le rapport VISA
|
|
# chaque notification possède un texte et sa catégorie concernée.
|
|
class _Notification:
|
|
def __init__(self,
|
|
categorie="Pas de catégorie",
|
|
texte="Pas de texte"):
|
|
self.categorie = categorie
|
|
self.texte = texte
|
|
|
|
|
|
|
|
|
|
# -------------------------------------------------------------------------- #
|
|
# -------------------------------------------------------------------------- #
|
|
# MAIN
|
|
|
|
racine = os.getcwd()
|
|
print("\nRépertoire courant : ".center(18), racine)
|
|
|
|
# --recupérer le chemin du dossier à traiter
|
|
pas_de_dossier = True
|
|
|
|
for a in os.scandir():
|
|
print("\ncourant scandir() = ", a.name.ljust(25), " ", a.is_dir())
|
|
if a.is_dir():
|
|
#il y a un dossier
|
|
pas_de_dossier = False
|
|
print(f"\nDossier à Traiter trouvé : {a.name}")
|
|
racine = a
|
|
|
|
if pas_de_dossier:
|
|
print("\nPas de dossier trouvé...\nFin de programme.\n")
|
|
os.system("pause")
|
|
exit()
|
|
|
|
# Création entité projet et maj de ses attributs
|
|
projet = _Projet()
|
|
|
|
projet.dater()
|
|
|
|
projet.nommer()
|
|
# controle de la longueur du nom du projet
|
|
if len(projet.nom) >=46 :
|
|
print(f"\n{projet.nom} est un nom de dossier trop long (+ de 46 caractères). Veuillez raccourcir son nom\nFin de programme.\n")
|
|
os.system("pause")
|
|
exit()
|
|
|
|
projet.enraciner()
|
|
|
|
projet.rapport = f"{racine}\\Visa_lot_2.xlsx"
|
|
|
|
projet.fichiers = creer_liste(projet.racine)
|
|
|