pyCreateTh

This commit is contained in:
Alex38Lyon
2025-06-24 09:11:26 +02:00
parent 0f2a5c9d73
commit b6d4a2dc47
11 changed files with 256 additions and 105 deletions
+13
View File
@@ -11,3 +11,16 @@
2025-06-20 17:00:03,470 - INFO - ******************************************************************************************************************************************** 2025-06-20 17:00:03,470 - INFO - ********************************************************************************************************************************************
2025-06-20 17:00:03,472 - ERROR - !!! file not yet supported 2025-06-20 17:00:03,472 - ERROR - !!! file not yet supported
2025-06-20 17:00:03,472 - ERROR - !!! There were 1 errors during 7.63 secondes, check the log file: pyCreateTh.log 2025-06-20 17:00:03,472 - ERROR - !!! There were 1 errors during 7.63 secondes, check the log file: pyCreateTh.log
2025-06-23 12:45:03,041 - INFO - ********************************************************************************************************************************************
2025-06-23 12:45:03,043 - INFO - * Conversion Th, Dat, Mak files to Therion files and folders
2025-06-23 12:45:03,043 - INFO - * Script pyCreateTh by : alexandre.pont@yahoo.fr
2025-06-23 12:45:03,044 - INFO - * Version : 2025.06.23
2025-06-23 12:45:03,044 - INFO - * Input file :
2025-06-23 12:45:03,044 - INFO - * Output file : ~\.
2025-06-23 12:45:03,044 - INFO - * Log file : ~\..\pyCreateTh.log
2025-06-23 12:45:03,045 - INFO - *
2025-06-23 12:45:03,045 - INFO - *
2025-06-23 12:45:03,045 - INFO - *
2025-06-23 12:45:03,045 - INFO - ********************************************************************************************************************************************
2025-06-23 12:45:03,046 - ERROR - !!! file not yet supported
2025-06-23 12:45:03,047 - ERROR - !!! There were 1 errors during 4.73 secondes, check the log file: pyCreateTh.log
+16
View File
@@ -0,0 +1,16 @@
{
// Utilisez IntelliSense pour en savoir plus sur les attributs possibles.
// Pointez pour afficher la description des attributs existants.
// Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{"name":"Débogueur Python : Fichier actuel","type":"debugpy","request":"launch","program":"${file}","console":"integratedTerminal"},
{
"name": "Débogueur Python : Fichier actuel",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal"
}
]
}
+2
View File
@@ -52,6 +52,7 @@
"northamericandatum", "northamericandatum",
"nouvelletriangulationfrançaise", "nouvelletriangulationfrançaise",
"pulkovo", "pulkovo",
"resultats",
"sinfo", "sinfo",
"sirgas", "sirgas",
"southamerican", "southamerican",
@@ -62,6 +63,7 @@
"thconfig", "thconfig",
"therion", "therion",
"totdata", "totdata",
"totfile",
"UUUUDDDDSSSB", "UUUUDDDDSSSB",
"UUUUDDDDSSSBL", "UUUUDDDDSSSBL",
"UUUUDDDDSSSSSBL", "UUUUDDDDSSSSSBL",
@@ -275,7 +275,6 @@ def setup_logger(logfile="app.log", debug_log=False):
return logger return logger
################################################################################################# #################################################################################################
def release_log_file(logger): def release_log_file(logger):
handlers = logger.handlers[:] handlers = logger.handlers[:]
+14
View File
@@ -30,6 +30,20 @@ linesInTh2 = -1
stationNamesInTh2 = -1 stationNamesInTh2 = -1
#################################################################################################
totfile = """\t## Survey file:
\tinput Data/{TH_NAME}.th
\t## Plan file:
\t{ERR}input Data/{TH_NAME}-Plan.th2
\t## Extended file:
\t{ERR}input Data/{TH_NAME}-Extended.th2
\t## Maps file:
\t{ERR}input {TH_NAME}-maps.th
"""
################################################################################################# #################################################################################################
thFileDat = """ thFileDat = """
+15 -16
View File
@@ -12,7 +12,6 @@ from Lib.general_fonctions import Colors, safe_relpath
log = logging.getLogger("Logger") log = logging.getLogger("Logger")
################################################################################################# #################################################################################################
# Compilation Therion 'Template' (version sans blocage) # # Compilation Therion 'Template' (version sans blocage) #
# Compiler une configuration générée dynamiquement à partir d'un template texte. # # Compiler une configuration générée dynamiquement à partir d'un template texte. #
@@ -94,7 +93,7 @@ def compile_file(filename, **kwargs):
therion_path = kwargs.get("therion_path", "therion") therion_path = kwargs.get("therion_path", "therion")
timeout = kwargs.get("timeout", 240) timeout = kwargs.get("timeout", 240)
log.info(f"Start therion compilation file: {Colors.ENDC}{safe_relpath(filename)}") log.info(f"Start therion [Therion Compile {Colors.WHITE}{os.path.basename(filename)[:-9]}{Colors.GREEN}], compilation file")
def run(): def run():
try: try:
@@ -113,22 +112,22 @@ def compile_file(filename, **kwargs):
line = line.rstrip() line = line.rstrip()
lower_line = line.lower() lower_line = line.lower()
if "average loop error" in lower_line: if "average loop error" in lower_line:
log.warning(f"[Therion Compile {os.path.basename(filename)[:-9]}] {Colors.ENDC}{line}") log.warning(f"[Therion Compile {Colors.WHITE}{os.path.basename(filename)[:-9]}{Colors.WARNING}] {Colors.ENDC}{line}")
elif "error" in lower_line: elif "error" in lower_line:
log.error(f"[Therion_Compile {os.path.basename(filename)[:-9]}] {Colors.ENDC}{line}") log.error(f"[Therion_Compile {Colors.WHITE}{os.path.basename(filename)[:-9]}{Colors.ERROR}] {Colors.ENDC}{line}")
elif "warning" in lower_line: elif "warning" in lower_line:
log.warning(f"[Therion Compile {os.path.basename(filename)[:-9]}] {Colors.ENDC}{line}") log.warning(f"[Therion Compile {Colors.WHITE}{os.path.basename(filename)[:-9]}{Colors.WARNING}] {Colors.ENDC}{line}")
else: else:
log.debug(f"[Therion Compile {os.path.basename(filename)[:-9]}] {Colors.ENDC}{line}") log.debug(f"[Therion Compile {Colors.WHITE}{os.path.basename(filename)[:-9]}{Colors.DEBUG}] {Colors.ENDC}{line}")
except Exception as e: except Exception as e:
log.warning(f"Reading Therion output: {Colors.ENDC}{e}") log.warning(f"Reading Therion [Therion_Compile {Colors.WHITE}{os.path.basename(filename)[:-9]}{Colors.WARNING}], output: {Colors.ENDC}{e}")
output_thread = threading.Thread(target=read_output, args=(process,)) output_thread = threading.Thread(target=read_output, args=(process,))
output_thread.start() output_thread.start()
output_thread.join(timeout) output_thread.join(timeout)
if output_thread.is_alive(): if output_thread.is_alive():
log.error(f"Therion compilation timed out after {Colors.ENDC}{timeout}{Colors.ERROR} seconds. Killing process...") log.error(f"Therion compilation [Therion_Compile {Colors.WHITE}{os.path.basename(filename)[:-9]}{Colors.ERROR}], timed out after {Colors.ENDC}{timeout}{Colors.ERROR} seconds. Killing process...")
global_data.error_count += 1 global_data.error_count += 1
process.kill() process.kill()
@@ -136,20 +135,22 @@ def compile_file(filename, **kwargs):
process.wait() process.wait()
if process.returncode != 0: if process.returncode != 0:
log.error(f"Therion returned error code {Colors.ENDC}{process.returncode}") log.error(f"Therion [Therion_Compile {Colors.WHITE}{os.path.basename(filename)[:-9]}{Colors.ERROR}], returned error code {Colors.ENDC}{process.returncode}")
global_data.error_count += 1 global_data.error_count += 1
else: else:
log.info(f"Therion file: {Colors.ENDC}{safe_relpath(filename)}{Colors.GREEN} compilation succeeded") # stat = get_stats_from_log(log_file)
log.info(f"Therion file: [Therion Compile {Colors.WHITE}{os.path.basename(filename)[:-9]}{Colors.GREEN}], compilation succeeded")
except Exception as e: except Exception as e:
log.error(f"Therion file: {Colors.ENDC}{safe_relpath(filename)}{Colors.ERROR} compilation error: {Colors.ENDC}{e}") log.error(f"Therion file: [Therion Compile {Colors.WHITE}{os.path.basename(filename)[:-9]}{Colors.ERROR}], compilation error: {Colors.ENDC}{e}")
global_data.error_count += 1 global_data.error_count += 1
# Lancer le thread principal pour cette compilation et le retourner # Lancer le thread principal pour cette compilation et le retourner
thread = threading.Thread(target=run) thread = threading.Thread(target=run)
thread.start()
return thread
thread.start()
return thread
################################################################################################# #################################################################################################
@@ -170,10 +171,9 @@ lengthre = re.compile(r".*Longueur totale de la topographie = \s*(\S+)m")
depthre = re.compile(r".*Longueur totale verticale =\s*(\S+)m") depthre = re.compile(r".*Longueur totale verticale =\s*(\S+)m")
def get_stats_from_log(log): def get_stats_from_log(log):
lenmatch = lengthre.findall(log) lenmatch = lengthre.findall(log)
depmatch = depthre.findall(log) depmatch = depthre.findall(log)
if len(lenmatch) == 1 and len(depmatch) == 1: if len(lenmatch) == 1 and len(depmatch) == 1:
return {"length": lenmatch[0], "depth": depmatch[0]} return {"length": lenmatch[0], "depth": depmatch[0]}
return {"length": 0, "depth": 0} return {"length": 0, "depth": 0}
@@ -182,7 +182,6 @@ def get_stats_from_log(log):
################################################################################################# #################################################################################################
syscoord = re.compile(r".*output coordinate system: \s*(\S+)") syscoord = re.compile(r".*output coordinate system: \s*(\S+)")
def get_syscoord_from_log(log): def get_syscoord_from_log(log):
lenmatch = syscoord.findall(log) lenmatch = syscoord.findall(log)
View File
+192 -84
View File
@@ -21,28 +21,25 @@ Version 2025 06 16 : Création fonction create_th_folders
En cours : En cours :
- gérer les visées orphelines dans une même survey
- gérer les updates (th, dat, mak) - gérer les updates (th, dat, mak)
- créer fonction pour faire habillage des th2 files, les jointures... - créer fonction pour faire habillage des th2 files, les jointures...
- reprendre l'option shot lines dans les th2 files pour supprimer les splays. - reprendre l'option shot lines dans les th2 files pour supprimer les splays.
- créer l'option wall shot lines dans les th2 files. - créer l'option wall shot lines dans les th2 files.
- reprendre les options en ligne de commande, tester - reprendre les options en ligne de commande, tester
- trouver une solution pour les teams et les clubs manquants - trouver une solution pour les teams et les clubs manquants
- gérer le cas ou il y a 2 SurveyTitle identiques
- tester la nouvelle version de DAT (CORRECTION2 et suivants) - tester la nouvelle version de DAT (CORRECTION2 et suivants)
- tester différents cas et versions de dat et mak
- pb de equate avec points non valides (non répétable ?)
- vérifier le fonctionnement de merge survey (les valeurs compilées et les critères de compilation... voir )
- comparer résultats Therion - Compass (Stat, kml, etc....) - comparer résultats Therion - Compass (Stat, kml, etc....)
- intégrer .tro files d'après XRo
""" """
Version ="2025.06.18" Version ="2025.06.24"
################################################################################################# #################################################################################################
################################################################################################# #################################################################################################
import os, re, unicodedata, argparse, shutil, sys, time import os, re, unicodedata, argparse, shutil, sys, time, math
from os.path import isfile, join, abspath, splitext from os.path import isfile, join, abspath, splitext
import pandas as pd import pandas as pd
pd.set_option('future.no_silent_downcasting', True) pd.set_option('future.no_silent_downcasting', True)
@@ -57,7 +54,6 @@ from Lib.therion import compile_template, compile_file, get_stats_from_log
from Lib.general_fonctions import setup_logger, Colors, safe_relpath, colored_help, read_config, select_file_tk_window, release_log_file from Lib.general_fonctions import setup_logger, Colors, safe_relpath, colored_help, read_config, select_file_tk_window, release_log_file
import Lib.global_data as globalData import Lib.global_data as globalData
log = setup_logger(logfile="app.log", debug_log=True)
################################################################################################# #################################################################################################
configIni = "config.ini" # Default config file name configIni = "config.ini" # Default config file name
@@ -282,6 +278,36 @@ def str_to_bool(value):
else: else:
raise argparse.ArgumentTypeError(f"{Colors.ERROR}Error: Invalid boolean value: {Colors.ENDC}{value}") raise argparse.ArgumentTypeError(f"{Colors.ERROR}Error: Invalid boolean value: {Colors.ENDC}{value}")
#################################################################################################
def convert_to_line_polaire(lines):
line_polaire = []
for line in lines:
try:
x1 = float(line[0])
y1 = float(line[1])
x2 = float(line[2])
y2 = float(line[3])
name1 = line[4]
name2 = line[5]
dx = x2 - x1
dy = y2 - y1
# Longueur (distance)
length = math.hypot(dx, dy)
# Roth = azimut en degrés, 0° = Est, 90° = Nord
roth_rad = math.atan2(dy, dx)
roth_deg = (math.degrees(roth_rad)) % 360 # pour rester entre 0-360
line_polaire.append([x1, y1, roth_deg, length, name1, name2])
except Exception as e:
print(f"Erreur sur la ligne {line} : {e}")
return line_polaire
################################################################################################# #################################################################################################
def parse_xvi_file(th_name_xvi): def parse_xvi_file(th_name_xvi):
@@ -336,8 +362,82 @@ def parse_xvi_file(th_name_xvi):
station2 = stations[key2][2] if key2 in stations else None station2 = stations[key2][2] if key2 in stations else None
lines.append([x1, y1, x2, y2, station1, station2]) lines.append([x1, y1, x2, y2, station1, station2])
return stations, lines, x_min, x_max, y_min, y_max, x_ecart, y_ecart return stations, lines, x_min, x_max, y_min, y_max, x_ecart, y_ecart
#################################################################################################
def wall_construction(stations, lines):
station_names = {s[2] for s in stations.values()}
# Séparer lines en lignes valides et splays
splays = {}
filtered_lines = []
for i, line in enumerate(lines):
if line[4] in station_names and line[5] in station_names:
filtered_lines.append(line)
else:
splays[f"splay_{i}"] = line
# Conversion polaire
lines_polaire = convert_to_line_polaire(filtered_lines)
splays_polaire = convert_to_line_polaire(list(splays.values()))
# Index rapide des lignes polaires par nom de station (col. 4)
index_by_station = {
line[4]: idx for idx, line in enumerate(lines_polaire)
}
# Associer à chaque splay son index de ligne, et récupérer roth/length
splays_complet = []
for splay in splays_polaire:
station_name = splay[4]
idx = index_by_station.get(station_name)
if idx is not None:
roth_ref = lines_polaire[idx][2]
length_ref = lines_polaire[idx][3]
else:
roth_ref = length_ref = None
splays_complet.append(splay + [idx, roth_ref, length_ref])
# Ajouter sin(delta roth) * length_ref
for ligne in splays_complet:
roth_splay, roth_ref, length_ref = ligne[2], ligne[7], ligne[8]
if None not in (roth_splay, roth_ref, length_ref):
delta_rad = math.radians(roth_ref - roth_splay)
proj = math.sin(delta_rad) * length_ref
else:
proj = None
ligne.append(proj)
# Filtrer les extrêmes (min/max) par station (col. 4)
groupes = defaultdict(list)
for ligne in splays_complet:
station, proj = ligne[4], ligne[9]
if proj is not None:
groupes[station].append(ligne)
resultats = []
for station, lignes in groupes.items():
lignes_triees = sorted(lignes, key=lambda x: x[9])
resultats.append(lignes_triees[0]) # min
if lignes_triees[0] != lignes_triees[-1]: # max ≠ min
resultats.append(lignes_triees[-1]) # max
print(f"\nlines_polaires: {len(lines_polaire)}")
print(f"{lines_polaire}")
print(f"\nsplays_polaire: {len(splays_complet)}")
print(f"{splays_complet}")
print(f"\nresultats: {len(resultats)}")
print(f"{resultats}")
exit(0)
return resultats
################################################################################################# #################################################################################################
# Création des dossiers à partir d'un th file # # Création des dossiers à partir d'un th file #
################################################################################################# #################################################################################################
@@ -478,19 +578,10 @@ def create_th_folders(ENTRY_FILE,
################################################################################################# #################################################################################################
if UPDATE == "": if UPDATE == "":
totdata = globalData.totfile.format(
TH_NAME = TH_NAME,
ERR = "# " if flagErrorCompile else "" ERR = "# " if flagErrorCompile else ""
)
totdata = f"""\tinput Data/{TH_NAME}.th
\t## Pour le plan
\t{ERR}input Data/{TH_NAME}-Plan.th2
\t## Pour la coupe développée
\t{ERR}input Data/{TH_NAME}-Extended.th2
\t## Appel des maps
\t{ERR}input {TH_NAME}-maps.th
"""
# Adapte templates # Adapte templates
config_vars = { config_vars = {
@@ -533,6 +624,8 @@ def create_th_folders(ENTRY_FILE,
stations, lines, x_min, x_max, y_min, y_max, x_ecart, y_ecart = parse_xvi_file(th_name_xvi) stations, lines, x_min, x_max, y_min, y_max, x_ecart, y_ecart = parse_xvi_file(th_name_xvi)
# wall wall_construction(stations, lines)
if UPDATE == "th2": if UPDATE == "th2":
th2_name = DEST_PATH + "/" + TH_NAME th2_name = DEST_PATH + "/" + TH_NAME
else : else :
@@ -897,7 +990,7 @@ def mak_to_th_file(ENTRY_FILE) :
stationList = pd.DataFrame(columns=['StationName', 'Survey_Name_01', 'Survey_Name_02']) stationList = pd.DataFrame(columns=['StationName', 'Survey_Name_01', 'Survey_Name_02'])
totdata = f"\t## Liste inputs\n" totdata = f"\t## Input list:\n"
totMapsPlan = "" totMapsPlan = ""
totMapsExtended = "" totMapsExtended = ""
@@ -915,7 +1008,10 @@ def mak_to_th_file(ENTRY_FILE) :
with redirect_stdout(sys.__stdout__): with redirect_stdout(sys.__stdout__):
for file in datFiles: for file in datFiles:
bar.text(f"{Colors.INFO}file: {Colors.ENDC}{file}") if globalData.error_count > 0:
bar.text(f"{Colors.INFO}, file: {Colors.ENDC}{file[:-4]}{Colors.ERROR}, error: {Colors.ENDC}{globalData.error_count}")
else :
bar.text(f"{Colors.INFO}, file: {Colors.ENDC}{file[:-4]}")
_file = os.path.dirname(abspath(args.survey_file)) + "\\" + file _file = os.path.dirname(abspath(args.survey_file)) + "\\" + file
shutil.copy(_file, folderDest + "\\Data\\") shutil.copy(_file, folderDest + "\\Data\\")
@@ -933,8 +1029,9 @@ def mak_to_th_file(ENTRY_FILE) :
totMapsExtended += f"\tMC-{SurveyTitle}-Extended-tot@{SurveyTitle}\n\tbreak\n" totMapsExtended += f"\tMC-{SurveyTitle}-Extended-tot@{SurveyTitle}\n\tbreak\n"
if not Station.empty: if not Station.empty:
stationList = pd.concat([stationList, Station], ignore_index=True) __stationList = pd.concat([stationList, Station], ignore_index=True)
stationList.sort_values(by='Survey_Name_02', inplace=True, ignore_index=True) __stationList.sort_values(by='Survey_Name_02', inplace=True, ignore_index=True)
stationList = __stationList.copy()
destination = os.path.join(folderDest, "Sources", os.path.basename(ABS_file)) destination = os.path.join(folderDest, "Sources", os.path.basename(ABS_file))
if os.path.exists(destination): if os.path.exists(destination):
@@ -946,7 +1043,7 @@ def mak_to_th_file(ENTRY_FILE) :
################################################################################################# #################################################################################################
# Gestion des equats # Gestion des equates
################################################################################################# #################################################################################################
totdata +=f"\n" totdata +=f"\n"
@@ -966,17 +1063,16 @@ def mak_to_th_file(ENTRY_FILE) :
# print(f"tableau_pivot : {Colors.ENDC}{tableau_pivot}{Colors.INFO} in {Colors.ENDC}{args.survey_file}") # print(f"tableau_pivot : {Colors.ENDC}{tableau_pivot}{Colors.INFO} in {Colors.ENDC}{args.survey_file}")
totdata +=f"\n\t## Liste equates\n" totdata +=f"\n\t## Equates list:\n"
if 'Survey_Name_2' in tableau_pivot.columns: if 'Survey_Name_2' in tableau_pivot.columns:
# On réinitialise l'index pour avoir StationName comme colonne normale # On réinitialise l'index pour avoir StationName comme colonne normale
tableau_pivot = tableau_pivot.reset_index() tableau_pivot = tableau_pivot.reset_index()
tableau_equate = tableau_pivot[tableau_pivot['Survey_Name_2'].notna()] tableau_equate = tableau_pivot[tableau_pivot['Survey_Name_2'].notna()]
log.info(f"Total des 'equates' in mak file: {Colors.ENDC}{len(tableau_equate)}{Colors.INFO} in {Colors.ENDC}{safe_relpath(args.survey_file)}")
log.info(f"Total des 'equats' : {Colors.ENDC}{len(tableau_equate)}{Colors.INFO} in {Colors.ENDC}{safe_relpath(args.survey_file)}")
# print(tableau_equate) # print(tableau_equate)
# print(f"fixePoints : {Colors.ENDC}{fixed_names}{Colors.INFO} in {Colors.ENDC}{args.survey_file}") # print(f"fixPoints: {Colors.ENDC}{fixPoints}{Colors.INFO} in {Colors.ENDC}{args.survey_file}")
# Pour chaque ligne du tableau # Pour chaque ligne du tableau
for _, row in tableau_equate.iterrows(): for _, row in tableau_equate.iterrows():
@@ -994,7 +1090,7 @@ def mak_to_th_file(ENTRY_FILE) :
else: else:
log.info(f"No 'equats' found in {Colors.ENDC}{args.survey_file}") log.info(f"No 'equats' found in {Colors.ENDC}{args.survey_file}")
totdata +=f"\n\t## Appel des maps\n\tinput {SurveyTitleMak}-maps.th\n" totdata +=f"\n\t## Maps list:\n\tinput {SurveyTitleMak}-maps.th\n"
config_vars = { config_vars = {
'fileName': SurveyTitleMak, 'fileName': SurveyTitleMak,
@@ -1039,7 +1135,7 @@ def mak_to_th_file(ENTRY_FILE) :
################################################################################################# #################################################################################################
def station_list(data, list, fixPoints) : def station_list(data, list, fixPoints, currentSurveyName) :
""" """
Crée une liste de stations à partir des données fournies. Crée une liste de stations à partir des données fournies.
@@ -1065,7 +1161,7 @@ def station_list(data, list, fixPoints) :
new_entries = pd.DataFrame({ new_entries = pd.DataFrame({
'StationName': stations, 'StationName': stations,
'Survey_Name_01': data['SURVEY_NAME'] 'Survey_Name_01': currentSurveyName
}) })
list = pd.concat([list, new_entries], ignore_index=True) list = pd.concat([list, new_entries], ignore_index=True)
@@ -1341,54 +1437,58 @@ def find_duplicates_by_date_and_team(data):
duplicates = [] duplicates = []
# Étape 2 : chercher les entrées ayant des stations communes
for key, entries in grouped.items(): for key, entries in grouped.items():
if len(entries) < 2: if len(entries) < 2:
continue continue
visited_pairs = set() # Construire un mapping ID -> stations
id_to_entry = {entry['ID']: entry for entry in entries}
id_to_stations = {entry['ID']: set(entry['STATION'].iloc[:, 0]) for entry in entries}
for i in range(len(entries)): # Construire les connexions directes (graphe implicite)
id_i = entries[i]['ID'] adjacency = defaultdict(set)
stations_i = set(entries[i]['STATION'].iloc[:, 0]) ids = list(id_to_entry.keys())
ids_group = [id_i] for i in range(len(ids)):
common_stations = set() for j in range(i + 1, len(ids)):
id_i, id_j = ids[i], ids[j]
if id_to_stations[id_i] & id_to_stations[id_j]: # intersection non vide
adjacency[id_i].add(id_j)
adjacency[id_j].add(id_i)
for j in range(i+1, len(entries)): # Trouver les composantes connexes (DFS)
pair = tuple(sorted((id_i, entries[j]['ID']))) visited = set()
if pair in visited_pairs:
continue
visited_pairs.add(pair) def dfs(node, component):
stations_j = set(entries[j]['STATION'].iloc[:, 0]) visited.add(node)
intersection = stations_i & stations_j component.append(node)
for neighbor in adjacency[node]:
if neighbor not in visited:
dfs(neighbor, component)
if intersection: for id_ in ids:
ids_group.append(entries[j]['ID']) if id_ not in visited:
common_stations.update(intersection) component = []
dfs(id_, component)
if len(component) > 1:
# Calcul des stations communes (fusion de toutes)
stations_union = set()
for i in range(len(component)):
for j in range(i + 1, len(component)):
common = id_to_stations[component[i]] & id_to_stations[component[j]]
stations_union.update(common)
if len(ids_group) > 1:
ids_group = sorted(set(ids_group))
common_stations = sorted(common_stations)
already_recorded = False
for d in duplicates:
if set(d['IDS']) == set(ids_group):
already_recorded = True
break
if not already_recorded:
duplicates.append({ duplicates.append({
'SURVEY_DATE': key[0], 'SURVEY_DATE': key[0],
'SURVEY_TEAM': key[1], 'SURVEY_TEAM': key[1],
'IDS': ids_group, 'IDS': sorted(component),
'COMMON_STATIONS': common_stations 'COMMON_STATIONS': sorted(stations_union)
}) })
return duplicates return duplicates
################################################################################################# #################################################################################################
def points_uniques(data, crs_wkt): def points_uniques(data, crs_wkt):
# Création d'un DataFrame à partir des lignes de données # Création d'un DataFrame à partir des lignes de données
@@ -1694,7 +1794,6 @@ def load_text_file_utf8(filepath, short_filename):
return None, "" return None, ""
################################################################################################# #################################################################################################
# Création des dossiers Th à partir d'un dat # # Création des dossiers Th à partir d'un dat #
################################################################################################# #################################################################################################
@@ -1733,7 +1832,7 @@ def dat_to_th_files (ENTRY_FILE, fixPoints = [], crs_wkt = "", CONFIG_PATH = "",
# Listes pour stocker les données # Listes pour stocker les données
data = [] data = []
unique_id = 1 unique_id = 1
totdata = f"\t## Liste inputs\n" totdata = f"\t## Input list:\n"
totMapsPlan = "" totMapsPlan = ""
totMapsExtended = "" totMapsExtended = ""
totReadMeErrorDat = "" totReadMeErrorDat = ""
@@ -1844,7 +1943,7 @@ def dat_to_th_files (ENTRY_FILE, fixPoints = [], crs_wkt = "", CONFIG_PATH = "",
# Ajouter les données de la section à la liste # Ajouter les données de la section à la liste
if len(section_data['DATA']) > 0 : if len(section_data['DATA']) > 0 :
listStationSection, dfDATA = station_list(section_data, listStationSection, fixPoints) listStationSection, dfDATA = station_list(section_data, listStationSection, fixPoints, section_data['SURVEY_NAME'])
section_data['STATION'] = listStationSection section_data['STATION'] = listStationSection
data.append(section_data) data.append(section_data)
unique_id += 1 unique_id += 1
@@ -1911,7 +2010,6 @@ def dat_to_th_files (ENTRY_FILE, fixPoints = [], crs_wkt = "", CONFIG_PATH = "",
################################################################################################# #################################################################################################
surveyCount = 1 surveyCount = 1
SurveyListEqui = []
# totReadMe += f"* Source file: {os.path.basename(ENTRY_FILE)}\n" # totReadMe += f"* Source file: {os.path.basename(ENTRY_FILE)}\n"
@@ -1963,14 +2061,13 @@ def dat_to_th_files (ENTRY_FILE, fixPoints = [], crs_wkt = "", CONFIG_PATH = "",
'SURVEY_NAME': _line['SURVEY_NAME'] 'SURVEY_NAME': _line['SURVEY_NAME']
} }
SurveyListEqui.append(SurveyNameCount)
################################################################################################# #################################################################################################
# gestion des DATA # # gestion des DATA #
################################################################################################# #################################################################################################
stationList, dfDATA = station_list(_line, stationList, fixPoints) stationList, dfDATA = station_list(_line, stationList, fixPoints, currentSurveyName)
################################################################################################# #################################################################################################
# Recherche des points fixes (entrées) # Recherche des points fixes (entrées)
@@ -2057,7 +2154,7 @@ def dat_to_th_files (ENTRY_FILE, fixPoints = [], crs_wkt = "", CONFIG_PATH = "",
totReadMeError = totReadMeErrorDat) totReadMeError = totReadMeErrorDat)
threads += thread2 threads += thread2
log.info(f"File: {Colors.ENDC}{os.path.basename(ENTRY_FILE)}.dat{Colors.INFO} compilation successful, length: {Colors.ENDC}{stat["length"]}m{Colors.INFO}, depth: {Colors.ENDC}{stat["depth"]}m") log.info(f"File: {Colors.ENDC}{currentSurveyName}{Colors.INFO}, compilation successful, length: {Colors.ENDC}{stat["length"]}m{Colors.INFO}, depth: {Colors.ENDC}{stat["depth"]}m")
totReadMe += f"\t{currentSurveyName} compilation successful length: {stat["length"]} m, depth: {stat["depth"]} m\n" totReadMe += f"\t{currentSurveyName} compilation successful length: {stat["length"]} m, depth: {stat["depth"]} m\n"
_destination = output_file[:-3] + "\\Sources" _destination = output_file[:-3] + "\\Sources"
@@ -2075,6 +2172,10 @@ def dat_to_th_files (ENTRY_FILE, fixPoints = [], crs_wkt = "", CONFIG_PATH = "",
totMapsExtended += f"\tMC-{currentSurveyName}-Extended-tot@{currentSurveyName}\n\tbreak\n" totMapsExtended += f"\tMC-{currentSurveyName}-Extended-tot@{currentSurveyName}\n\tbreak\n"
surveyCount += 1 surveyCount += 1
if globalData.error_count > 0:
bar.text(f"{Colors.INFO}, file: {Colors.ENDC}{os.path.basename(ENTRY_FILE)[:-4]}{Colors.INFO}, survey: {Colors.ENDC}{currentSurveyName}{Colors.ERROR}, error: {Colors.ENDC}{globalData.error_count}")
else :
bar.text(f"{Colors.INFO}, file: {Colors.ENDC}{os.path.basename(ENTRY_FILE)[:-4]}{Colors.INFO}, survey: {Colors.ENDC}{currentSurveyName}")
bar() bar()
################################################################################################# #################################################################################################
@@ -2082,21 +2183,18 @@ def dat_to_th_files (ENTRY_FILE, fixPoints = [], crs_wkt = "", CONFIG_PATH = "",
################################################################################################# #################################################################################################
################################################################################################# #################################################################################################
# Gestion des equats # Gestion des equates
################################################################################################# #################################################################################################
totdata +=f"\n" totdata +=f"\n"
dfEqui = pd.DataFrame(SurveyListEqui)
stationList = stationList.merge(dfEqui, how='left', left_on='Survey_Name_01', right_on='SURVEY_NAME')
stationList['Survey_Name_01'] = stationList['surveyCount']
stationList.drop(columns=['SURVEY_NAME', 'surveyCount'], inplace=True)
_stationList = stationList.copy() _stationList = stationList.copy()
# On numérote les doublons de Survey_Name pour chaque StationName # On numérote les doublons de Survey_Name pour chaque StationName
_stationList['Survey_Number'] = _stationList.groupby('StationName').cumcount() + 1 _stationList['Survey_Number'] = _stationList.groupby('StationName').cumcount() + 1
# print(_stationList)
# On pivote le tableau pour que chaque Survey_Name devienne une colonne # On pivote le tableau pour que chaque Survey_Name devienne une colonne
tableau_pivot = _stationList.pivot(index='StationName', columns='Survey_Number', values='Survey_Name_01') tableau_pivot = _stationList.pivot(index='StationName', columns='Survey_Number', values='Survey_Name_01')
@@ -2104,15 +2202,14 @@ def dat_to_th_files (ENTRY_FILE, fixPoints = [], crs_wkt = "", CONFIG_PATH = "",
# print(f"tableau_pivot: {Colors.ENDC}{tableau_pivot}{Colors.INFO} in {Colors.ENDC}{ENTRY_FILE}") # print(f"tableau_pivot: {Colors.ENDC}{tableau_pivot}{Colors.INFO} in {Colors.ENDC}{ENTRY_FILE}")
totdata +=f"\n\t## Liste equates\n" totdata +=f"\n\t## equates list:\n"
if 'Survey_Name_2' in tableau_pivot.columns: if 'Survey_Name_2' in tableau_pivot.columns:
# On réinitialise l'index pour avoir StationName comme colonne normale # On réinitialise l'index pour avoir StationName comme colonne normale
tableau_pivot = tableau_pivot.reset_index() tableau_pivot = tableau_pivot.reset_index()
tableau_equate = tableau_pivot[tableau_pivot['Survey_Name_2'].notna()] tableau_equate = tableau_pivot[tableau_pivot['Survey_Name_2'].notna()]
log.info(f"Total 'equates' founds: {Colors.ENDC}{len(tableau_equate)}{Colors.INFO} in {Colors.ENDC}{shortCurentFile}")
log.info(f"Total 'equats' : {Colors.ENDC}{len(tableau_equate)}{Colors.INFO} in {Colors.ENDC}{shortCurentFile}")
# print(tableau_equate) # print(tableau_equate)
# print(f"fixePoints : {Colors.ENDC}{fixed_names}{Colors.INFO} in {Colors.ENDC}{ENTRY_FILE}") # print(f"fixePoints : {Colors.ENDC}{fixed_names}{Colors.INFO} in {Colors.ENDC}{ENTRY_FILE}")
@@ -2123,15 +2220,14 @@ def dat_to_th_files (ENTRY_FILE, fixPoints = [], crs_wkt = "", CONFIG_PATH = "",
# On récupère tous les Survey_Name non vides (NaN exclus) # On récupère tous les Survey_Name non vides (NaN exclus)
surveys = [row[col] for col in tableau_equate.columns if col.startswith('Survey_Name') and pd.notna(row[col])] surveys = [row[col] for col in tableau_equate.columns if col.startswith('Survey_Name') and pd.notna(row[col])]
# Pour chaque paire unique (i < j), on écrit la ligne 'equate' # Pour chaque paire unique (i < j), on écrit la ligne 'equate'
for i in range(len(surveys)): for i in range(len(surveys)):
for j in range(i + 1, len(surveys)): for j in range(i + 1, len(surveys)):
totdata +=f"\tequate {station}@{surveys[i]}.{surveys[i]} {station}@{surveys[j]}.{surveys[j]}\n" totdata +=f"\tequate {station}@{surveys[i]}.{surveys[i]} {station}@{surveys[j]}.{surveys[j]}\n"
else: else:
log.info(f"No 'equats' found in {Colors.ENDC}{ENTRY_FILE}") log.info(f"No 'equates' found in {Colors.ENDC}{ENTRY_FILE}")
totdata +=f"\n\t## Appel des maps\n\tinput {SurveyTitle}-maps.th\n" totdata +=f"\n\t## Maps list:\n\tinput {SurveyTitle}-maps.th\n"
if totReadMeErrorDat == "" : totReadMeErrorDat += "\tAny error in this file, that's perfect !\n" if totReadMeErrorDat == "" : totReadMeErrorDat += "\tAny error in this file, that's perfect !\n"
@@ -2216,7 +2312,7 @@ if __name__ == u'__main__':
#parser.add_argument("--format", choices=['th2', 'plt'], help="Output format. Either th2 for producing skeleton for drawing or plt for visualizing in aven/loch", default="th2") #parser.add_argument("--format", choices=['th2', 'plt'], help="Output format. Either th2 for producing skeleton for drawing or plt for visualizing in aven/loch", default="th2")
parser.add_argument("--output", default="./", help="Output folder path") parser.add_argument("--output", default="./", help="Output folder path")
# parser.add_argument("--therion-path", help="Path to therion binary", default="therion") # parser.add_argument("--therion-path", help="Path to therion binary", default="therion")
parser.add_argument("--scale", help="Scale for the exports", default="1000") parser.add_argument("--scale", help="Scale for the exports", default="100")
parser.add_argument("--lines", type=str_to_bool, help="Shot lines in th2 files", default=-1) parser.add_argument("--lines", type=str_to_bool, help="Shot lines in th2 files", default=-1)
parser.add_argument("--names", type=str_to_bool, help="Stations names in th2 files", default=-1) parser.add_argument("--names", type=str_to_bool, help="Stations names in th2 files", default=-1)
parser.add_argument("--update", help="Mode update, option th2", default="") parser.add_argument("--update", help="Mode update, option th2", default="")
@@ -2331,10 +2427,22 @@ if __name__ == u'__main__':
log.critical(f"The folder {Colors.ENDC}{SurveyTitleDat}{Colors.ERROR}{Colors.BOLD}, all ready exist : update mode is not possible for mak files") log.critical(f"The folder {Colors.ENDC}{SurveyTitleDat}{Colors.ERROR}{Colors.BOLD}, all ready exist : update mode is not possible for mak files")
exit(0) exit(0)
with alive_bar(QtySections, title=f"{Colors.GREEN}Surveys progress: {Colors.BLUE}", length = 20, enrich_print=False) as bar: with alive_bar(
QtySections,
title=f"{Colors.GREEN}Surveys progress: {Colors.BLUE}",
length = 20,
enrich_print=False,
stats=True, # Désactive les stats par défaut pour plus de lisibilité
elapsed=True, # Optionnel : masque le temps écoulé
monitor=True, # Optionnel : masque les métriques (ex: "eta")
bar="smooth" # Style de la barre (autres options: "smooth", "classic", "blocks")
) as bar:
with redirect_stdout(sys.__stdout__): with redirect_stdout(sys.__stdout__):
for i in range(1): for i in range(1):
bar.text(f"{Colors.INFO}file: {Colors.ENDC}{os.path.basename(ABS_file)}") if globalData.error_count > 0:
bar.text(f"{Colors.INFO}, file: {Colors.ENDC}{os.path.basename(ABS_file)[:-4]}{Colors.ERROR}, error: {Colors.ENDC}{globalData.error_count}")
else :
bar.text(f"{Colors.INFO}, file: {Colors.ENDC}{os.path.basename(ABS_file)[:-4]}")
stationList, fileTitle, totReadMeError, thread2 = dat_to_th_files (ABS_file , fixPoints = [], crs_wkt = "", CONFIG_PATH = _ConfigPath, totReadMeError = "", bar = bar) stationList, fileTitle, totReadMeError, thread2 = dat_to_th_files (ABS_file , fixPoints = [], crs_wkt = "", CONFIG_PATH = _ConfigPath, totReadMeError = "", bar = bar)
threads += thread2 threads += thread2
bar() bar()