update pyThtoQgis

This commit is contained in:
Alex38Lyon
2026-08-31 12:24:44 +02:00
parent 793af4bfe0
commit f4c71ada84
16 changed files with 268 additions and 73 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2 -1
View File
@@ -12,6 +12,7 @@
"Qgis", "Qgis",
"shapefiles", "shapefiles",
"thconfig", "thconfig",
"therion" "therion",
"TYPEFCR"
] ]
} }
+8 -7
View File
@@ -5,7 +5,7 @@
# # # #
!############################################################################################# !#############################################################################################
Alex 2026 02 26 Alex 2026 08 30
""" """
import os, logging, sys, re, unicodedata import os, logging, sys, re, unicodedata
@@ -14,7 +14,7 @@ from pathlib import Path
log = logging.getLogger("Logger") log = logging.getLogger("Logger")
################################################################################################# #################################################################################################
# Couleurs ANSI par niveau de log # Couleurs ANSI par niveau de log #
################################################################################################# #################################################################################################
COLOR_CODES = { COLOR_CODES = {
logging.DEBUG: "\033[94m", # Bleu logging.DEBUG: "\033[94m", # Bleu
@@ -26,7 +26,8 @@ COLOR_CODES = {
RESET = "\033[0m" RESET = "\033[0m"
################################################################################################# #################################################################################################
# Codes de couleur ANSI # Codes de couleur ANSI #
#################################################################################################
class Colors: class Colors:
BLACK = '\033[90m' BLACK = '\033[90m'
RED = '\033[91m' RED = '\033[91m'
@@ -120,14 +121,14 @@ def sanitize_filename(thName):
return thName or "default_filename" # Avoid empty result return thName or "default_filename" # Avoid empty result
################################################################################################# #################################################################################################
# Supprime les codes ANSI (pour l'écriture dans les fichiers) # Supprime les codes ANSI (pour l'écriture dans les fichiers) #
################################################################################################# #################################################################################################
def strip_ansi_codes(text): def strip_ansi_codes(text):
ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
return ansi_escape.sub('', text) return ansi_escape.sub('', text)
################################################################################################# #################################################################################################
# Formatter pour la console avec couleurs # Formatter pour la console avec couleurs #
################################################################################################# #################################################################################################
class ConsoleFormatter(logging.Formatter): class ConsoleFormatter(logging.Formatter):
def format(self, record): def format(self, record):
@@ -136,7 +137,7 @@ class ConsoleFormatter(logging.Formatter):
return f"{color}{message}{RESET}" return f"{color}{message}{RESET}"
################################################################################################# #################################################################################################
# Formatter pour le fichier avec "!!!" sur les erreurs # Formatter pour le fichier avec "!!!" sur les erreurs #
################################################################################################# #################################################################################################
class FileFormatter(logging.Formatter): class FileFormatter(logging.Formatter):
def format(self, record): def format(self, record):
@@ -156,7 +157,7 @@ class FileFormatter(logging.Formatter):
return super().format(record_copy) return super().format(record_copy)
################################################################################################# #################################################################################################
# Fonction de configuration du logger # Fonction de configuration du logger #
################################################################################################# #################################################################################################
def setup_logger(logfile="app.log", debug_log=False): def setup_logger(logfile="app.log", debug_log=False):
logger = logging.getLogger("Logger") logger = logging.getLogger("Logger")
+8 -6
View File
@@ -1,13 +1,14 @@
""" """
!#############################################################################################! !#############################################################################################
# #
# global_data.py for pyThtoQgis.py #
# #
!#############################################################################################
global_data.py for pyThtoQgis.py
!#############################################################################################!
""" """
#################################################################################################
Version = "2026.08.28" Version = "2026.08.28"
################################################################################################# #################################################################################################
pathshp = ".\\Inputs\\" pathshp = ".\\Inputs\\"
@@ -19,5 +20,6 @@ file_log = "pyThtoQgis.log"
output_log = ".\\" + file_log output_log = ".\\" + file_log
errorCount = 0 errorCount = 0
geometryErrors = []
debug_log = True debug_log = False
@@ -11,7 +11,8 @@
"geopandas", "geopandas",
"Ggis", "Ggis",
"outputfolder", "outputfolder",
"Qgis" "Qgis",
"TYPEFCR"
] ]
} }
} }
+243 -53
View File
@@ -22,13 +22,27 @@ Modifié Alex 2025 01 31
Modifié Alex 2026 02 27 Modifié Alex 2026 02 27
Modifié Alex 2026 08 28 Modifié Alex 2026 08 28
Inputs files (16): (.dbf, .prj, .shp, .shx) Inputs files (28): (.dbf, .prj, .shp, .shx)
- points2d - points2d (4)
- lines2d - lines2d (4)
- areas2d - areas2d (4)
- outlines - outlines (4)
- shots3d (4)
- stations3d (4)
- walls3d (4)
En cas d'erreur corriger manuellement (QGis) la topologie des fichiers Outputs files (8), in QGis_GPKG_Files folder:
- points2d.gpkg
- lines2dMasked.gpkg
- areas2dMasked.gpkg
- outline2d.gpkg
- shots3d.gpkg
- stations3d.gpkg
- walls3d.gpkg
- pyThtoQgis.log
En cas d'erreur (voir log), corriger manuellement avec QGis ou dans therion la topologie des fichiers
""" """
@@ -42,7 +56,7 @@ from Lib.general_fonctions import setup_logger, Colors, safe_relpath, colored_he
# Import Python modules # Import Python modules
import sys, os, argparse, time, math import sys, os, argparse, time, math, logging
import tkinter as tk import tkinter as tk
from tkinter import filedialog from tkinter import filedialog
from osgeo import ogr, gdal from osgeo import ogr, gdal
@@ -71,6 +85,7 @@ def cutGPKG(input_gpkg_path, outlines_path, output_gpkg_path):
# OPEN INPUT # OPEN INPUT
# ------------------------------------------------- # -------------------------------------------------
ds_in = ogr.Open(input_gpkg_path) ds_in = ogr.Open(input_gpkg_path)
if ds_in is None: if ds_in is None:
log.error(f"cutGPKG, cannot open file : {Colors.ENDC}{input_gpkg_path}") log.error(f"cutGPKG, cannot open file : {Colors.ENDC}{input_gpkg_path}")
globalDat.errorCount += 1 globalDat.errorCount += 1
@@ -83,6 +98,7 @@ def cutGPKG(input_gpkg_path, outlines_path, output_gpkg_path):
# Vérification présence champ _SCRAP_ID # Vérification présence champ _SCRAP_ID
idx_scrap = in_defn.GetFieldIndex("_SCRAP_ID") idx_scrap = in_defn.GetFieldIndex("_SCRAP_ID")
if idx_scrap == -1: if idx_scrap == -1:
log.error("cutGPKG, field '_SCRAP_ID' not found in input layer.") log.error("cutGPKG, field '_SCRAP_ID' not found in input layer.")
globalDat.errorCount += 1 globalDat.errorCount += 1
@@ -92,6 +108,7 @@ def cutGPKG(input_gpkg_path, outlines_path, output_gpkg_path):
# OPEN OUTLINES # OPEN OUTLINES
# ------------------------------------------------- # -------------------------------------------------
ds_outline = ogr.Open(outlines_path) ds_outline = ogr.Open(outlines_path)
if ds_outline is None: if ds_outline is None:
log.error(f"cutGPKG, cannot open file : {Colors.ENDC}{outlines_path}") log.error(f"cutGPKG, cannot open file : {Colors.ENDC}{outlines_path}")
globalDat.errorCount += 1 globalDat.errorCount += 1
@@ -101,6 +118,7 @@ def cutGPKG(input_gpkg_path, outlines_path, output_gpkg_path):
outline_defn = layer_outline.GetLayerDefn() outline_defn = layer_outline.GetLayerDefn()
idx_id = outline_defn.GetFieldIndex("_ID") idx_id = outline_defn.GetFieldIndex("_ID")
if idx_id == -1: if idx_id == -1:
log.error("cutGPKG, field '_ID' not found in outlines layer.") log.error("cutGPKG, field '_ID' not found in outlines layer.")
globalDat.errorCount += 1 globalDat.errorCount += 1
@@ -159,7 +177,7 @@ def cutGPKG(input_gpkg_path, outlines_path, output_gpkg_path):
# ------------------------------------------------- # -------------------------------------------------
# PROCESS FEATURES # PROCESS FEATURES
# ------------------------------------------------- # -------------------------------------------------
with alive_bar(len(layer_in), title=f"{Colors.YELLOW}Clipping file {Colors.ENDC}", length=20) as bar: with alive_bar(len(layer_in), title=f"{Colors.YELLOW}Clipping file {Colors.ENDC}{safe_relpath(input_gpkg_path)} {Colors.ENDC}", length=20) as bar:
for feat in layer_in: for feat in layer_in:
geom = feat.GetGeometryRef() geom = feat.GetGeometryRef()
@@ -350,7 +368,7 @@ def extractVertices(input_gpkg_path, output_gpkg_path):
# ------------------------------------------------- # -------------------------------------------------
layer_in.ResetReading() layer_in.ResetReading()
with alive_bar(len(layer_in), title=f"{Colors.YELLOW}Extract vertices {Colors.ENDC}", length=20) as bar: with alive_bar(len(layer_in), title=f"{Colors.YELLOW}Extract vertices {Colors.ENDC}{input_gpkg_path}{Colors.ENDC}", length=20) as bar:
for feat in layer_in: for feat in layer_in:
@@ -458,6 +476,10 @@ def diagnostic(file_path):
has_m = False has_m = False
field_stats = defaultdict(list) field_stats = defaultdict(list)
error_codes = defaultdict(int)
error_types = defaultdict(int)
error_messages = defaultdict(int)
extent = layer.GetExtent() extent = layer.GetExtent()
srs = layer.GetSpatialRef() srs = layer.GetSpatialRef()
crs = srs.ExportToWkt() if srs else "CRS inconnu" crs = srs.ExportToWkt() if srs else "CRS inconnu"
@@ -476,6 +498,10 @@ def diagnostic(file_path):
if not geom.IsValid(): if not geom.IsValid():
invalid += 1 invalid += 1
code, error_type, error_msg = get_geometry_error(geom)
error_codes[code] += 1
error_types[error_type] += 1
error_messages[error_msg] += 1
gtype = geom.GetGeometryType() gtype = geom.GetGeometryType()
@@ -497,41 +523,53 @@ def diagnostic(file_path):
elapsed = time.time() - start_time elapsed = time.time() - start_time
file_size = os.path.getsize(file_path) / (1024*1024) # Mo file_size = os.path.getsize(file_path) / (1024*1024) # Mo
log.info(f"==================== BILAN FILE : {Colors.ENDC}{safe_relpath(file_path)}{Colors.INFO} ====================") log.info(f"==================== BILAN FILE: {Colors.ENDC}{safe_relpath(file_path)}{Colors.INFO} ===================================")
log.debug(f"Temps d'analyse : {Colors.ENDC}{elapsed:.2f}{Colors.INFO} s") log.debug(f"Temps d'analyse : {Colors.ENDC}{elapsed:.2f}{Colors.DEBUG} s")
log.debug(f"Taille : {Colors.ENDC}{file_size:.2f}{Colors.INFO} Mo") log.debug(f"Taille : {Colors.ENDC}{file_size:.2f}{Colors.DEBUG} Mo")
log.debug(f"Nombre d'objets : {Colors.ENDC}{total}") log.debug(f"Nombre d'objets : {Colors.ENDC}{total}")
if empty == 0 : log.debug(f"Géométries vides : {Colors.ENDC}{empty}") if empty == 0 :
else : log.warning(f"Géométries vides : {Colors.ENDC}{empty}") log.debug(f"Géométries vides : {Colors.ENDC}{empty}")
else :
log.warning(f"Géométries vides : {Colors.ENDC}{empty}")
if invalid == 0 : log.debug(f"Géométries invalides : {Colors.ENDC}{invalid}") if invalid == 0 :
else : log.warning(f"Géométries invalides : {Colors.ENDC}{invalid}") log.info(f"Géométries invalides : {Colors.ENDC}{invalid}")
else :
log.warning(f"Géométries invalides : {Colors.ENDC}{invalid}")
# for code, count in sorted( error_codes.items(), key=lambda item: item[1], reverse=True ):
# log.warning( f"\t\t{Colors.ENDC}{str(code):<20}{Colors.WARNING}: {Colors.ENDC}{count}" )
for error, count in sorted( error_types.items(), key=lambda item: item[1], reverse=True ):
log.warning( f"\t\t{Colors.ENDC}{error}{Colors.WARNING} : {Colors.ENDC}{count}" )
log.debug(f"MultiGeometries / Collections : {Colors.ENDC}{multi_geom_count}") log.debug(f"MultiGeometries / Collections : {Colors.ENDC}{multi_geom_count}")
log.debug("Types géométriques :") log.debug("Types géométriques :")
for gtype, count in geom_types.items(): for gtype, count in geom_types.items():
log.debug(f"\t{gtype} : {Colors.ENDC}{count}") log.debug(f"\t\t{gtype} : {Colors.ENDC}{count}")
log.debug("Bounding box :") log.debug("Bounding box :")
log.debug(f"\txmin = {Colors.ENDC}{extent[0]}") log.debug(f"\t\txmin = {Colors.ENDC}{extent[0]:>8.3f}{Colors.DEBUG}\txmax = {Colors.ENDC}{extent[1]:>8.3f}")
log.debug(f"\txmax = {Colors.ENDC}{extent[1]}") log.debug(f"\t\tymin = {Colors.ENDC}{extent[2]:>8.3f}{Colors.DEBUG}\tymax = {Colors.ENDC}{extent[3]:>8.3f}")
log.debug(f"\tymin = {Colors.ENDC}{extent[2]}")
log.debug(f"\tymax = {Colors.ENDC}{extent[3]}")
log.debug(f"CRS : {Colors.ENDC}{crs}") log.debug(f"CRS : {Colors.ENDC}{crs}")
log.debug("Dimensions :") log.debug(f"Dimensions, Z présent : {Colors.ENDC}{has_z}{Colors.DEBUG}\tM présent : {Colors.ENDC}{has_m}")
log.debug(f"\tZ présent : {Colors.ENDC}{has_z}")
log.debug(f"\tM présent : {Colors.ENDC}{has_m}")
log.debug("Champs attributaires :") log.debug("Champs attributaires :")
for field, values in field_stats.items(): for field, values in field_stats.items():
unique_count = len(set(values)) unique_count = len(set(values))
log.debug(f"\tchamp : {Colors.ENDC}{field}{Colors.DEBUG} : {Colors.ENDC}{len(values)}{Colors.DEBUG} valeurs, {Colors.ENDC}{unique_count}{Colors.DEBUG} uniques") # log.debug(f"\t\t{Colors.ENDC}{field}{Colors.DEBUG}\t\t: {Colors.ENDC}{len(values)}{Colors.DEBUG} valeurs,\t\t{Colors.ENDC}{unique_count}{Colors.DEBUG} uniques")
# Définir une largeur fixe pour les noms de champs (par exemple 20 caractères)
field_width = 20
number_width = 10
log.debug(
f"\t\t{Colors.ENDC}{field:<{field_width}}{Colors.DEBUG}: "
f"{Colors.ENDC}{len(values):>{number_width}}{Colors.DEBUG} valeurs, "
f"{Colors.ENDC}{unique_count:>{number_width}}{Colors.DEBUG} uniques"
)
log.info(f"=========================================================================================================") log.info(f"=========================================================================================================")
@@ -544,8 +582,97 @@ def diagnostic(file_path):
globalDat.errorCount += 1 globalDat.errorCount += 1
################################################################################################# #################################################################################################
def fix_geometry(geom, GetFID): def get_geometry_error(geom):
"""
Analyse la validité d'une géométrie OGR.
Retourne :
code : code d'erreur entier fourni par GDAL/OGR
error_type : type d'erreur normalisé
error_msg : message complet retourné par GEOS/OGR
"""
if geom is None:
return ( -1, "Null geometry", "Geometry is None" )
if geom.IsEmpty():
return ( -1, "Empty geometry", "Geometry is empty" )
# ------------------------------------------------------------------
# GEOMETRYCOLLECTION / MULTI*
# ------------------------------------------------------------------
geom_name = geom.GetGeometryName().upper()
if geom_name == "GEOMETRYCOLLECTION":
return -2, "Not supported", "Geometry not checked (GEOMETRYCOLLECTION)"
error_messages = []
def error_handler(err_class, err_no, message):
error_messages.append((err_no, message))
# Intercepte les messages GDAL/GEOS
gdal.PushErrorHandler(error_handler)
try: try:
valid = geom.IsValid()
finally:
gdal.PopErrorHandler()
# Géométrie valide
if valid:
return (
0,
"Valid",
"Geometry is valid"
)
# Récupération du code et du message GEOS
if error_messages:
code, error_msg = error_messages[0]
else:
code = -1
error_msg = "Invalid geometry"
# Normalisation du type d'erreur
msg = error_msg.lower()
if "ring self-intersection" in msg:
error_type = "Ring self-intersection"
elif "self-intersection" in msg:
error_type = "Self-intersection"
elif "hole lies outside shell" in msg:
error_type = "Hole lies outside shell"
elif "nested shells" in msg:
error_type = "Nested shells"
elif "too few points" in msg:
error_type = "Too few points"
elif "duplicate rings" in msg:
error_type = "Duplicate rings"
elif "invalid coordinate" in msg:
error_type = "Invalid coordinate"
else:
error_type = f"Unknown ({code})"
return code, error_type, error_msg
#################################################################################################
def fix_geometry(feature, infile, layer_defn):
try:
geom = feature.GetGeometryRef()
GetFID = feature.GetFID()
if geom is None: if geom is None:
return None return None
@@ -555,41 +682,80 @@ def fix_geometry(geom, GetFID):
# geom = geom.RemoveDuplicatePoints() # supprime points dupliqués # geom = geom.RemoveDuplicatePoints() # supprime points dupliqués
if not geom.IsValid(): # corrige topologie if not geom.IsValid(): # corrige topologie
log.error(f"Invalid geometry FID {Colors.ENDC}{GetFID}{Colors.WARNING}") geom_type = geom.GetGeometryName() if geom else "None"
globalDat.errorCount += 1
code, error_type, error_msg = get_geometry_error(geom)
# Récupérer les attributs de l'objet
attrs = []
for i in range(layer_defn.GetFieldCount()):
field_name = layer_defn.GetFieldDefn(i).GetNameRef()
field_value = feature.GetField(i)
attrs.append(f"{Colors.ENDC}{field_name}{Colors.ERROR}={Colors.ENDC}{field_value}{Colors.ERROR}")
attrs_formatted = ', '.join(attrs)
if error_type == "Too few points" :
log.debug(
f"geometry in file : {Colors.ENDC}{infile}{Colors.DEBUG}, "
f"geometry type: {Colors.ENDC}{geom_type}{Colors.DEBUG}, "
f"FID: {Colors.ENDC}{GetFID}{Colors.DEBUG}, "
f"message code: {Colors.ENDC}{code}{Colors.DEBUG}, {Colors.ENDC}{error_msg}{Colors.DEBUG}, "
# f"attributes: {Colors.ENDC}{attrs_formatted}{Colors.DEBUG}"
)
elif error_type == "Not supported" :
log.debug(
f"geometry in file : {Colors.ENDC}{infile}{Colors.DEBUG}, "
f"geometry type: {Colors.ENDC}{geom_type}{Colors.DEBUG}, "
f"FID: {Colors.ENDC}{GetFID}{Colors.DEBUG}, "
f"message code: {Colors.ENDC}{code}{Colors.DEBUG}, {Colors.ENDC}{error_msg}{Colors.DEBUG}, "
# f"attributes: {Colors.ENDC}{attrs_formatted}{Colors.DEBUG}"
)
else :
error_info = (
f"invalid geometry in file : {Colors.ENDC}{infile}{Colors.ERROR}, "
f"geometry type: {Colors.ENDC}{geom_type}{Colors.ERROR}, "
f"FID: {Colors.ENDC}{GetFID}{Colors.ERROR}, "
f"message code: {Colors.ENDC}{code}{Colors.ERROR}, {Colors.ENDC}{error_msg}{Colors.ERROR}, "
f"attributes: {Colors.ENDC}{attrs_formatted}{Colors.ERROR}"
)
log.error(f"{error_info}")
globalDat.geometryErrors.append(error_info)
globalDat.errorCount += 1
geom = geom.MakeValid() geom = geom.MakeValid()
if not geom.IsValid(): if not geom.IsValid():
return None return None
if geom is None or geom.IsEmpty(): # supprime géométries vides if geom is None or geom.IsEmpty(): # supprime géométries vides
log.warning(f"Empty geometry removed FID {Colors.ENDC}{GetFID}{Colors.WARNING}") log.warning(f"Empty geometry removed FID {Colors.ENDC}{GetFID}")
return None return None
gtype = geom.GetGeometryType() gtype = geom.GetGeometryType()
if gtype in (ogr.wkbLineString, ogr.wkbLineString25D): if gtype in (ogr.wkbLineString, ogr.wkbLineString25D):
if geom.GetPointCount() < 2: if geom.GetPointCount() < 2:
log.warning(f"Line geometry removed, insufficient number of points < 2 {Colors.ENDC}{GetFID}{Colors.WARNING}") log.warning(f"Line geometry removed, insufficient number of points < 2 {Colors.ENDC}{GetFID}")
return None return None
if gtype == ogr.wkbPolygon: if gtype == ogr.wkbPolygon:
ring = geom.GetGeometryRef(0) ring = geom.GetGeometryRef(0)
if ring is None or ring.GetPointCount() < 4: if ring is None or ring.GetPointCount() < 4:
log.warning(f"Polygon geometry removed, insufficient number of points < 4 {Colors.ENDC}{GetFID}{Colors.WARNING}") log.warning(f"Polygon geometry removed, insufficient number of points < 4 {Colors.ENDC}{GetFID}")
return None return None
return geom return geom
except Exception as e: except Exception as e:
log.error(f"Geometry cannot be repaired, FID {Colors.ENDC}{GetFID}{Colors.ERROR} code : {Colors.ENDC}{e}") log.error(f"Geometry in file {Colors.ENDC}{infile}{Colors.ERROR}, cannot be repaired : FID {Colors.ENDC}{GetFID}{Colors.ERROR}, code : {Colors.ENDC}{e}")
globalDat.errorCount += 1 globalDat.errorCount += 1
return None return None
################################################################################################# #################################################################################################
def shp2gpkg(pathshp, infile, outputspath, outfile): def shp2gpkg(pathshp, infile, outputspath, outfile):
""" """
Conversion rapide SHP -> GPKG. Conversion rapide SHP -> GPKG.
@@ -604,7 +770,7 @@ def shp2gpkg(pathshp, infile, outputspath, outfile):
input_shp = os.path.join(pathshp, infile + ".shp") input_shp = os.path.join(pathshp, infile + ".shp")
output_gpkg = os.path.join(outputspath, outfile + ".gpkg") output_gpkg = os.path.join(outputspath, outfile + ".gpkg")
geom_stats = defaultdict(int) # geom_stats = defaultdict(int)
try: try:
@@ -616,6 +782,7 @@ def shp2gpkg(pathshp, infile, outputspath, outfile):
# ouverture SHP # ouverture SHP
ds = ogr.Open(input_shp) ds = ogr.Open(input_shp)
if ds is None: if ds is None:
log.error(f"shp2gpkg, impossible d'ouvrir le SHP : {Colors.ENDC}{input_shp}") log.error(f"shp2gpkg, impossible d'ouvrir le SHP : {Colors.ENDC}{input_shp}")
globalDat.errorCount += 1 globalDat.errorCount += 1
@@ -652,10 +819,10 @@ def shp2gpkg(pathshp, infile, outputspath, outfile):
log.info(f"SHP file conversion : {Colors.ENDC}{infile}.shp{Colors.INFO} with {Colors.ENDC}{total_count}{Colors.INFO} objets") log.info(f"SHP file conversion : {Colors.ENDC}{infile}.shp{Colors.INFO} with {Colors.ENDC}{total_count}{Colors.INFO} objets")
with alive_bar(len(layer), title=f"{Colors.YELLOW}Conversion SHP file to GPKG {Colors.ENDC}" , length = 20) as bar: with alive_bar(len(layer), title=f"{Colors.YELLOW}Conversion SHP file {Colors.ENDC}{infile}{Colors.YELLOW} to GPKG {Colors.ENDC}" , length = 20) as bar:
for feature in layer: for feature in layer:
geom = fix_geometry(feature.GetGeometryRef(), feature.GetFID() ) geom = fix_geometry(feature, infile, layer_defn)
if geom is None : if geom is None :
log.warning(f"Géométrie impossible à corriger FID") log.warning(f"Géométrie impossible à corriger FID")
@@ -683,6 +850,7 @@ def shp2gpkg(pathshp, infile, outputspath, outfile):
if feature_count % 10000 == 0: if feature_count % 10000 == 0:
out_layer.CommitTransaction() out_layer.CommitTransaction()
out_layer.StartTransaction() out_layer.StartTransaction()
bar() bar()
out_layer.CommitTransaction() out_layer.CommitTransaction()
@@ -699,12 +867,13 @@ def shp2gpkg(pathshp, infile, outputspath, outfile):
if error_count > 0: log.warning(f"{Colors.ENDC}{error_count}{Colors.WARNING} géométries n'ont pas pu être corrigées") if error_count > 0: log.warning(f"{Colors.ENDC}{error_count}{Colors.WARNING} géométries n'ont pas pu être corrigées")
if (total_count - feature_count) > 0 : log.warning(f"{Colors.ENDC}{total_count - feature_count}{Colors.WARNING} géométries supprimées") if (total_count - feature_count) > 0 : log.warning(f"{Colors.ENDC}{total_count - feature_count}{Colors.WARNING} géométries supprimées")
except Exception as e: except Exception as e:
if log: if log:
log.error(f"Error in conversion SHP to GPKG : {e}") log.error(f"Error in conversion file {infile} SHP to GPKG : {e}")
globalDat.errorCount += 1 globalDat.errorCount += 1
raise raise
@@ -847,10 +1016,13 @@ def ThtoQGis(pathshp, outputspath):
file_list = ['points2d', 'lines2d', 'outline2d', 'areas2d', 'walls3d', 'stations3d', 'shots3d'] file_list = ['points2d', 'lines2d', 'outline2d', 'areas2d', 'walls3d', 'stations3d', 'shots3d']
dest_list = ['points2d', 'outline2d', 'walls3d', 'stations3d', 'shots3d'] dest_list = ['points2d', 'outline2d', 'walls3d', 'stations3d', 'shots3d']
log.info(f"{Colors.HEADER}{Colors.UNDERLINE}Step 1: Test files and convert to GPKG format in the folder:{Colors.ENDC} {safe_relpath(outputspath)}") log.info(f"{Colors.HEADER}{Colors.UNDERLINE}Step 1: test files and convert to GPKG format in the folder:{Colors.ENDC} {safe_relpath(outputspath)}")
count = 0
for fname in file_list: for fname in file_list:
log.info(f"Working with file: {Colors.ENDC}{fname}.shp") count+= 1
log.info(f"Working with file ({Colors.ENDC}{count}/{len(file_list)}{Colors.INFO}): {Colors.ENDC}{fname}.shp")
file = os.path.join(pathshp, fname + '.shp') file = os.path.join(pathshp, fname + '.shp')
@@ -859,16 +1031,18 @@ def ThtoQGis(pathshp, outputspath):
globalDat.errorCount += 1 globalDat.errorCount += 1
continue continue
diagnostic(file) err = diagnostic(file)
if fname in dest_list : if fname in dest_list :
destinationName = fname destinationName = fname
else : else :
destinationName = fname + '_fixed' destinationName = fname + '_fixed'
shp2gpkg(pathshp, fname, outputspath, destinationName) shp2gpkg(pathshp, fname, outputspath, destinationName)
err = diagnostic(os.path.join(outputspath,destinationName + '.gpkg')) if err != 0 :
err = diagnostic(os.path.join(outputspath,destinationName + '.gpkg'))
if err != 0 : if err != 0 :
log.error(f"ERROR: in file {Colors.ENDC}{(str(outputspath + destinationName + '.gpkg'))} {Colors.ERROR} please fix it manually with QGis...") log.error(f"ERROR: in file {Colors.ENDC}{(str(outputspath + destinationName + '.gpkg'))} {Colors.ERROR} please fix it manually with QGis...")
@@ -876,19 +1050,28 @@ def ThtoQGis(pathshp, outputspath):
return False return False
log.info(f"{Colors.HEADER}{Colors.UNDERLINE}Step 2: Adapte drawing files for QGis in the folder:{Colors.ENDC} {safe_relpath(outputspath)}") log.info(f"{Colors.HEADER}{Colors.UNDERLINE}Step 2: adapte drawing files (cut it) for QGis in the folder:{Colors.ENDC} {safe_relpath(outputspath)}")
## Work with lines ## Work with lines
cutGPKG(os.path.join(outputspath,'lines2d_fixed.gpkg'),os.path.join(outputspath,'outline2d.gpkg'), os.path.join(outputspath,'lines2dMasked.gpkg')) file_path = os.path.join(outputspath, 'lines2d_fixed.gpkg')
cutGPKG(file_path, os.path.join(outputspath,'outline2d.gpkg'), os.path.join(outputspath,'lines2dMasked.gpkg'))
diagnostic(os.path.join(outputspath,'lines2dMasked.gpkg')) diagnostic(os.path.join(outputspath,'lines2dMasked.gpkg'))
if os.path.exists(file_path):
os.remove(file_path)
## Work with Areas ## Work with Areas
cutGPKG(os.path.join(outputspath, 'areas2d_fixed.gpkg'), os.path.join(outputspath,'outline2d.gpkg'), os.path.join(outputspath,'areas2dMasked.gpkg')) file_path = os.path.join(outputspath, 'areas2d_fixed.gpkg')
cutGPKG(file_path, os.path.join(outputspath,'outline2d.gpkg'), os.path.join(outputspath,'areas2dMasked.gpkg'))
diagnostic(os.path.join(outputspath,'areas2dMasked.gpkg')) diagnostic(os.path.join(outputspath,'areas2dMasked.gpkg'))
if os.path.exists(file_path):
os.remove(file_path)
## Work with Points 'add altitudes' ## Work with Points 'add altitudes'
extractVertices(os.path.join(outputspath,'lines2dMasked.gpkg'), os.path.join(outputspath,'points2d.gpkg')) # extractVertices(os.path.join(outputspath,'lines2dMasked.gpkg'), os.path.join(outputspath,'points2d.gpkg'))
diagnostic(os.path.join(outputspath,'points2d.gpkg')) # diagnostic(os.path.join(outputspath,'points2d.gpkg'))
##################################################################################################################################### #####################################################################################################################################
@@ -897,7 +1080,7 @@ def ThtoQGis(pathshp, outputspath):
# # # #
##################################################################################################################################### #####################################################################################################################################
if __name__ == u'__main__': if __name__ == u'__main__':
################################################### #################################################################################################
ogr.UseExceptions() ogr.UseExceptions()
gdal.UseExceptions() gdal.UseExceptions()
gdal.PushErrorHandler("CPLQuietErrorHandler") gdal.PushErrorHandler("CPLQuietErrorHandler")
@@ -951,7 +1134,7 @@ if __name__ == u'__main__':
elif os.name == 'nt': os.system('cls')# Windows elif os.name == 'nt': os.system('cls')# Windows
else: print("\n" * 100) else: print("\n" * 100)
#################################################################################################
if args.folder : if args.folder :
input_folder = os.path.normpath(args.folder) input_folder = os.path.normpath(args.folder)
@@ -976,6 +1159,7 @@ if __name__ == u'__main__':
log.info(f'{Colors.HEADER} commande line mode') log.info(f'{Colors.HEADER} commande line mode')
log.info(f'{Colors.HEADER} input folder : {Colors.ENDC}{safe_relpath(input_folder)}') log.info(f'{Colors.HEADER} input folder : {Colors.ENDC}{safe_relpath(input_folder)}')
log.info(f'{Colors.HEADER} output folder : {Colors.ENDC}{safe_relpath(output_folder)}') log.info(f'{Colors.HEADER} output folder : {Colors.ENDC}{safe_relpath(output_folder)}')
log.info(f'{Colors.HEADER} log file : {Colors.ENDC}{safe_relpath(next((h.baseFilename for h in log.handlers if isinstance(h, logging.FileHandler)), None))}')
log.info(f'{Colors.HEADER}*********************************************************************************************************') log.info(f'{Colors.HEADER}*********************************************************************************************************')
ThtoQGis(input_folder, output_folder) ThtoQGis(input_folder, output_folder)
@@ -1095,12 +1279,18 @@ if __name__ == u'__main__':
# fname = "walls3d" # fname = "walls3d"
# shp2gpkg(globalDat.pathshp, fname , globalDat.outputspath, fname) # shp2gpkg(globalDat.pathshp, fname , globalDat.outputspath, fname)
#################################################################################################
if globalDat.errorCount == 0 : if globalDat.errorCount == 0 :
log.info(f"{Colors.HEADER}=========================================================================================================")
log.info(f'Execution completed without errors') log.info(f'Execution completed without errors')
log.info(f"{Colors.HEADER}=========================================================================================================")
else : else :
log.error(f'Execution completed with {Colors.ENDC}{globalDat.errorCount}{Colors.ERROR} errors') log.error(f"{Colors.HEADER}=========================================================================================================")
log.error(f"Execution completed with {Colors.ENDC}{globalDat.errorCount}{Colors.ERROR} errors")
for i, error in enumerate(globalDat.geometryErrors, start=1):
log.error(f"{error}")
log.error(f"{Colors.HEADER}=========================================================================================================")