diff --git a/NEOBANBOU.py b/NEOBANBOU.py new file mode 100644 index 0000000..9e32bf2 --- /dev/null +++ b/NEOBANBOU.py @@ -0,0 +1,1175 @@ +## Version en un seul fichier du script banbou ## + + +import subprocess + + +# 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.") +except subprocess.CalledProcessError: + print("La mise à jour de pip a échoué.") + +bibliotheques = ['os', 'getpass', 're', 'shutil', 'datetime', '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, getpass +import re # module RegEx +import shutil, datetime +import openpyxl as xls + + + + + + +### CONSTANTES ### + + +# chemin absolue en dur du modèle +prenom = getpass.getuser() +MODELE = f"C:\\Users\\{prenom}\\Desktop\\Banbou\\VISA_BANBOU.xlsx" + + +# nom du dossier créé pour l'opérateur +TRAVAIL = "Travail" + + +# séparateur d'un CSV +SEP = ";" + + +# regex, motifs à chercher dans une chaine. Explication : +# (MOTIF)+ matche 1 OU PLUS occurence du mot MOTIF +# (?:) le groupe ne sera pas capturé, du coup dans python le groupe ne créé pas de chaine vide si pas de match du groupe en question +# | OR +EXPRESSION = r"(?:fiche)+|(?:relev)+|(?:topo)+|(?:ouvr)+|(?:\.doc)+|(?:\.dwg)+|(?:\.csv)+|(?:\.odt)+|(?:\.pdf)+|(?:\.doc)+" + + +# Masques Binaires représentants les controles de chaques catégorie traitées +# les bits sont allumés à chaque position où un test existe +FAIL_CSV = 7 # Explication : il existe pour le moment 3 tests pour les CSVs donc : "0000 0000 0000 0111" +FAIL_DWG = 16 # un seul test pour les DWGs : "0000 0000 0001 0000" +FAIL_PDF = 256 # un seul test pour les PDFs : "0000 0001 0000 0000" +FAIL_INFO = 4096 # un seul test pour les fiches infos : "0001 0000 0000 0000" + + +# Valeurs constantes définie par IGN +PROJECTIONS = [ + { + "nom" : "GPS", + "E0" : 0, + "N0" : 0, + "offsetE0" : 180, + "offsetN0" : 90, + "EPSG" : 4326 # Correspond à la projection des GPS + }, + { + "nom" : "Lambert93", + "E0" : 700000, + "N0" : 6600000, + "offsetE0" : 600000, + "offsetN0" : 600000, + "EPSG" : 2154 # Attention la plus recente est 9794 + }, + { + "nom" : "CC42", + "E0" : 1700000, + "N0" : 1200000, + "offsetE0" : 600000, + "offsetN0" : 111000, + "EPSG" : 3942 + }, + { + "nom" : "CC43", + "E0" : 1700000, + "N0" : 2200000, + "offsetE0" : 600000, + "offsetN0" : 111000, + "EPSG" : 3943 + }, + { + "nom" : "CC44", + "E0" : 1700000, + "N0" : 3200000, + "offsetE0" : 600000, + "offsetN0" : 111000, + "EPSG" : 3944 + }, + { + "nom" : "CC45", + "E0" : 1700000, + "N0" : 4200000, + "offsetE0" : 600000, + "offsetN0" : 111000, + "EPSG" : 3945 + }, + { + "nom" : "CC46", + "E0" : 1700000, + "N0" : 5200000, + "offsetE0" : 600000, + "offsetN0" : 111000, + "EPSG" : 3946 + }, + { + "nom" : "CC47", + "E0" : 1700000, + "N0" : 6200000, + "offsetE0" : 600000, + "offsetN0" : 111000, + "EPSG" : 3947 + }, + { + "nom" : "CC48", + "E0" : 1700000, + "N0" : 7200000, + "offsetE0" : 600000, + "offsetN0" : 111000, + "EPSG" : 3948 + }, + { + "nom" : "CC49", + "E0" : 1700000, + "N0" : 8200000, + "offsetE0" : 600000, + "offsetN0" : 111000, + "EPSG" : 3949 + }, + { + "nom" : "CC50", + "E0" : 1700000, + "N0" : 9200000, + "offsetE0" : 600000, + "offsetN0" : 111000, + "EPSG" : 3950 + } + # TODO : ajouter les projections DOMTOMs +] + + + + + + + +### CLASSES ET FONCTIONS + + +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", + categorie="Pas de catégorie", + taille=0): + #self.nom_original = nom_original # - son nom original + self.chemin = chemin # - son chemin absolue (dossier+fichier+extension) + self.extension = extension # - son extension (ecrit en minuscule) + self.nom = nom # - son nom formaté + self.implication = implication # - son implication dans le projet {"Necessaire", "Non-conforme", "A-ignorer"} + self.categorie = categorie # - sa categorie {"CSV", "DWG", "SHEMAS", "TOPO", "Non-definie"} + self.taille = taille # taille en octets + + + def afficher(self): + """ + Affiche dans la sortie standard les éléments du fichier + """ + print("\nfichier.afficher()") + 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("categorie :".ljust(16) + self.categorie) + 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 + """ + print("\nfichier.lire()") + # initialiser un _Fichier + ce_Fichier = _Fichier() + + # son chemin absolue + ce_Fichier.chemin = dossier + "\\" + fichier + + # déterminer son nom original et son extension + # vérifie qu'il y est au moins un point dans le nom + if "." in fichier : + + *nom_original, ce_Fichier.extension = fichier.rsplit(".", maxsplit=1) + # reconstitue une chaine simple depuis la liste + nom_original = "".join(nom_original) + + # lettrer l'extension en minuscule + ce_Fichier.extension = ce_Fichier.extension.casefold() + + # formatter et écrire le nom + ce_Fichier.nom = formater(nom_original) + # déterminer son implication + ce_Fichier.implication, ce_Fichier.categorie = impliquer(fichier) + # calculer sa taille + ce_Fichier.taille = os.path.getsize(ce_Fichier.chemin) + print(f"implication : {ce_Fichier.implication} {ce_Fichier.categorie}") + else: + print(f'"{fichier}" est un fichier sans extension --> Non-conforme.') + ce_Fichier.nom = fichier + ce_Fichier.implication = "Non-conforme" + #TODO: voir si il manque pas des données à lire ici pour pas bloquer le reste du prog + + return ce_Fichier + + + + + +class _Notification: + def __init__(self, + categorie="Pas de catégorie", + texte="Pas de texte"): + self.categorie = categorie # vu comme une énumération de l'ensemble {"CSV", "DWG", "PDF", "FRONT"} seule ces valeurs sont donc possible et traité par le script + self.texte = texte + + + + + + + + + +def controler(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. + NOTE : On ne controle pas l'élevation (coord Z), ces projections ne prennent pas en compte l'élevation. + + 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" + """ + + #print("\nprojection.controler()") + + # 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. En fait non, pas d'export juste un avertissement dans le shell + if 6089000 < point_y < 6311000: + if 1100000 < point_x < 1300000: + print(f"AVERTISSEMENT : ID {ID_POINT} : SES COORDONNÉES PEUVENT ÊTRE INTERPRÉTER CORRECTEMENT COMME DU CC47 ET DU LAMBERT93.") + print("DANS LA PLUPART DES CAS, VOUS DEVREZ PROJETER LE FICHIER CSV EN CC47.") + + + # définie la projection conique Nord en regardant dans quelle intervalle la valeur se situe + for P in PROJECTIONS: + borne_basse = P["N0"] - P["offsetN0"] + borne_haute = P["N0"] + P["offsetN0"] + if borne_basse < point_y < borne_haute : + projection = P["nom"] + + #print(f"Proj. conique Nord trouvé : {projection}") + print(f" --> {projection:10}") + + # 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"AVERTISSEMENT : LONGITUDE DU POINT ID {ID_POINT} PAS EN MÉTROPOLE.") + + return projection + + + + + + + + +## Analyse lexicale + + + +def tokeniser(ligne): #Pas utilisé, je le laisse au cas ou. + """ + Tokenise une ligne de texte. + + Lit en entrée une "ligne" d'un fichier texte, découpe la ligne en mots, + chaque mot étant séparé par le séparateur "sep". + Attendu : si un champ est vide alors la liste reçoit quand même un + item pour representer ce champ (une chaine vide "" qui peut servir + pour tester sa présence) + Retourne une liste des mots trouvés, ou une liste vide si aucun mot. + """ + sortie = [] + + # enleve les espaces superflues avant et après la chaine + tempo = ligne.strip() + tempo = tempo.split(sep=SEP) + + #affichage test + print("tokeniser") + print(tempo) + + # enlever les mots vides + for i in tempo: + print + if i not in '': + sortie.append(i) + + return sortie + + + + +## Conversion, Formatage + + +def formater(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 ' ', les traits d'union '-' et les ''' apostrophes + 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 + + + + + + +## Analyse semantique + + +def impliquer(chaine): + """ + Définir l'implication d'une chaine (attendu : un nom de fichier avec son extension). + + Retourne un tuple de str (Necessité, Catégorie). + + La necessité peut prendre une des valeurs suivantes : + {"Necessaire", "A-ignorer}. + La catégorie peut prendre une des valeurs suivantes: + {"CSV", "DWG", "SHEMAS", "TOPO", "Non-definie"}. + Il est attendu que lorsqu'un fichier obtient une valeur de catégorie + Alors il est aussi considéré "Necessaire". + + Les fichiers "Necessaire" seront les fichiers copiés dans le + répertoire "Travail". + + La catégorie est définie par une recherche de motif dans le nom du fichier. + # TODO: voir si elle peut pas être définie en lisant les métadonnées des fichiers + + 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), + """ + + print(f"\nimpliquer({chaine})") + + # mettre la chaine en minuscule + chaine = chaine.casefold() + + # cherche la PREMIERE correspondance du motif (alors que findall() cherche toutes les correspodances) + motif = re.search(EXPRESSION, chaine) + + if motif: + print("Il y a un match") + motif = motif.group() + else: + print("Pas de match") + + match motif : + # On va traiter les mots clés d'abord + case "fiche" | "relev" | "topo" | "ouvr" | ".doc" | ".odt" : + return ("Necessaire", "TOPO") + case ".dwg" : + return ("Necessaire", "DWG") + case ".csv" : + return ("Necessaire", "CSV") + case ".pdf" : + return ("Necessaire", "SHEMAS") + case _ : + return ("A-ignorer","Non-definie") + + + + + + + + + +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_csvs=0, + points=[], + nb_points=0, + nb_dwgs=0, + notifs=[], + controles=0): + 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_csvs = nb_csvs # nb de fichiers CSV + self.points = points # liste des points du CSV + self.nb_points = nb_points # nb de points des CSVs TODO: REDONDANT avec len(points) + self.nb_dwgs = nb_dwgs # nb de fichiers DWG + self.notifs = notifs # liste contenant les notifs du VISA + self.controles = controles # variable drapeau contenant toutes les validations de controles + # (voir Document pour plus d'info sur sa représentation) + + def notifier(self, categorie, texte): + """ + Ajoute une notification au _Projet + """ + print("\n_Projet.notifier()") + print(f"Incident ajouté :{texte} ") + incident = _Notification( categorie, texte) + self.notifs.append(incident) + + + def lister_fichiers(self, dossier): + """ + Construit une liste avec les fichiers de 'dossier' + + - Parcours le dossier et ses sous-dossiers, ajoute TOUS les fichiers + dans une liste de _Fichier. + - Chaque _Fichier ajouté a ses attributs mis a jour. + - Les nombres des différentes catégories de fichiers nécessaires + au projet sont mis a jour. + :param user_input: nom complet du dossier + :return: liste d'élements de type _Fichier + """ + print("\n_Projet.lister_fichiers()") + self.fichiers = [] + + print("Création liste de _Fichier...") + + for dossier_courant, list_sousdossiers, list_fichiers in os.walk(dossier): + for fichier_courant in list_fichiers: + ce_Fichier = lire(dossier_courant, fichier_courant) + self.fichiers.append(ce_Fichier) + print(f'"{fichier_courant}" ajouté.') + + # mettre à jour les nbs des catégories de fichiers necessaires + if ce_Fichier.implication in "Necessaire": + ### sa categorie {"CSV", "DWG", "SHEMAS", "TOPO", "Non-definie"} + match ce_Fichier.categorie : + case "CSV": + self.nb_csvs += 1 + case "DWG": + self.nb_dwgs += 1 + case "SHEMAS": + self.nb_shemas += 1 + case "TOPO": + self.nb_releves += 1 + case _: + pass + + print("Fin création liste.") + + +# TODO surcharger la fonction print native pour cet affichage +def afficher_liste(liste): + """ + Affiche le nom des fichiers de la liste des _Fichiers + """ + print("\nprojet.afficher_liste()") + for courant in liste: + print("\n" + courant.nom + "." + courant.extension) + + +def enraciner(projet): + """ + récupère le repertoire de travail (working directory) courant + met à jour l'attribut 'racine' d'un projet + """ + print("\nprojet.enraciner()") + projet.racine = os.getcwd() + print("Racine : ".ljust(16), f"{projet.racine}") + + +def calculer_taille(projet): + """ + calcule la taille des fichiers necessaires d'un liste + d'élements _Fichier. + Met à jour l'attribut 'taille' dans le projet + """ + print("\nprojet.calculer_taille()") + taille = 0.0 + for courant in projet.fichiers: + if courant.implication in "Necessaire": + taille += courant.taille + projet.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"Taille totale : {taille:.2f} {unite}.") + + +def dater(projet): + """ + recupère la date du jour + met à jour l'attribut 'date' du projet + """ + print("\nprojet.dater()") + projet.date = datetime.datetime.today().strftime('%Y%m%d') + print(f"Date : {projet.date}") + + +def nommer(projet): + """ + Met à jour l'attribut 'nom' en composant un nom. + + Le nom est constitué du NOM du dossier racine ET de la DATE + courante formatté. + + """ + print("\nprojet.nommer()") + projet.nom = f"{os.path.basename(os.getcwd())}_{projet.date}" + projet.nom = formater(projet.nom) + print(f'Nom : "{projet.nom}"') + + +def preparer_dossier_travail(projet): + """ + Créer un dossier "Travail" dans la racine du working directory et + le peuple des fichiers nécessaires + + Préfixe et suffixe les fichiers le nécessitant (le CSV et le DWG) + """ + print("\nprojet.preparer_dossier_travail()") + #travail = "Travail" + + # création du dossier "Travail" et de ses sous-dossiers " + _chemin = f"{projet.racine}\\{TRAVAIL}" + #nomemclaturer les noms des sousdossiers + sousdossier = f"{_chemin}\\{projet.nom}" + + # pour le moment tous les sous dossiers sont + dossierCSVs = _chemin + dossierDWGs = _chemin + dossierPDFs = _chemin + dossierTOPOs = _chemin + + print(f'Chemin du dossier de préparation : "{_chemin}"') + try: + os.mkdir(_chemin) + print (f'Dossier "{TRAVAIL}" créé.') + os.mkdir(sousdossier) # dossier pour le projet ARCGIS + + if projet.nb_csvs > 1: + dossierCSVs = f"{sousdossier}_CSV" + os.mkdir(dossierCSV) + print (f'Dossier "CSV" créé.') + if projet.nb_dwgs > 1: + dossierDWGs = f"{sousdossier}_DWG" + os.mkdir(dossierDWGs) + print (f'Dossier "DWG" créé.') + if projet.nb_shemas > 1: + dossierPDFs = f"{sousdossier}_PDF" + os.mkdir(dossierPDFs) + print (f'Dossier "PDF" créé.') + if projet.nb_releves > 1: + dossierTOPOs = f"{sousdossier}_TOPO" + os.mkdir(dossierTOPOs) + print (f'Dossier "TOPO" créé.') + + except FileExistsError as erreur: + print(f'AVERTISSEMENT: LE DOSSIER "{TRAVAIL}" EXISTE DÉJA. SUPPRIMER LE, PUIS RELANCER LE SCRIPT SVP.') + purger_et_finir_programme(projet) + except OSError as erreur: + print(f"FICHIER NON TROUVÉ. SUREMENT UN PB DE CHEMIN EN AMONT.") + purger_et_finir_programme(projet) + + + + # peuplement du dossier Travail avec les fichiers necessaires + print("Copie des fichiers nécessaires et nomenclature de leurs noms...") + for fichier in projet.fichiers: + if fichier.implication in "Necessaire": + source = fichier.chemin + print(f"source : {source}") + + # fabrication du nom du fichier de destination + dest = _chemin + "\\" + + # 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. + + #{"CSV", "DWG", "SHEMAS", "TOPO", "Non-definie"} + match fichier.categorie : + case "CSV": + dest = f"{dossierCSVs}\\Point_{fichier.nom}_IN" + case "DWG": + dest = f"{dossierDWGs}\\Plan_{fichier.nom}" + case "SHEMAS": + dest = f"{dossierPDFs}\\{fichier.nom}" + case "TOPO": + dest = f"{dossierTOPOs}\\{fichier.nom}" + case _: + pass # TODO: voir pour les autres cas, normalement il n'y en a pas pour le moment + + dest = dest + f".{fichier.extension}" + print(f"dest : {dest}") + + if fichier.implication in "Necessaire": + try: + shutil.copyfile( source , dest) + print("" + fichier.nom.ljust(40,".") + "copié") + except shutil.SameFileError as err : + print(f"Le fichier 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. EDIT : Nathalie prefere que tous les fichier DWG soient préfixé avec Plan_ + #TODO : verifier aussi la longueur des noms de fichiers. Notifier si nécessaire + + +def controler_longueur_noms(projet): + """ + controle la longueur des noms des fichiers du projet. + + Notifie si necessaire les noms trop longs + """ + #print("\nprojet.controler_longueur_noms()") + for fichier in projet.fichiers: + match fichier.extension: + case "dwg": + if len(fichier.nom) > 25 : # Plan_ + 25 carac --> 30 carac + projet.notifier("DWG", f'"Plan_{fichier.nom}.dwg" : Nom trop long.') + case "csv" : + if len(fichier.nom) > 35 : # Point_ + 35 carac + _OUT --> 45 carac + projet.notifier("CSV", f'"Point_{fichier.nom}_IN.csv" : Nom trop long.') + case "pdf" : + if len(fichier.nom) > 45 : + projet.notifier("PDF", f'"{fichier.nom}.pdf" : Nom trop long.') + case _: + if len(fichier.nom) > 45 : + projet.notifier("FRONT", f'"{fichier.nom}.{fichier.extension}" : Nom trop long.') + +def formater_vers_ArcGIS(projet, fichier_entree, fichier_sortie): + """ + 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. + Change le séparateur décimal ',' en '.' (necessaire car sinon je ne peux convertir les strings en float) + Notifie si nécessaire les points de controles qui ne passent pas. + Met à jour l'attribut nb_points + Contraintes : + - Le fichier CSV NÉCESSITE des ';' comme séparateur d'élements. (Normalement c'est toujours le cas de toutes façon) + - Le fichier doit avoir exactement 5 colonnes. TODO : vois si plus de souplesse avec le catchage de paramètres restants + param user_input: nom complet d'un fichier csv + NOTE: Ne pas formatter le fichier original, mais celui qui est deja copié dans le dossier Travail + """ + print("\nprojet.formatter_vers_ArcGIS()") + + # titres des colonnes correctement formattés. + titres = "id_point;TYPE;X;Y;Z\n" + + sortie = [titres] # le fichier de sortie ( representé comme une liste de lignes ) contient un premiere ligne de titre + + + df = open(fichier_entree, "r") + ligne = df.readline() + + # analyser la premiere ligne et formatter cette ligne + # 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) + print(f"Mots[0] = {mots[0]}") + + + if mots[0].isnumeric(): + #insérer ligne + sortie.append(ligne) + projet.nb_points += 1 + + # 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 : + + # lit et controle la ligne courante + courant = lire_et_controler_ligne(projet, ligne_courante) + # ajoute la ligne + if courant not in "": + sortie.append(f"{courant}\n") + # compter ce point + projet.nb_points += 1 + + df.close() + + # je reouvre le descripteur en mode w only pour ecrire le fichier + df = open(fichier_sortie, "w") + # écriture + for ligne in sortie: + #print(f"{ligne}") + df.write(ligne) # NOTE: write() + + df.close() + + print("Fin formatage du CSV.") + + +def lire_et_controler_ligne(projet, ligne): + """ + Analyse une chaine de caractère (concrètement, une ligne du CSV)/ + + remplace les séparateurs décimaux ',' par '.' + Notifie (dans le projet lié) les points de controles qui ne passent pas. + Met à jour l'attribut nb_points du projet lié + Met à jour la variable de controles du projet lié + Contraintes : + - Le fichier CSV NÉCESSITE des ';' comme séparateur d'élements. + :return: une chaine de carac formatté pour ArcGIS + """ + #print("\nprojet.lire_et_controler_ligne()") + #print(f'ENTREE : "{ligne}"') + # enlever les espaces + ligne = ligne.replace(" ", "") + ligne = ligne.replace(",", ".") + ligne = ligne.replace("\n", "") + # si la ligne n'est pas vides alors + # TODO : changer le match en if ligne not in ["", ";;;;"]: + match ligne: + case ";;;;;;" | ";;;;;" | ";;;;" | ";;;" | ";;" | ";" | "" : + ligne = "" + case _: + # controler la projection de ce point + # NOTE: split() tokenise la ligne en items (des strings), si un champ est manquant, alors il tokenise une chaine vide( "" ) + # On peut donc tester cette chaine vide pour savoir si une champ est manquant.abs + # Je vais dont controler la présence du champ ID, car il apparait que parfois il est manquant. Si tel est le cas, + # Notifier et terminer le programme --> dossier FAIL + # + # NOTE: split() n'enleve pas le \n de la fin de ligne dans la chaine --> ligne = ligne.replace("\n", "") que j'ai rajouter au dessus + try : + id_point, type_, point_x, point_y, point_z, *autres = ligne.split(sep=";") + except ValueError: + print("LE FICHIER SOURCE CSV EST MAL FORMATÉ, IL DOIT MANQUER DES CHAMPS ET DE SÉPARATEURS DE CHAMPS.") + purger_et_finir_programme(projet) + + + #PRINT de controle + print(f'ID:"{id_point}", TYPE:"{type_}", X:"{point_x}", Y:"{point_y}", Z:"{point_z}", *autres={autres}') + + # controler qu'il n'y est pas un champ vide pour id_point, TYPE, X et Y. (Z étant optionnel) + # Si tel est le cas, on quitte FAIL + if id_point in "": + print('AVERTISSEMENT : COLONNE "ID_POINT" MANQUANTE.') + purger_et_finir_programme(projet) + if type_ in "": + print('AVERTISSEMENT : COLONNE "TYPE" MANQUANTE.') + purger_et_finir_programme(projet) + if point_x in "": + print('AVERTISSEMENT : COLONNE "X" MANQUANTE.') + purger_et_finir_programme(projet) + if point_y in "": + print('AVERTISSEMENT : COLONNE "Y" MANQUANTE.') + purger_et_finir_programme(projet) + if point_z in "": + print('AVERTISSEMENT : COLONNE "Z" MANQUANTE.') + purger_et_finir_programme(projet) + + # controler que id_point est un entier + # Si c'est un flottant alors ya un pb, on quitte FAIL + try : + if float(id_point)/int(id_point) != 1.0 : + print("AVERTISSEMENT : LA PREMIÈRE COLONNE EST UN FLOTTANT. ATTENDU UN ID_POINT DE VALEUR ENTIÈRE.") + print('IL DOIT MANQUER LA COLONNE "ID_POINT AINSI QUE LE SÉPARATEUR DE CHAMP ";".') + purger_et_finir_programme(projet) + except ValueError : + print("AVERTISSEMENT : LA COLONNE ID_POINT N'A PAS UN NOMBRE.") + print(f'ATTENDU UN ENTIER. TROUVÉ ID_POINT DE TYPE "{type(id_point)}"') + purger_et_finir_programme(projet) + + + # A partir d'ici il est attendu que la tokenisation se soit bien déroulée + try : + projo = controler(id_point, float(point_x), float(point_y)) + except ValueError : + print("AVERTISSEMENT : UNE COLLONNE (X OU Y) EST MAL CONVERTIE.") + print(f'ATTENDU VALEURS DÉCIMALES. TROUVÉ X : DE TYPE "{type(point_x)}", Y DE TYPE "{type(point_y)}"') + purger_et_finir_programme(projet) + # notifier si pas bonne projection + if projo in "Mauvaise projection": + incident = _Notification("CSV", f"Point ID {id_point} : Mauvaise projection") + projet.notifs.append(incident) + projet.controles |= 2**2 + + #print(f'SORTIE : "{ligne}"') + + return ligne + + + + + + + + + +def remplir(modele, projet): + """ + Complète le Visa du projet à partir d'un fichier modele. + + Controle le nb de fichiers de chaque catégorie et notifie. + Parcours la liste de notifications du projet et ajoute chaque + notification dans la feuille concerné du classeur + A défaut, une notification de la liste qui n'a pas de catégorie + avec sa propre feuille sera ajoutée à la première feuille (FRONT) + + Enregistre le Visa dans le repertoire de Travail + Contraintes : + - le modele du VISA doit être NÉCESSAIREMENT dans le même + dossier que le script. TODO : traité l'exception si fichier non trouvé + - le modele du VISA NE doit PAS avoir eu de modification notable + dans son design ( emplacements des cellules utilisées pour + notifier, nom des feuilles, etc) + """ + + print("\nvisa.remplir()") + + + + # creation d'un workbook à partir d'un modele + classeur = xls.load_workbook(modele) + + + ## definition personnalisé vers chaque feuille du classeur + FRONT = classeur[classeur.sheetnames[0]] + PDF = classeur[classeur.sheetnames[1]] + CSV = classeur[classeur.sheetnames[2]] + DWG = classeur[classeur.sheetnames[3]] + + + # Nom dossier + # Date de reception + # Date Visa + FRONT['C1'].value = f'Analyse : {projet.nom}\nDate de réception :\nDate visa : {projet.date}' + + # Taille du dossier + taille = projet.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 + + + FRONT['C10'].value = f"Taille totale : {taille:.2f} {unite}." + + + # TODO: factoriser les instructions ci dessous (pas sur...) + + + + ### FRONT DWG + # Je ne peux pas controler automatiquement la projection du DWG (c'est un binaire illisible hors de AutoCAD) + # J'ajoute donc automatiquement la mention comme dans le script précedent. + FRONT['C3'].value = f"{projet.nb_dwgs} fichier(s) DWG présent(s).\nProjection de tous les fichiers DWG : RFG93-CC43 (EPSG:3943)." + + # Controle le nb de fichiers DWG + if projet.nb_dwgs == 0: + projet.controles |= 2**4 # allume le bit 5ième bit + FRONT['C3'].value = "Pas de fichier DWG présent." + + # Regarde si des fails ont été détectés sur les DWGs + fail = projet.controles & FAIL_DWG # operation logique avec le masque + # notifie en consequence + if fail : + FRONT['B3'].value = "FAIL" + else : + FRONT['B3'].value = "OK" + + + + ### FRONT PDF + FRONT['C4'].value = f"{projet.nb_shemas} fichier(s) PDF présent(s)." + + # Controle le nb de fichiers PDF + if projet.nb_shemas == 0: + projet.controles |= 2**8 # allume le 9ième bit + FRONT['C4'] = "Pas de fichier PDF présent." + + # Regarde si des fails ont été détectés sur les PDFs + fail = projet.controles & FAIL_PDF + # notifie en consequence + if fail : + FRONT['B4'].value = "FAIL" + else : + FRONT['B4'].value = "OK" + + + + ### FRONT CSV + FRONT['C5'].value = f"1 fichier(s) CSV présent(s).\n{projet.nb_points} point(s) en RFG93-CC43 (EPSG:3943)." + + # Controle le nb de fichiers CSV + if projet.nb_csvs == 0: + projet.controles |= 2**0 # allume le 1er bit + FRONT['C5'].value = "Pas de fichier CSV présent." + + # Regarde si des fails ont été détectés sur les CSVs + fail = projet.controles & FAIL_CSV + # notifie en consequence + if fail : + FRONT['B5'].value = "FAIL" + else : + FRONT['B5'].value = "OK" + + + + # FRONT FICHE INFO TOPOLOGIE + FRONT['C6'].value = f"{projet.nb_releves} fiche(s) Info. présente(s)." + + # Controle le nb de fichiers Fiche Topologique + if projet.nb_releves == 0: + projet.controles |= 2**12 # allume le 13ième bit + FRONT['C6'].value = "Pas de fiche d'Info. Topologie présente." + + # Regarde si des fails ont été détectés sur les Fiches Topo + fail = projet.controles & FAIL_INFO + # notifie en consequence + if fail : + FRONT['B6'].value = "FAIL" + else : + FRONT['B6'].value = "OK" + + + + + # Notifier dans les autres Feuilles + + # lignes en cours à remplir pour chaque feuille + # initialisé aux lignes où on va commencer à notifier + A = 2 # "B2" # Pour la feuille PDF + B = 2 # "B2" # Pour la feuille CSV + C = 2 # "B2" # Pour la feuille DWG + D = 12 # "C12" # Pour la feuille FRONT + + cell = "" # cellule courante qui va être notifier + + for notif in projet.notifs : + match notif.categorie : + case "PDF" : + cell = PDF["B"+str(A)] + A += 1 + case "CSV" : + cell = CSV["B"+str(B)] + B += 1 + case "DWG" : + cell = DWG["B"+ str(C)] + C += 1 + case _: + cell = FRONT["C"+str(D)] + D += 1 + + cell.value = notif.texte + + + # Ici le VISA doit être correctement rempli + # on sauvegarde dans un fichier le classeur + classeur.save(f"Travail\\{projet.nom}_VISA.xlsx") + + +def purger_et_finir_programme(projet): + """ + Efface le dossier "Travail" si il existe puis termine le programme. + + Cette fonction est utilisée quand des expections sont levées et + qu'elles nécessitent la fermeture prématurée du programme. + """ + print("\npurger_et_finir_programme()") + print("\nFIN DE PROGRAMME.\n") + # Ne fonctionne pas car des descripteurs de fichiers sont utilisés au moment de la demande + ## shutil.rmtree(f"{projet.racine}\\{TRAVAIL}" , ignore_errors=True) + quit() + + + + + + + + + + + +## NOTE : Pour traiter le cas des sous-dossiers lors du dézippage +# il vaut mieux que l'opérateur copie le fichier banbou.py dans chaque +# sous-dossiers puis l'execute. +## Donc : +# - Il est attendu que le fichier script se situe dans le dossier, au +# même niveau que les fichiers originaux. +# - Il est attendu que le modele du visa soit dans un autre dossier +# BIEN SPÉCIFIÉ (je vais remettre un chemin absolue "Desktop\Banbou" car suivant l'opérateur, le dézippage crée des sous-sous-dossiers) +# De plus, pour simplifier le dossier Travail se créera, dans le dossier +# ou se situe le script, aux coté des fichiers originaux +##TODO: Automatiser le cas des sous dossiers, avec un FOR each dossier FAIRE +## Et créer des dossiers Travail01, Travail02, etc + + + +# Création entité projet et mise à jour de ses attributs +ce_Projet = _Projet() + +enraciner(ce_Projet) + +dater(ce_Projet) + +nommer(ce_Projet) +# controle de la longueur du nom du projet +if len(ce_Projet.nom) >=46 : + print(f"\n{ce_Projet.nom} EST UN NOM DE DOSSIER TROP LONG (+ DE 46 CARACTÈRES). VEUILLEZ RACCOURCIR SON NOM.") + purger_et_finir_programme(ce_Projet) + +# explorer le dossier Trouvé et fabriquer la liste des fichiers nécessaires +ce_Projet.lister_fichiers(ce_Projet.racine) + +calculer_taille(ce_Projet) + +# Ici le projet doit avoir toutes les données nécessaires pour fabriquer +# et remplir les dossiers attendus +preparer_dossier_travail(ce_Projet) + + +# il faut UN SEUL fichier CSV NOTE: a voir si ce controle est obligatoire +if ce_Projet.nb_csvs > 1: + print("\nAVERTISSEMENT : IL Y A PLUSIEURS FICHIERS CSV.") + print("VEUILLEZ NE GARDER QU'UN SEUL FICHIER CSV DANS LE DOSSIER ORIGINAL.") + purger_et_finir_programme(ce_Projet) + +# On formatte le fichier CSV pour ArcGIS +for fichier in ce_Projet.fichiers: + if fichier.categorie in "CSV": + entree = ce_Projet.racine + "\\Travail\\Point_" + fichier.nom + "_IN" + ".csv" + sortie = ce_Projet.racine + "\\Travail\\Point_" + fichier.nom + "_IN" + ".csv" + formater_vers_ArcGIS(ce_Projet, entree, sortie) + + + +# On controle les noms des fichiers copiés +controler_longueur_noms(ce_Projet) + + +# On fabrique le VISA +#modele = ce_Projet.racine + "\\VISA_BANBOU.xlsx" +remplir(MODELE, ce_Projet) + + + + + + +#Fin de Programme Attendu +print("\nProgramme terminé correctement. ") \ No newline at end of file diff --git a/README.md b/README.md index 4367c78..be10558 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,139 @@ -# NeoBanbou +# NeoBanbou 🐼🐼 -Script Python d'assistance dans le controle des dossiers de plan de recollement de fibre optiques. +Script Python utilisé pour le controle qualité de dossiers de plan de +recollement de fibre optiques. + +## 🎍 Mise en place 📖 + +Lorsque vous débutez sur Banbou, la première fois. + +1. Sur le bureau, créez un dossier 📁 nommé *Banbou* (B majuscule). +2. Copiez/collez 📋 dans celui ci, les fichiers : + - `NEOBANBOU.py` 📄 + - `VISA_BANBOU.xlsx` 📄 + - `STYLE_SYMBOLOGIE.lyrx` 📄 + +Et voilà ! + +## 🎍 Utilisation 📖 + +A faire à chaque dossier à traiter. + +1. Copiez/collez 📋 l'archive 🗃 du dossier à traiter dans le dossier *Banbou*, et dézipper la 🤐. +2. A l'intérieur de ⮩ **chaque sous-dossier** ainsi dézippé (ou à défaut dans le dossier dézippé), copiez/collez-y 📋 le fichier `NEOBANBOU.py` 📄. +3. Exécutez ⚙ le script `NEOBANBOU.py` ainsi déposé. + +Vous pouvez fermer les fenêtres apparues une fois le script terminé. Bien joué ! 👍 + +## 🎍 Fonctionnement 📖 + +### ⛪ Historique ⛪ + +Après avoir lu et compris le contenu de l'ancien script, et avoir apporté quelques modifications, j'ai plutot décidé de repartir de zéro. +Ma première version était divisée en plusieurs modules et fichiers. +Mais pour une question d'ergonomie dans l'utilisation, j'ai tout regroupé sur un seul fichier. + +### 🧪 Tests unitaires 🧪 + +Dans l'optique de m'exercer, j'ai créé dans l'ancienne version des tests unitaires pour chaque fonction et classe. Malheuresement, je ne les ai pas mis a jour avec la dernière version, ils ne sont donc pour le moment plus fonctionnels. + +### 🔃 Déroulé du programme 🔃 + +Voici un explicatif du déroulé du programme : + +#### 1. Imports des bibliothèques et modules. + + - lignes 4-14 : Le script commence par vérifier la présence de la commande `pip` (commande qui permet d'installer des modules *PyPI* (Python Package Index, le dépot communautaire des modules Python) et l'installe si nécessaire. + - lignes 15-25 : Le script vérifie la présence des modules additionnels utilisés dans le script et les installe si nécessaire. + +> Note : Cette partie est en vérité à exécuter qu'une seule fois, lors de la première utilisation du script. Si vous souhaitez accélérer l'exécution du script pour les prochaines fois, effacez cette partie (lignes 4 à 25). + + - lignes 29-32 : Import des modules utilisés. + + #### 2. Constantes + + - lignes 39-162 : Vous trouverez une liste de variables constantes : + - Le nom de l'utilisateur Windows. + - Le chemin vers le modèle du Visa. + - Le nom du dossier qui receptionne les fichiers créés. + - Le caractère choisi comme séparateur d'élements d'un fichier CSV. + - L'expression régulière utilisée pour filtrer les fichiers nécessaires des fichiers inutiles. (VOir lien wiki sur les RegEx) + - Les masques binaires utilisés pour les opérations logiques de validation de point de controle. TODO Voir lien plus loin pour les explications. + - Une liste de projections polaires coniques utilisé en France Métropolitaine (TODO Voir lien réf IGN). Elles sont, chacunes, définies et contraintes par leurs plages des valeurs (les coordonnées géographiques). -## Lancer les Tests +> Note : Vous pouvez modifier facilement le comportement du programme en modifiant directement ces variables. -Pour lancer les tests, il faudra préalablement installer un environnement de travail isolé +#### 3. Classes et fonctions -```bash - python -m venv venv -``` +Il s'agit du regroupement des modules que j'avais externalisés dans l'ancienne version : -Puis basculer vers cet environnement (a faire à chaque fois qu'on ouvre un shell) -```bash - source venv/Scripts/activate -``` -Sous Windows ce sera `env\Scripts\activate.bat` +- lignes 172-245 : Une représentation d'un fichier. +- lignes 251-256 : Une représentation d'une notification. +- lignes 266-328 : Une fonction importante qui controle la validité des coordonnées d'un point géographique. +- lignes 337-472 : Des fonctions *Input/Output* sur des chaines de caractères (ex: formater un nom suivant une nomenclature donné, etc) +- lignes 482-906 : Une représentation du projet. +- lignes 916-1081 : Une fonction qui génère le Visa de controle. + +#### 4. Exécution du programme + +>Note : Le programme principal parait donc très court car il va ensuite faire appel à toutes les autres fonctions vu plus haut. + +- lignes 1123-1134 : Le programme commence par créer un Projet et définie des valeurs utiles (date, chemin, etc) +- ligne 1136 : Le programme parcourt le dossier où se trouve le script et analyse tous les fichiers trouvés (quel est son nom, son extension, est-il est utile pour le projet ? ) et stocke toutes ces infos dans une liste pour être utiliser plus tard. + - ligne 1138 : calcule la taille **seulement** des fichiers qui vont être copiés dans le dossier de Travail. + - ligne 1142 : reprend la liste créée et décide des dossiers à créer puis y copie les fichiers attendus. Ajoute les préfixes et suffixes aux noms de fichiers si nécessaire. +- lignes 1151-1156 : le fichier CSV contenant les points est lu. Chaque ligne, contenant une coordonnée, est : + - analysée, on vérifie de sa projection. + - formatée, on corrige certains défauts de syntaxe. Les lignes vides, etc. + - ajoutée à une liste. Cette liste est ensuite écrite dans un fichier. + +- ligne 1161 : Le programme controle la longueur de tous les noms de fichiers. +- ligne 1166 : finalement le programme crée un visa à partir d'un modèle, et ajoute toutes les informations nécessaires, ainsi que toutes les notifications +sur des points de controles qui ne seraient pas passés. + +### 🛂🚧 Les points de controles réalisés 🚧🛂 + +#### 1. 🚦Intersection CC47 et Lambert93 🚦 + +Voir shémas (TODO) + +#### 2. 🧭 Longitude pas en métropole 🧭 +Si la projection polaire est correcte, je vérifie et avertie si la longitude du point ne se situe pas en métropole. + +#### 3. 📏 Longueur des noms de fichiers 📏 +>Note : Par nom de fichier je veux dire le nom du fichier sans son point ni son extension. Il faut cependant compter les préfixes et suffixes ajoutés. +Ex : pour le fichier `Plan_monfichierDWG.dwg`, le nom retenu sera *Plan_monfichierDWG* simplement. + +La règle retenu est : +- Pour le fichier DWG, longueur nom strictement inférieure à 31. +- Tous les autres noms de fichiers et dossiers, longueur nom strictement inférieure à 46. + +#### 4. 🛸 Fichier CSV imcomplet, inconnu 🛸 +Le formatage du fichier ne résout pas tout. Si il manque des données, des champs entiers, etc, je ne vais pas les créer. Dans ce cas, le programme s'arrete +en affichant un avertissement. -J'ai utiliser Pytest pour les tests et son module pytest-mock +#### 5. 🚧 Controle la présence des fichiers nécessaires +Un projet doit necessairement comporter au moins ces 4 fichiers : +- un fichier PDF 📄 de plan de situation +- un fichier PDF 📄 (ou autres formats), la fiche de topographie +- un fichier DWG 📄, le fichier de couches AutoCad +- un **seul** fichier CSV 📄, le fichier contenant le relevé des coordonnées de chaque points. -```bash - pip install pytest - pip install pytest-mock -``` +>Note: Si un de ces 4 fichiers est absent, le projet devrait être considéré comme **incomplet**. Des FAILs se notifieront dans la page FRONT. -Les tests sont situés dans le dossier `test/` pour les lancer tous, depuis la racine du projet : +## 🎍 A l'attention des utilisateurs avancés 📖 -``` - pytest test/ -``` +J'ai considéré l'écriture de ce script comme un exercice d'algorithmie et de d'apprentissage du Python. +De ce point de vue, il y a certainement beaucoup d'optimisation. +*C'est vrai ça : Pourquoi s'embêter à faire des classes pour des objets qui ont deja nativement dans le langage des classes et méthodes ( wtf une classe _Fichier ??), ou alors pourquoi implementer une variable binaire pour comptabiliser les controles ? (alors que de simples variables ou une liste de variables aurait pu aussi bien fait l'affaire...).* -## Auteurs +**Réponse :** pour la pratique et la curiosité ! -- [@david.castex](https://gitea.digitanie.org/david.castex) \ No newline at end of file +Donc oui, ce script n'est pas optimal. Mais, il est robuste (plutot) et fonctionnel pour l'usage prévu en production. + +Notez également qu'il n'y a eu aucun recours à de l'IA. L'exercice consistait aussi à conceptualiser les besoins. +TODO Explication avancées. \ No newline at end of file diff --git a/STYLE_SYMBOLOGIE.lyrx b/STYLE_SYMBOLOGIE.lyrx new file mode 100644 index 0000000..a935774 --- /dev/null +++ b/STYLE_SYMBOLOGIE.lyrx @@ -0,0 +1,1458 @@ +{ + "type" : "CIMLayerDocument", + "version" : "2.4.0", + "build" : 19948, + "layers" : [ + "CIMPATH=carte/c_zurlescuresro002fi091640000v1_210426_in_xytabletopoint.xml" + ], + "layerDefinitions" : [ + { + "type" : "CIMFeatureLayer", + "name" : "BANBOU_DOE_GC_ZURLESCURESRO002FI091640000V1_210426_IN_XYTableToPoint", + "uRI" : "CIMPATH=carte/c_zurlescuresro002fi091640000v1_210426_in_xytabletopoint.xml", + "sourceModifiedTime" : { + "type" : "TimeInstant" + }, + "useSourceMetadata" : true, + "description" : "BANBOU_DOE_GC_ZURLESCURESRO002FI091640000V1_210426_IN_XYTableToPoint", + "layerElevation" : { + "type" : "CIMLayerElevationSurface", + "mapElevationID" : "{CC73DA4E-A2D2-4B9E-91FB-5DC812875CDD}" + }, + "expanded" : true, + "layerType" : "Operational", + "showLegends" : true, + "visibility" : true, + "displayCacheType" : "Permanent", + "maxDisplayCacheAge" : 5, + "showPopups" : true, + "serviceLayerID" : -1, + "refreshRate" : -1, + "refreshRateUnit" : "esriTimeUnitsSeconds", + "autoGenerateFeatureTemplates" : true, + "featureElevationExpression" : "0", + "featureTable" : { + "type" : "CIMFeatureTable", + "displayField" : "TYPE", + "editable" : true, + "dataConnection" : { + "type" : "CIMStandardDataConnection", + "workspaceConnectionString" : "DATABASE=..\\..\\..\\ArcGIS\\MyProject\\MyProject.gdb", + "workspaceFactory" : "FileGDB", + "dataset" : "BANBOU_DOE_GC_ZURLESCURESRO002FI091640000V1_210426_IN_XYTableToPoint", + "datasetType" : "esriDTFeatureClass" + }, + "studyAreaSpatialRel" : "esriSpatialRelUndefined", + "searchOrder" : "esriSearchOrderSpatial" + }, + "featureTemplates" : [ + { + "type" : "CIMFeatureTemplate", + "name" : "BANBOU_DOE_GC_ZURLESCURESRO002FI091640000V1_210426_IN_XYTableToPoint", + "tags" : "Point", + "toolProgID" : "2a8b3331-5238-4025-972e-452a69535b06" + } + ], + "htmlPopupEnabled" : true, + "selectable" : true, + "featureCacheType" : "Session", + "labelClasses" : [ + { + "type" : "CIMLabelClass", + "expression" : "$feature.TYPE", + "expressionEngine" : "Arcade", + "featuresToLabel" : "AllVisibleFeatures", + "maplexLabelPlacementProperties" : { + "type" : "CIMMaplexLabelPlacementProperties", + "featureType" : "Point", + "avoidPolygonHoles" : true, + "canOverrunFeature" : true, + "canPlaceLabelOutsidePolygon" : true, + "canRemoveOverlappingLabel" : true, + "canStackLabel" : true, + "connectionType" : "Unambiguous", + "constrainOffset" : "NoConstraint", + "contourAlignmentType" : "Page", + "contourLadderType" : "Straight", + "contourMaximumAngle" : 90, + "enableConnection" : true, + "enablePointPlacementPriorities" : true, + "featureWeight" : 0, + "fontHeightReductionLimit" : 4, + "fontHeightReductionStep" : 0.5, + "fontWidthReductionLimit" : 90, + "fontWidthReductionStep" : 5, + "graticuleAlignmentType" : "Straight", + "keyNumberGroupName" : "Par défaut", + "labelBuffer" : 15, + "labelLargestPolygon" : true, + "labelPriority" : -1, + "labelStackingProperties" : { + "type" : "CIMMaplexLabelStackingProperties", + "stackAlignment" : "ChooseBest", + "maximumNumberOfLines" : 3, + "minimumNumberOfCharsPerLine" : 3, + "maximumNumberOfCharsPerLine" : 24, + "separators" : [ + { + "type" : "CIMMaplexStackingSeparator", + "separator" : " ", + "splitAfter" : true + }, + { + "type" : "CIMMaplexStackingSeparator", + "separator" : ",", + "visible" : true, + "splitAfter" : true + } + ] + }, + "lineFeatureType" : "General", + "linePlacementMethod" : "OffsetCurvedFromLine", + "maximumLabelOverrun" : 36, + "maximumLabelOverrunUnit" : "Point", + "minimumFeatureSizeUnit" : "Map", + "multiPartOption" : "OneLabelPerPart", + "offsetAlongLineProperties" : { + "type" : "CIMMaplexOffsetAlongLineProperties", + "placementMethod" : "BestPositionAlongLine", + "labelAnchorPoint" : "CenterOfLabel", + "distanceUnit" : "Percentage", + "useLineDirection" : true + }, + "pointExternalZonePriorities" : { + "type" : "CIMMaplexExternalZonePriorities", + "aboveLeft" : 4, + "aboveCenter" : 2, + "aboveRight" : 1, + "centerRight" : 3, + "belowRight" : 5, + "belowCenter" : 7, + "belowLeft" : 8, + "centerLeft" : 6 + }, + "pointPlacementMethod" : "AroundPoint", + "polygonAnchorPointType" : "GeometricCenter", + "polygonBoundaryWeight" : 0, + "polygonExternalZones" : { + "type" : "CIMMaplexExternalZonePriorities", + "aboveLeft" : 4, + "aboveCenter" : 2, + "aboveRight" : 1, + "centerRight" : 3, + "belowRight" : 5, + "belowCenter" : 7, + "belowLeft" : 8, + "centerLeft" : 6 + }, + "polygonFeatureType" : "General", + "polygonInternalZones" : { + "type" : "CIMMaplexInternalZonePriorities", + "center" : 1 + }, + "polygonPlacementMethod" : "CurvedInPolygon", + "primaryOffset" : 1, + "primaryOffsetUnit" : "Point", + "removeExtraWhiteSpace" : true, + "repetitionIntervalUnit" : "Map", + "rotationProperties" : { + "type" : "CIMMaplexRotationProperties", + "rotationType" : "Arithmetic", + "alignmentType" : "Straight" + }, + "secondaryOffset" : 100, + "strategyPriorities" : { + "type" : "CIMMaplexStrategyPriorities", + "stacking" : 1, + "overrun" : 2, + "fontCompression" : 3, + "fontReduction" : 4, + "abbreviation" : 5 + }, + "thinningDistanceUnit" : "Point", + "truncationMarkerCharacter" : ".", + "truncationMinimumLength" : 1, + "truncationPreferredCharacters" : "aeiou" + }, + "name" : "Classe\u00A01", + "priority" : -1, + "standardLabelPlacementProperties" : { + "type" : "CIMStandardLabelPlacementProperties", + "featureType" : "Line", + "featureWeight" : "Low", + "labelWeight" : "High", + "numLabelsOption" : "OneLabelPerName", + "lineLabelPosition" : { + "type" : "CIMStandardLineLabelPosition", + "above" : true, + "inLine" : true, + "parallel" : true + }, + "lineLabelPriorities" : { + "type" : "CIMStandardLineLabelPriorities", + "aboveStart" : 3, + "aboveAlong" : 3, + "aboveEnd" : 3, + "centerStart" : 3, + "centerAlong" : 3, + "centerEnd" : 3, + "belowStart" : 3, + "belowAlong" : 3, + "belowEnd" : 3 + }, + "pointPlacementMethod" : "AroundPoint", + "pointPlacementPriorities" : { + "type" : "CIMStandardPointPlacementPriorities", + "aboveLeft" : 2, + "aboveCenter" : 2, + "aboveRight" : 1, + "centerLeft" : 3, + "centerRight" : 2, + "belowLeft" : 3, + "belowCenter" : 3, + "belowRight" : 2 + }, + "rotationType" : "Arithmetic", + "polygonPlacementMethod" : "AlwaysHorizontal" + }, + "textSymbol" : { + "type" : "CIMSymbolReference", + "symbol" : { + "type" : "CIMTextSymbol", + "blockProgression" : "TTB", + "depth3D" : 1, + "extrapolateBaselines" : true, + "fontEffects" : "Normal", + "fontEncoding" : "Unicode", + "fontFamilyName" : "Tahoma", + "fontStyleName" : "Regular", + "fontType" : "Unspecified", + "haloSize" : 1, + "height" : 10, + "hinting" : "Default", + "horizontalAlignment" : "Left", + "kerning" : true, + "letterWidth" : 100, + "ligatures" : true, + "lineGapType" : "ExtraLeading", + "symbol" : { + "type" : "CIMPolygonSymbol", + "symbolLayers" : [ + { + "type" : "CIMSolidFill", + "enable" : true, + "color" : { + "type" : "CIMRGBColor", + "values" : [ + 0, + 0, + 0, + 100 + ] + } + } + ] + }, + "textCase" : "Normal", + "textDirection" : "LTR", + "verticalAlignment" : "Bottom", + "verticalGlyphOrientation" : "Right", + "wordSpacing" : 100, + "billboardMode3D" : "FaceNearPlane" + } + }, + "useCodedValue" : true, + "visibility" : true, + "iD" : -1 + } + ], + "renderer" : { + "type" : "CIMUniqueValueRenderer", + "colorRamp" : { + "type" : "CIMRandomHSVColorRamp", + "colorSpace" : { + "type" : "CIMICCColorSpace", + "url" : "Default RGB" + }, + "maxH" : 360, + "minS" : 15, + "maxS" : 30, + "minV" : 99, + "maxV" : 100, + "minAlpha" : 100, + "maxAlpha" : 100 + }, + "defaultLabel" : "", + "defaultSymbol" : { + "type" : "CIMSymbolReference", + "symbol" : { + "type" : "CIMPointSymbol", + "symbolLayers" : [ + { + "type" : "CIMVectorMarker", + "enable" : true, + "anchorPoint" : { + "x" : 0, + "y" : 0, + "z" : 0 + }, + "anchorPointUnits" : "Relative", + "dominantSizeAxis3D" : "Y", + "size" : 10, + "billboardMode3D" : "FaceNearPlane", + "frame" : { + "xmin" : 0, + "ymin" : 0, + "xmax" : 17, + "ymax" : 17 + }, + "markerGraphics" : [ + { + "type" : "CIMMarkerGraphic", + "geometry" : { + "rings" : [ + [ + [ + 13.35, + 8.48000000000000043 + ], + [ + 8.5, + 0 + ], + [ + 3.64999999999999991, + 8.48000000000000043 + ], + [ + 8.5, + 17 + ], + [ + 13.35, + 8.48000000000000043 + ] + ] + ] + }, + "symbol" : { + "type" : "CIMPolygonSymbol", + "symbolLayers" : [ + { + "type" : "CIMSolidStroke", + "enable" : true, + "capStyle" : "Round", + "joinStyle" : "Round", + "lineStyle3D" : "Strip", + "miterLimit" : 10, + "width" : 1, + "color" : { + "type" : "CIMRGBColor", + "values" : [ + 0, + 0, + 0, + 100 + ] + } + }, + { + "type" : "CIMSolidFill", + "enable" : true, + "color" : { + "type" : "CIMRGBColor", + "values" : [ + 115, + 223, + 255, + 100 + ] + } + } + ] + } + } + ], + "scaleSymbolsProportionally" : true, + "respectFrame" : true + } + ], + "haloSize" : 1, + "scaleX" : 1, + "angleAlignment" : "Display" + } + }, + "defaultSymbolPatch" : "Default", + "fields" : [ + "TYPE" + ], + "groups" : [ + { + "type" : "CIMUniqueValueGroup", + "classes" : [ + { + "type" : "CIMUniqueValueClass", + "editable" : true, + "label" : "RCCC", + "patch" : "Default", + "symbol" : { + "type" : "CIMSymbolReference", + "symbol" : { + "type" : "CIMPointSymbol", + "symbolLayers" : [ + { + "type" : "CIMVectorMarker", + "enable" : true, + "anchorPointUnits" : "Relative", + "dominantSizeAxis3D" : "Z", + "size" : 7, + "billboardMode3D" : "FaceNearPlane", + "frame" : { + "xmin" : -2, + "ymin" : -2, + "xmax" : 2, + "ymax" : 2 + }, + "markerGraphics" : [ + { + "type" : "CIMMarkerGraphic", + "geometry" : { + "curveRings" : [ + [ + [ + 0, + 2 + ], + { + "a" : [ + [ + 0, + 2 + ], + [ + 5.091292279098724e-16, + 0 + ], + 0, + 1 + ] + } + ] + ] + }, + "symbol" : { + "type" : "CIMPolygonSymbol", + "symbolLayers" : [ + { + "type" : "CIMSolidStroke", + "enable" : true, + "capStyle" : "Round", + "joinStyle" : "Round", + "lineStyle3D" : "Strip", + "miterLimit" : 10, + "width" : 1.2249999999999999, + "color" : { + "type" : "CIMRGBColor", + "values" : [ + 0, + 0, + 0, + 100 + ] + } + }, + { + "type" : "CIMSolidFill", + "enable" : true, + "color" : { + "type" : "CIMRGBColor", + "values" : [ + 169, + 0, + 230, + 100 + ] + } + } + ] + } + } + ], + "respectFrame" : true + } + ], + "haloSize" : 1, + "scaleX" : 1, + "angleAlignment" : "Display" + } + }, + "values" : [ + { + "type" : "CIMUniqueValue", + "fieldValues" : [ + "RCCC" + ] + } + ], + "visible" : true + }, + { + "type" : "CIMUniqueValueClass", + "editable" : true, + "label" : "RCIC", + "patch" : "Default", + "symbol" : { + "type" : "CIMSymbolReference", + "symbol" : { + "type" : "CIMPointSymbol", + "symbolLayers" : [ + { + "type" : "CIMVectorMarker", + "enable" : true, + "anchorPointUnits" : "Relative", + "dominantSizeAxis3D" : "Z", + "size" : 7, + "billboardMode3D" : "FaceNearPlane", + "frame" : { + "xmin" : -2, + "ymin" : -2, + "xmax" : 2, + "ymax" : 2 + }, + "markerGraphics" : [ + { + "type" : "CIMMarkerGraphic", + "geometry" : { + "curveRings" : [ + [ + [ + 0, + 2 + ], + { + "a" : [ + [ + 0, + 2 + ], + [ + 5.091292279098724e-16, + 0 + ], + 0, + 1 + ] + } + ] + ] + }, + "symbol" : { + "type" : "CIMPolygonSymbol", + "symbolLayers" : [ + { + "type" : "CIMSolidStroke", + "enable" : true, + "capStyle" : "Round", + "joinStyle" : "Round", + "lineStyle3D" : "Strip", + "miterLimit" : 10, + "width" : 1.2249999999999999, + "color" : { + "type" : "CIMRGBColor", + "values" : [ + 0, + 0, + 0, + 100 + ] + } + }, + { + "type" : "CIMSolidFill", + "enable" : true, + "color" : { + "type" : "CIMRGBColor", + "values" : [ + 0, + 92, + 230, + 100 + ] + } + } + ] + } + } + ], + "respectFrame" : true + } + ], + "haloSize" : 1, + "scaleX" : 1, + "angleAlignment" : "Display" + } + }, + "values" : [ + { + "type" : "CIMUniqueValue", + "fieldValues" : [ + "RCIC" + ] + } + ], + "visible" : true + }, + { + "type" : "CIMUniqueValueClass", + "editable" : true, + "label" : "TEIC", + "patch" : "Default", + "symbol" : { + "type" : "CIMSymbolReference", + "symbol" : { + "type" : "CIMPointSymbol", + "symbolLayers" : [ + { + "type" : "CIMVectorMarker", + "enable" : true, + "anchorPoint" : { + "x" : 0, + "y" : 0, + "z" : 0 + }, + "anchorPointUnits" : "Relative", + "dominantSizeAxis3D" : "Y", + "size" : 10, + "billboardMode3D" : "FaceNearPlane", + "frame" : { + "xmin" : 0, + "ymin" : 0, + "xmax" : 17, + "ymax" : 17 + }, + "markerGraphics" : [ + { + "type" : "CIMMarkerGraphic", + "geometry" : { + "rings" : [ + [ + [ + 0, + 17 + ], + [ + 17, + 17 + ], + [ + 17, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 17 + ] + ] + ] + }, + "symbol" : { + "type" : "CIMPolygonSymbol", + "symbolLayers" : [ + { + "type" : "CIMSolidStroke", + "enable" : true, + "capStyle" : "Round", + "joinStyle" : "Round", + "lineStyle3D" : "Strip", + "miterLimit" : 10, + "width" : 1, + "color" : { + "type" : "CIMRGBColor", + "values" : [ + 0, + 0, + 0, + 100 + ] + } + }, + { + "type" : "CIMSolidFill", + "enable" : true, + "color" : { + "type" : "CIMRGBColor", + "values" : [ + 230, + 0, + 0, + 100 + ] + } + } + ] + } + } + ], + "scaleSymbolsProportionally" : true, + "respectFrame" : true + } + ], + "haloSize" : 1, + "scaleX" : 1, + "angleAlignment" : "Display" + } + }, + "values" : [ + { + "type" : "CIMUniqueValue", + "fieldValues" : [ + "TEIC" + ] + } + ], + "visible" : true + }, + { + "type" : "CIMUniqueValueClass", + "editable" : true, + "label" : "TACC", + "patch" : "Default", + "symbol" : { + "type" : "CIMSymbolReference", + "symbol" : { + "type" : "CIMPointSymbol", + "symbolLayers" : [ + { + "type" : "CIMVectorMarker", + "enable" : true, + "anchorPoint" : { + "x" : 0, + "y" : 0, + "z" : 0 + }, + "anchorPointUnits" : "Relative", + "dominantSizeAxis3D" : "Y", + "size" : 10, + "billboardMode3D" : "FaceNearPlane", + "frame" : { + "xmin" : 0, + "ymin" : 0, + "xmax" : 17, + "ymax" : 17 + }, + "markerGraphics" : [ + { + "type" : "CIMMarkerGraphic", + "geometry" : { + "rings" : [ + [ + [ + 0, + 0 + ], + [ + 8.60999999999999943, + 14.85 + ], + [ + 17, + 0 + ], + [ + 0, + 0 + ] + ] + ] + }, + "symbol" : { + "type" : "CIMPolygonSymbol", + "symbolLayers" : [ + { + "type" : "CIMSolidStroke", + "enable" : true, + "capStyle" : "Round", + "joinStyle" : "Round", + "lineStyle3D" : "Strip", + "miterLimit" : 10, + "width" : 1, + "color" : { + "type" : "CIMRGBColor", + "values" : [ + 0, + 0, + 0, + 100 + ] + } + }, + { + "type" : "CIMSolidFill", + "enable" : true, + "color" : { + "type" : "CIMRGBColor", + "values" : [ + 230, + 0, + 0, + 100 + ] + } + } + ] + } + } + ], + "scaleSymbolsProportionally" : true, + "respectFrame" : true + } + ], + "haloSize" : 1, + "scaleX" : 1, + "angleAlignment" : "Display" + } + }, + "values" : [ + { + "type" : "CIMUniqueValue", + "fieldValues" : [ + "TACC" + ] + } + ], + "visible" : true + }, + { + "type" : "CIMUniqueValueClass", + "editable" : true, + "label" : "TEIE", + "patch" : "Default", + "symbol" : { + "type" : "CIMSymbolReference", + "symbol" : { + "type" : "CIMPointSymbol", + "symbolLayers" : [ + { + "type" : "CIMVectorMarker", + "enable" : true, + "anchorPoint" : { + "x" : 0, + "y" : 0, + "z" : 0 + }, + "anchorPointUnits" : "Relative", + "dominantSizeAxis3D" : "Y", + "size" : 10, + "billboardMode3D" : "FaceNearPlane", + "frame" : { + "xmin" : 0, + "ymin" : 0, + "xmax" : 17, + "ymax" : 17 + }, + "markerGraphics" : [ + { + "type" : "CIMMarkerGraphic", + "geometry" : { + "rings" : [ + [ + [ + 0, + 17 + ], + [ + 17, + 17 + ], + [ + 17, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 17 + ] + ] + ] + }, + "symbol" : { + "type" : "CIMPolygonSymbol", + "symbolLayers" : [ + { + "type" : "CIMSolidStroke", + "enable" : true, + "capStyle" : "Round", + "joinStyle" : "Round", + "lineStyle3D" : "Strip", + "miterLimit" : 10, + "width" : 1, + "color" : { + "type" : "CIMRGBColor", + "values" : [ + 0, + 0, + 0, + 100 + ] + } + }, + { + "type" : "CIMSolidFill", + "enable" : true, + "color" : { + "type" : "CIMRGBColor", + "values" : [ + 85, + 255, + 0, + 100 + ] + } + } + ] + } + } + ], + "scaleSymbolsProportionally" : true, + "respectFrame" : true + } + ], + "haloSize" : 1, + "scaleX" : 1, + "angleAlignment" : "Display" + } + }, + "values" : [ + { + "type" : "CIMUniqueValue", + "fieldValues" : [ + "TEIE" + ] + } + ], + "visible" : true + }, + { + "type" : "CIMUniqueValueClass", + "editable" : true, + "label" : "TACE", + "patch" : "Default", + "symbol" : { + "type" : "CIMSymbolReference", + "symbol" : { + "type" : "CIMPointSymbol", + "symbolLayers" : [ + { + "type" : "CIMVectorMarker", + "enable" : true, + "anchorPoint" : { + "x" : 0, + "y" : 0, + "z" : 0 + }, + "anchorPointUnits" : "Relative", + "dominantSizeAxis3D" : "Y", + "size" : 10, + "billboardMode3D" : "FaceNearPlane", + "frame" : { + "xmin" : 0, + "ymin" : 0, + "xmax" : 17, + "ymax" : 17 + }, + "markerGraphics" : [ + { + "type" : "CIMMarkerGraphic", + "geometry" : { + "rings" : [ + [ + [ + 0, + 0 + ], + [ + 8.60999999999999943, + 14.85 + ], + [ + 17, + 0 + ], + [ + 0, + 0 + ] + ] + ] + }, + "symbol" : { + "type" : "CIMPolygonSymbol", + "symbolLayers" : [ + { + "type" : "CIMSolidStroke", + "enable" : true, + "capStyle" : "Round", + "joinStyle" : "Round", + "lineStyle3D" : "Strip", + "miterLimit" : 10, + "width" : 1, + "color" : { + "type" : "CIMRGBColor", + "values" : [ + 0, + 0, + 0, + 100 + ] + } + }, + { + "type" : "CIMSolidFill", + "enable" : true, + "color" : { + "type" : "CIMRGBColor", + "values" : [ + 85, + 255, + 0, + 100 + ] + } + } + ] + } + } + ], + "scaleSymbolsProportionally" : true, + "respectFrame" : true + } + ], + "haloSize" : 1, + "scaleX" : 1, + "angleAlignment" : "Display" + } + }, + "values" : [ + { + "type" : "CIMUniqueValue", + "fieldValues" : [ + "TACE" + ] + } + ], + "visible" : true + }, + { + "type" : "CIMUniqueValueClass", + "editable" : true, + "label" : "FDIE", + "patch" : "Default", + "symbol" : { + "type" : "CIMSymbolReference", + "symbol" : { + "type" : "CIMPointSymbol", + "symbolLayers" : [ + { + "type" : "CIMVectorMarker", + "enable" : true, + "anchorPoint" : { + "x" : 0, + "y" : 0, + "z" : 0 + }, + "anchorPointUnits" : "Relative", + "dominantSizeAxis3D" : "Y", + "size" : 10, + "billboardMode3D" : "FaceNearPlane", + "frame" : { + "xmin" : 0, + "ymin" : 0, + "xmax" : 17, + "ymax" : 17 + }, + "markerGraphics" : [ + { + "type" : "CIMMarkerGraphic", + "geometry" : { + "rings" : [ + [ + [ + 0, + 17 + ], + [ + 17, + 17 + ], + [ + 17, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 17 + ] + ] + ] + }, + "symbol" : { + "type" : "CIMPolygonSymbol", + "symbolLayers" : [ + { + "type" : "CIMSolidStroke", + "enable" : true, + "capStyle" : "Round", + "joinStyle" : "Round", + "lineStyle3D" : "Strip", + "miterLimit" : 10, + "width" : 1, + "color" : { + "type" : "CIMRGBColor", + "values" : [ + 0, + 0, + 0, + 100 + ] + } + }, + { + "type" : "CIMSolidFill", + "enable" : true, + "color" : { + "type" : "CIMRGBColor", + "values" : [ + 168, + 112, + 0, + 100 + ] + } + } + ] + } + } + ], + "scaleSymbolsProportionally" : true, + "respectFrame" : true + } + ], + "haloSize" : 1, + "scaleX" : 1, + "angleAlignment" : "Display" + } + }, + "values" : [ + { + "type" : "CIMUniqueValue", + "fieldValues" : [ + "FDIE" + ] + } + ], + "visible" : true + }, + { + "type" : "CIMUniqueValueClass", + "editable" : true, + "label" : "RACC", + "patch" : "Default", + "symbol" : { + "type" : "CIMSymbolReference", + "symbol" : { + "type" : "CIMPointSymbol", + "symbolLayers" : [ + { + "type" : "CIMVectorMarker", + "enable" : true, + "anchorPoint" : { + "x" : 0, + "y" : 0, + "z" : 0 + }, + "anchorPointUnits" : "Relative", + "dominantSizeAxis3D" : "Y", + "size" : 10, + "billboardMode3D" : "FaceNearPlane", + "frame" : { + "xmin" : 0, + "ymin" : 0, + "xmax" : 17, + "ymax" : 17 + }, + "markerGraphics" : [ + { + "type" : "CIMMarkerGraphic", + "geometry" : { + "rings" : [ + [ + [ + 0, + 0 + ], + [ + 8.60999999999999943, + 14.85 + ], + [ + 17, + 0 + ], + [ + 0, + 0 + ] + ] + ] + }, + "symbol" : { + "type" : "CIMPolygonSymbol", + "symbolLayers" : [ + { + "type" : "CIMSolidStroke", + "enable" : true, + "capStyle" : "Round", + "joinStyle" : "Round", + "lineStyle3D" : "Strip", + "miterLimit" : 10, + "width" : 1, + "color" : { + "type" : "CIMRGBColor", + "values" : [ + 0, + 0, + 0, + 100 + ] + } + }, + { + "type" : "CIMSolidFill", + "enable" : true, + "color" : { + "type" : "CIMRGBColor", + "values" : [ + 168, + 112, + 0, + 100 + ] + } + } + ] + } + } + ], + "scaleSymbolsProportionally" : true, + "respectFrame" : true + } + ], + "haloSize" : 1, + "scaleX" : 1, + "angleAlignment" : "Display" + } + }, + "values" : [ + { + "type" : "CIMUniqueValue", + "fieldValues" : [ + "RACC" + ] + } + ], + "visible" : true + }, + { + "type" : "CIMUniqueValueClass", + "editable" : true, + "label" : "", + "patch" : "Default", + "symbol" : { + "type" : "CIMSymbolReference", + "symbol" : { + "type" : "CIMPointSymbol", + "symbolLayers" : [ + { + "type" : "CIMVectorMarker", + "enable" : true, + "anchorPoint" : { + "x" : 0, + "y" : 0, + "z" : 0 + }, + "anchorPointUnits" : "Relative", + "dominantSizeAxis3D" : "Y", + "size" : 10, + "billboardMode3D" : "FaceNearPlane", + "frame" : { + "xmin" : 0, + "ymin" : 0, + "xmax" : 17, + "ymax" : 17 + }, + "markerGraphics" : [ + { + "type" : "CIMMarkerGraphic", + "geometry" : { + "rings" : [ + [ + [ + 6, + 11 + ], + [ + 5.98000000000000043, + 17 + ], + [ + 10.98, + 17 + ], + [ + 11, + 11 + ], + [ + 17, + 11 + ], + [ + 17, + 6 + ], + [ + 11, + 6 + ], + [ + 11, + 0 + ], + [ + 6, + 0 + ], + [ + 6, + 6 + ], + [ + 0, + 5.98000000000000043 + ], + [ + 0, + 10.98 + ], + [ + 6, + 11 + ] + ] + ] + }, + "symbol" : { + "type" : "CIMPolygonSymbol", + "symbolLayers" : [ + { + "type" : "CIMSolidStroke", + "enable" : true, + "capStyle" : "Round", + "joinStyle" : "Round", + "lineStyle3D" : "Strip", + "miterLimit" : 10, + "width" : 1, + "color" : { + "type" : "CIMRGBColor", + "values" : [ + 0, + 0, + 0, + 100 + ] + } + }, + { + "type" : "CIMSolidFill", + "enable" : true, + "color" : { + "type" : "CIMRGBColor", + "values" : [ + 255, + 255, + 0, + 100 + ] + } + } + ] + } + } + ], + "scaleSymbolsProportionally" : true, + "respectFrame" : true + } + ], + "haloSize" : 1, + "scaleX" : 1, + "angleAlignment" : "Display" + } + }, + "values" : [ + { + "type" : "CIMUniqueValue", + "fieldValues" : [ + "" + ] + } + ], + "visible" : true + } + ], + "heading" : "TYPE" + } + ], + "useDefaultSymbol" : true, + "polygonSymbolColorTarget" : "Fill" + }, + "scaleSymbols" : true, + "snappable" : true, + "symbolLayerDrawing" : { + "type" : "CIMSymbolLayerDrawing" + } + } + ], + "elevationSurfaces" : [ + { + "type" : "CIMMapElevationSurface", + "elevationMode" : "BaseGlobeSurface", + "name" : "Sol", + "verticalExaggeration" : 1, + "mapElevationID" : "{CC73DA4E-A2D2-4B9E-91FB-5DC812875CDD}", + "color" : { + "type" : "CIMRGBColor", + "values" : [ + 255, + 255, + 255, + 100 + ] + }, + "surfaceTINShadingMode" : "Smooth", + "visibility" : true, + "expanded" : true + } + ] +} \ No newline at end of file diff --git a/VISA_BANBOU.xlsx b/VISA_BANBOU.xlsx new file mode 100644 index 0000000..2499dfd Binary files /dev/null and b/VISA_BANBOU.xlsx differ