mirror of
https://github.com/Alex38Lyon/Synthese-PSM_LARRA.git
synced 2026-06-01 22:00:53 +00:00
710 lines
32 KiB
Python
710 lines
32 KiB
Python
######!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright (c) 2020 Xavier Robert <xavier.robert@ird.fr>
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
|
"""
|
|
#############################################################
|
|
# #
|
|
# Script to automatize data extraction of Therion databases #
|
|
# #
|
|
# By Xavier Robert #
|
|
# Grenoble, October 2022 #
|
|
# #
|
|
#############################################################
|
|
|
|
Written by Xavier Robert, October 2022x
|
|
Xavier.robert@ird.fr
|
|
|
|
Modifié Alex 2025 01 31
|
|
|
|
Inputs files (16): (.dbf, .prj, .shp, .shx)
|
|
- points2d
|
|
- lines2d
|
|
- areas2d
|
|
- outlines
|
|
|
|
En cas d'erreur corriger manuellement (QGis) les topologies des fichiers
|
|
|
|
"""
|
|
|
|
# Do divisions with Reals, not with integers
|
|
# Must be at the beginning of the file
|
|
from __future__ import division
|
|
|
|
import Lib.global_data as globalDat
|
|
from Lib.general_fonctions import setup_logger, Colors, safe_relpath, colored_help
|
|
|
|
try:
|
|
|
|
# Import Python modules
|
|
#import numpy as np
|
|
import sys, os, argparse, shutil
|
|
import matplotlib.pyplot as plt
|
|
import tkinter as tk
|
|
from tkinter import filedialog
|
|
import shapely
|
|
import fiona
|
|
from fiona import Env
|
|
import geopandas as gpd
|
|
import pandas as pd
|
|
from shapely.geometry import Point, MultiPoint, Polygon, LineString, MultiLineString, MultiPolygon, Polygon
|
|
from shapely.geometry import shape, mapping, GeometryCollection
|
|
from shapely.ops import transform, unary_union, polygonize
|
|
from shapely.errors import TopologicalError
|
|
from shapely.validation import make_valid, explain_validity
|
|
from collections import Counter
|
|
|
|
#from functools import wraps
|
|
from alive_progress import alive_bar # https://github.com/rsalmei/alive-progress
|
|
|
|
except ModuleNotFoundError as e:
|
|
print(f"{Colors.ERROR}ERROR: Some required modules were not found: {Colors.ENDC}{e}")
|
|
print(f"{Colors.WARNING}\t- installation: {Colors.ENDC}pip install xxxxx")
|
|
print(f"{Colors.ERROR}Create or activate the Python 3.13 virtual environment")
|
|
print(f'{Colors.WARNING}\t- command:{Colors.ENDC} . "D:\\08 GitHub\\.venv\\Scripts\\Activate.ps1"')
|
|
print(f"{Colors.ERROR}Script execution stopped{Colors.ENDC}")
|
|
sys.exit(1)
|
|
|
|
#################################################################################################
|
|
def cutareas(pathshp, outlines, outputspath):
|
|
"""
|
|
Function to cut shapefiles areas with the outline to only keep the lines inside the outline
|
|
|
|
Args:
|
|
pathshp (str) : path where are stored output shp from Therion
|
|
outlines (geopandas obj): the outline shapefile
|
|
outputspath (str) : path where to copy the gpkg files
|
|
"""
|
|
|
|
print(f'{Colors.GREEN}Working with :{Colors.ENDC} areas2d.gpkg')
|
|
# 2- Validate the outline and Areas shapefile
|
|
#for rec in outlines:
|
|
# rec2 = validate('outline2d.shp', rec)
|
|
# # update correction --> To do ?
|
|
# #if rec2 != rec:
|
|
#for rec in areas:
|
|
# rec2 = validate('areas2d.shp', rec)
|
|
# # update correction
|
|
# #if rec2 != rec:
|
|
|
|
# Read the Line Shapefile
|
|
# areas = gpd.read_file(pathshp + 'areas2d.shp', driver = 'ESRI shapefile')
|
|
areas = gpd.read_file(pathshp + 'areas2d.gpkg')
|
|
|
|
# Corriger les erreurs de topologie dans les lignes avant traitement
|
|
# areas = fix_topology(areas)
|
|
|
|
# Extract the intersections between outlines and lines
|
|
# be careful, for this operation, geopandas needs to work with rtree and not pygeos
|
|
# --> uninstall pygeos and install rtree
|
|
try:
|
|
areasIN = areas.overlay(outlines, how = 'intersection')
|
|
except:
|
|
print('ERROR: 1) uninstall pygeos and install rtree\n\t2) check your polygons validity')
|
|
import rtree
|
|
print ('\tYou may check the validity of your polygons with the verify function in QGIS')
|
|
areasIN = areas.overlay(outlines, how = 'intersection')
|
|
|
|
# Removes inner lines that have different id and scrap_id
|
|
areasIN = areasIN[areasIN['_SCRAP_ID'] == areasIN ['_ID']]
|
|
|
|
# Save output
|
|
#areasIN.to_file("areas2dMasekd.gpkg", driver = "GPKG", encoding = 'utf8')
|
|
areasIN.to_file(outputspath + "areas2dMasekd.gpkg", driver = "GPKG")
|
|
|
|
return
|
|
|
|
#################################################################################################
|
|
def cutLines(pathshp, outlines, outputspath):
|
|
"""
|
|
Function to cut shapefiles lines with the outline to only keep the lines inside the outline
|
|
|
|
Args:
|
|
pathshp (str) : path where are stored output shp from Therion
|
|
outlines (geopandas obj): the outline shapefile
|
|
outputspath (str) : path where to copy the gpkg files
|
|
"""
|
|
|
|
print(f'{Colors.GREEN}Working with :{Colors.ENDC} lines2d.gpkg')
|
|
# Read the Line Shapefile
|
|
lines = gpd.read_file(pathshp + 'lines2d.gpkg')
|
|
# lines = fix_topology(lines)
|
|
|
|
# Vérifier si outlines est un GeoDataFrame
|
|
if not isinstance(outlines, gpd.GeoDataFrame):
|
|
print(f"{Colors.GREEN}Outlines is not a GeoDataFrame. Attempting conversion...")
|
|
outlines = gpd.read_file(outlines) # Lire un fichier shapefile si outlines est une chaine de caractères
|
|
|
|
# Extract lines that are not masked by the outline
|
|
linesOUT = pd.concat((lines[lines['_TYPE'] == 'centerline'],
|
|
lines[lines['_TYPE'] == 'water_flow'],
|
|
lines[lines['_TYPE'] == 'label'],
|
|
lines[lines['_CLIP'] == 'off']),
|
|
ignore_index=True)
|
|
|
|
# Extract lines will be masked by the outline
|
|
linesIN = lines[lines['_CLIP'] != 'off']
|
|
linesIN = linesIN[linesIN['_TYPE'] != 'centerline']
|
|
linesIN = linesIN[linesIN['_TYPE'] != 'water_flow']
|
|
linesIN = linesIN[linesIN['_TYPE'] != 'label']
|
|
|
|
# Extract the intersections between outlines and lines
|
|
# be careful, for this operation, geopandas needs to work with rtree and not pygeos
|
|
# --> uninstall pygeos and install rtree
|
|
try:
|
|
# outlines = outlines.buffer(0) # [Note Alex] Réparer les géométries invalides
|
|
linesIN = linesIN.overlay(outlines, how = 'intersection', keep_geom_type=True)
|
|
except:
|
|
print(f"{Colors.ERROR}ERROR: uninstall pygeos and install rtree\n\t2) check your polygons validity")
|
|
print (f"{Colors.ERROR}You may check the validity of your polygons with the verify function in QGIS")
|
|
linesIN = linesIN.overlay(outlines, how = 'intersection', keep_geom_type=True)
|
|
|
|
|
|
# Removes inner lines that have different id and scrap_id
|
|
linesIN = linesIN[linesIN['_SCRAP_ID'] == linesIN ['_ID']]
|
|
|
|
# Merge the IN and OUT database
|
|
linesTOT = pd.concat((linesOUT, linesIN),ignore_index=True)
|
|
|
|
# Save output
|
|
linesTOT.to_file(outputspath + "lines2dMasekd.gpkg", driver="GPKG")
|
|
|
|
return
|
|
|
|
#################################################################################################
|
|
def shp2gpkg(pathshp, infile, outputspath, outfile ):
|
|
"""
|
|
Function to convert shp files into gpkg files using Fiona.
|
|
|
|
Args:
|
|
pathshp (str): Path where the input shp files are stored.
|
|
infile (str): Name of the file to be converted (without extension).
|
|
outputspath (str): Path where the output gpkg files will be saved.
|
|
outfile (str): Name of the output file (without extension).
|
|
"""
|
|
try:
|
|
# Configuration de l'environnement Fiona pour accepter les géométries non fermées
|
|
with Env(OGR_GEOMETRY_ACCEPT_UNCLOSED_RING="YES"):
|
|
|
|
input_shp = os.path.join(pathshp, infile + '.shp')
|
|
output_gpkg = os.path.join(outputspath, outfile + '.gpkg')
|
|
|
|
# Vérification que le fichier source existe
|
|
if not os.path.exists(input_shp):
|
|
raise FileNotFoundError(f"\t{Colors.ERROR}Error (shp2gpkg): the file {Colors.ENDC}{input_shp}{Colors.ERROR} did not exist.")
|
|
|
|
|
|
# Lecture du fichier Shapefile
|
|
with fiona.open(input_shp, 'r', encoding='utf-8') as source:
|
|
|
|
geom_types = Counter() # pour compter le nombre de chaque type
|
|
has_z = set()
|
|
|
|
for feat in source:
|
|
geom_type = feat["geometry"]["type"]
|
|
geom_types[geom_type] += 1
|
|
|
|
coords = feat["geometry"]["coordinates"]
|
|
|
|
# Vérifie si la géométrie a un Z
|
|
def check_z(coords):
|
|
"""
|
|
Détecte la présence d'une coordonnée Z
|
|
quelle que soit la profondeur de la géométrie
|
|
"""
|
|
if isinstance(coords, (list, tuple)):
|
|
# Cas Point : (x,y) ou (x,y,z)
|
|
if len(coords) in (2, 3) and all(isinstance(c, (int, float)) for c in coords):
|
|
return len(coords) == 3
|
|
|
|
# Cas Line / Polygon / Multi*
|
|
for c in coords:
|
|
if check_z(c):
|
|
return True
|
|
|
|
return False
|
|
|
|
if check_z(coords):
|
|
has_z.add(True)
|
|
else:
|
|
has_z.add(False)
|
|
|
|
print(
|
|
f"{Colors.GREEN}File conversion to GPKG: {Colors.ENDC}{input_shp}"
|
|
f"{Colors.GREEN}, geometries found: {Colors.ENDC}{dict(geom_types)}"
|
|
f"{Colors.GREEN}, with altitude: {Colors.ENDC}{has_z}"
|
|
)
|
|
|
|
|
|
# Affichage du nombre d'objets et de leur type
|
|
num_features = len(source)
|
|
geometry_type = source.schema['geometry']
|
|
|
|
# Vérification que le driver GPKG est disponible
|
|
if 'GPKG' not in fiona.supported_drivers:
|
|
raise RuntimeError(f"{Colors.ERROR}Error, the GPKG driver is not supported by Fiona")
|
|
|
|
# Création du fichier GeoPackage
|
|
with fiona.open( output_gpkg, 'w', driver='GPKG', schema=source.schema, crs=source.crs, encoding='utf-8') as destination:
|
|
for feature in source:
|
|
destination.write(feature)
|
|
|
|
print(f'{Colors.GREEN}Conversion to GPKG OK, {Colors.GREEN}file : {Colors.ENDC}{pathshp}{infile}.shp{Colors.GREEN} to : {Colors.ENDC}{outputspath}{outfile}.gpkg{Colors.GREEN}, type {Colors.ENDC}{geometry_type}{Colors.GREEN} : {Colors.ENDC}{num_features}')
|
|
|
|
|
|
except FileNotFoundError as e:
|
|
print(f"{Colors.ERROR}Error (shp2gpkg): {Colors.ENDC}{e}", file=sys.stderr)
|
|
|
|
except RuntimeError as e:
|
|
print(f"{Colors.ERROR}Error (shp2gpkg): {Colors.ENDC}{e}", file=sys.stderr)
|
|
|
|
except fiona.errors.FionaError as e:
|
|
print(f"{Colors.ERROR}Error read/write file (shp2gpkg): {Colors.ENDC}{e}", file=sys.stderr)
|
|
|
|
except Exception as e:
|
|
print(f"{Colors.ERROR}Error unknown (shp2gpkg): {Colors.ENDC}{e}", file=sys.stderr)
|
|
|
|
#################################################################################################
|
|
def poly_rock(infile, outfile):
|
|
"""
|
|
Converts line features from the input GeoPackage file into closed polygons,
|
|
and saves the valid ones to an existing layer in the output GeoPackage.
|
|
Only features with the attribute _Type equal to 'rock-edge' or 'rock-border' are considered.
|
|
"""
|
|
|
|
# Load the input layer (assumed to be a line layer)
|
|
gdf = gpd.read_file(infile, encoding='utf-8') # Adjust layer name if needed
|
|
|
|
# Filter features based on _Type attribute
|
|
gdf = gdf[gdf['_TYPE'].isin(['rock-edge', 'rock-border'])]
|
|
|
|
# Attempt to convert LineStrings to Polygons
|
|
polygons = []
|
|
for _, row in gdf.iterrows():
|
|
geom = row.geometry
|
|
if isinstance(geom, LineString) and geom.is_ring:
|
|
new_row = row.copy()
|
|
new_row['geometry'] = Polygon(geom)
|
|
polygons.append(new_row)
|
|
|
|
# Create a GeoDataFrame from the valid polygons
|
|
if polygons:
|
|
poly_gdf = gpd.GeoDataFrame(polygons, geometry='geometry', crs=gdf.crs)
|
|
|
|
# Load existing output data if it exists
|
|
try:
|
|
existing_gdf = gpd.read_file(outfile, encoding='utf-8')
|
|
poly_gdf = gpd.pd.concat([existing_gdf, poly_gdf], ignore_index=True)
|
|
except Exception:
|
|
pass # If file doesn't exist, create a new one
|
|
|
|
# Save updated data to the output GeoPackage
|
|
poly_gdf.to_file(outfile, driver='GPKG', encoding='utf-8') # Adjust layer name if needed
|
|
|
|
print(f"{Colors.GREEN}Added {Colors.ENDC}{len(polygons)} {Colors.GREEN}polygons to {Colors.ENDC}{outfile}.")
|
|
else:
|
|
print(f"{Colors.ERROR}No valid closed polylines found.")
|
|
|
|
#################################################################################################
|
|
def count_topology_errors(file_path):
|
|
"""
|
|
Analyse un shapefile pour détecter les erreurs topologiques et compte les occurrences par type.
|
|
|
|
Args:
|
|
file_path (str): Chemin vers le shapefile à analyser.
|
|
|
|
Returns:
|
|
tuple:
|
|
- dict: clé = type d'erreur, valeur = liste des indices de records concernés
|
|
- int: nombre total d'erreurs détectées
|
|
"""
|
|
error_details = {}
|
|
record_types = {}
|
|
total_records = 0
|
|
total_errors = 0
|
|
try:
|
|
if not os.path.exists(file_path):
|
|
print(f"{Colors.ERROR}File not found: {Colors.ENDC}{file_path}")
|
|
return {}, -1
|
|
|
|
with fiona.open(file_path, "r") as src:
|
|
for i, record in enumerate(src):
|
|
total_records += 1
|
|
|
|
# Vérifier si la géométrie est présente
|
|
if record is None or record.get('geometry') is None:
|
|
props = record.get('properties', {})
|
|
print(f"{Colors.ERROR}Error in file {Colors.ENDC}{safe_relpath(file_path)}{Colors.ERROR}, record {Colors.ENDC}{i+1}{Colors.ERROR} has no geometry, correct it : "
|
|
f"_ID: {Colors.ENDC}{props.get('_ID')}{Colors.ERROR}, _NAME: {Colors.ENDC}{props.get('_NAME')}{Colors.ERROR}, _SURVEY: {Colors.ENDC}{props.get('_SURVEY')}{Colors.ENDC}")
|
|
continue
|
|
|
|
# Créer l'objet Shapely
|
|
try:
|
|
geometry = shape(record['geometry'])
|
|
|
|
except Exception as e:
|
|
props = record.get('properties', {})
|
|
print(f"{Colors.ERROR}Error in file {Colors.ENDC}{safe_relpath(file_path)}{Colors.ERROR},Cannot create shape for record {Colors.ENDC}{i+1}{Colors.ERROR}: {Colors.ENDC}{e}{Colors.ERROR}"
|
|
f"_ID: {Colors.ENDC}{props.get('_ID')}{Colors.ERROR}, _NAME: {Colors.ENDC}{props.get('_NAME')}{Colors.ERROR}, _SURVEY: {Colors.ENDC}{props.get('_SURVEY')}{Colors.ENDC}")
|
|
continue
|
|
|
|
# Ignorer les géométries vides
|
|
if geometry.is_empty:
|
|
print(f"{Colors.WARNING}Warning, file {Colors.ENDC}{safe_relpath(file_path)}{Colors.WARNING},Record {i+1} has empty geometry. Skipping.{Colors.ENDC}")
|
|
continue
|
|
|
|
# Comptage des types de géométrie
|
|
geom_type = geometry.geom_type
|
|
record_types[geom_type] = record_types.get(geom_type, 0) + 1
|
|
|
|
# Vérifier la validité topologique
|
|
try:
|
|
validity_explanation = explain_validity(geometry)
|
|
|
|
if validity_explanation != "Valid Geometry":
|
|
total_errors += 1
|
|
# Conserver l'explication complète comme type d'erreur
|
|
error_details.setdefault(validity_explanation, []).append(i)
|
|
|
|
except Exception as e:
|
|
print(f"{Colors.ERROR}Error in file {Colors.ENDC}{safe_relpath(file_path)}{Colors.ERROR}, validating geometry for record {Colors.ENDC}{i+1}{Colors.ERROR}: {Colors.ENDC}{e}{Colors.ENDC}")
|
|
|
|
|
|
print(f"{Colors.GREEN}Geometry num: {Colors.ENDC}{i+1}{Colors.YELLOW}, types found: {Colors.ENDC}{record_types}")
|
|
|
|
# Affichage du résumé
|
|
if total_errors == 0:
|
|
print(f"{Colors.GREEN}File error check OK: {Colors.ENDC}{safe_relpath(file_path)}{Colors.GREEN}, "
|
|
f"records: {Colors.ENDC}{total_records}{Colors.GREEN}, no errors found")
|
|
|
|
else:
|
|
print(f"{Colors.ERROR}File error check NOK: {Colors.ENDC}{safe_relpath(file_path)}{Colors.ERROR}, "
|
|
f"records: {Colors.ENDC}{total_records}{Colors.ERROR}, total errors: {Colors.ENDC}{total_errors}")
|
|
|
|
# for err_type, indices in error_details.items():
|
|
# print(f"{Colors.ERROR}Detail error in file {Colors.ENDC}{safe_relpath(file_path)}{Colors.ERROR}, {Colors.ENDC}{err_type} "
|
|
# f"{Colors.ERROR}occurrences: {Colors.ENDC}{len(indices)}{Colors.ENDC}")
|
|
|
|
# Optionnel : afficher le détail des types de géométrie
|
|
print(f"{Colors.GREEN}Geometry in file: {Colors.ENDC}{safe_relpath(file_path)}{Colors.GREEN}, types found: {Colors.ENDC}{record_types}")
|
|
|
|
return error_details, total_errors
|
|
|
|
except Exception as e:
|
|
print(f"{Colors.ERROR}Topology error when analyzing the shapefile: {Colors.ENDC}"
|
|
f"{safe_relpath(file_path)}{Colors.ERROR}, code: {Colors.ENDC}{e}")
|
|
return {}, -1
|
|
|
|
#################################################################################################
|
|
def fix_geometries(input_shp, output_shp):
|
|
"""
|
|
Fixes geometry errors in a Shapefile and saves only objects of the same type as the source file.
|
|
Displays a summary of the modifications and a report of geometries by type before and after processing.
|
|
|
|
:param input_shp: Path to the input Shapefile
|
|
:param output_shp: Path to the output Shapefile
|
|
"""
|
|
|
|
try :
|
|
with fiona.open(input_shp, 'r') as src:
|
|
meta = src.meta # File metadata
|
|
original_geom_type = meta['schema']['geometry'].upper() # Expected geometry type (in uppercase to avoid format discrepancies)
|
|
original_geom_type_simple = original_geom_type.replace('3D ', '') # Remove '3D ' prefix
|
|
fixed_features = []
|
|
geom_counts_before = Counter()
|
|
geom_counts_after = Counter()
|
|
modifications = 0
|
|
error_details = {}
|
|
corrected = 0
|
|
i = 0
|
|
|
|
for feature in src:
|
|
fixed_feature = dict(feature) # Copy the feature
|
|
geom = shape(feature['geometry'])
|
|
geom_type = geom.geom_type.upper()
|
|
geom_counts_before[geom_type] += 1
|
|
|
|
# Fix the geometry
|
|
valid_geom = make_valid(geom)
|
|
|
|
geom_type_fixed = valid_geom.geom_type.upper()
|
|
|
|
geom_counts_after[geom_type_fixed] += 1
|
|
|
|
# Check if the fixed geometry is of the same type as the original
|
|
if geom_type_fixed == original_geom_type or geom_type_fixed == original_geom_type_simple:
|
|
fixed_feature['geometry'] = mapping(valid_geom)
|
|
fixed_features.append(fixed_feature)
|
|
|
|
else:
|
|
modifications += 1
|
|
|
|
try:
|
|
# Validate the geometry and explain any issues
|
|
validity_explanation = explain_validity(geom)
|
|
|
|
if validity_explanation != "Valid Geometry":
|
|
# Extract the type of error
|
|
error_type = validity_explanation.split(" ")[0] # First word of the explanation
|
|
|
|
# Add the record index to the error details
|
|
if error_type in error_details:
|
|
error_details[error_type].append(i)
|
|
|
|
else:
|
|
error_details[error_type] = [i]
|
|
|
|
if error_type=="Too" : corrected += 1
|
|
|
|
i += 1
|
|
|
|
else :
|
|
props = feature.get('properties', {})
|
|
print(f"{Colors.ERROR}Error in file {Colors.ENDC}{safe_relpath(input_shp)}{Colors.ERROR},correct it manually, "
|
|
f"_ID: {Colors.ENDC}{props.get('_ID')}{Colors.ERROR}, _NAME: {Colors.ENDC}{props.get('_NAME')}{Colors.ERROR}, _SURVEY: {Colors.ENDC}{props.get('_SURVEY')}{Colors.ENDC}")
|
|
|
|
|
|
except Exception as e:
|
|
print(f"{Colors.ERROR}Error processing record {Colors.ENDC}: {e}")
|
|
|
|
|
|
# Write the output file with only geometries of the original type
|
|
if fixed_features:
|
|
with fiona.open(output_shp, 'w', **meta) as dst:
|
|
dst.writerecords(fixed_features)
|
|
|
|
# print(f"{Colors.GREEN}Correction completed{Colors.GREEN}, file {Colors.ENDC}{input_shp}{Colors.GREEN} saved as: {Colors.ENDC}{output_shp}")
|
|
|
|
if error_details:
|
|
for error_type, indices in error_details.items():
|
|
if error_type=="Too" :
|
|
print(f"{Colors.GREEN}Correction completed{Colors.GREEN}, file {Colors.ENDC}{input_shp}{Colors.GREEN} saved as: {Colors.ENDC}{output_shp}{Colors.GREEN} Erreur type : {Colors.ENDC}{error_type} : {len(indices)}{Colors.GREEN} occurrences corrected{Colors.ENDC}")
|
|
|
|
else :
|
|
print(f"{Colors.WARNING}Correction issue{Colors.ERROR}, file {Colors.ENDC}{input_shp}{Colors.ERROR} saved as: {Colors.ENDC}{output_shp}{Colors.ERROR} Erreur type : {Colors.ENDC}{error_type} : {len(indices)}{Colors.ERROR} occurrences{Colors.ENDC}")
|
|
|
|
else:
|
|
print(f"{Colors.ERROR}Error, no valid features found in {Colors.ENDC}{input_shp}.{Colors.ERROR} No file generated.{Colors.ENDC}")
|
|
|
|
# Display the summary
|
|
if modifications !=0 : print(f"{Colors.WARNING}Total number of geometries ignored: {Colors.ENDC}{modifications}")
|
|
print(f"{Colors.INFO}Total number of geometries corrected : {Colors.ENDC}{corrected}")
|
|
print(f"{Colors.INFO}Before correction: {Colors.ENDC}{dict(geom_counts_before)}")
|
|
print(f"{Colors.INFO}After correction: {Colors.ENDC}{dict(geom_counts_after)}")
|
|
|
|
|
|
# Return the number of modifications
|
|
return modifications - corrected
|
|
|
|
except Exception as e:
|
|
print(f"{Colors.ERROR}Fix geometry error in the shapefile: {Colors.ENDC}"
|
|
f"{safe_relpath(input_shp)}{Colors.ERROR}, code: {Colors.ENDC}{e}")
|
|
return -1
|
|
|
|
#################################################################################################
|
|
def ThtoQGis(pathshp, outputspath):
|
|
|
|
# Check if areas, lines, points2d and outline shapefiles exists...
|
|
|
|
# Check if Outputs path exists
|
|
if not os.path.exists(outputspath):
|
|
print (f"{Colors.WARNING}WARNING: {Colors.ENDC}{safe_relpath(outputspath)}{Colors.WARNING} does not exist, I am creating it...")
|
|
os.mkdir(outputspath)
|
|
|
|
modifications = 0
|
|
if os.path.isfile(pathshp + 'areas2d.shp') :
|
|
file_list = ['outline2d', 'lines2d', 'areas2d', 'points2d']
|
|
areaOK = True
|
|
|
|
elif os.path.isfile(pathshp + 'walls3d.shp') :
|
|
file_list = ['outline2d', 'lines2d', 'points2d', 'walls3d']
|
|
areaOK = False
|
|
|
|
else :
|
|
file_list = ['outline2d', 'lines2d', 'points2d']
|
|
areaOK = False
|
|
|
|
print(f"{Colors.HEADER}{Colors.UNDERLINE}Step 1: Test files and convert to GPKG format in the folder:{Colors.ENDC} {safe_relpath(outputspath)}")
|
|
|
|
for fname in file_list:
|
|
print(f"{Colors.HEADER}Working with file: {Colors.ENDC}{fname}.shp")
|
|
|
|
if not os.path.isfile(pathshp + fname + '.shp'):
|
|
if fname == 'areas2d':
|
|
areaOK = False
|
|
|
|
else:
|
|
print(f"{Colors.ERROR}ERROR the file {Colors.ENDC}{(str(pathshp + fname + '.shp'))}{Colors.ERROR} does not exist'{Colors.ENDC}")
|
|
return False
|
|
|
|
err = count_topology_errors(pathshp + fname + '.shp')
|
|
|
|
if err[1] == -1 : return False
|
|
|
|
if err[1] != 0 :
|
|
modifications += fix_geometries(pathshp + fname + '.shp', pathshp + fname + '_fixed.shp')
|
|
|
|
err2 = count_topology_errors(pathshp + fname + '_fixed.shp')
|
|
|
|
if err2[1] == -1 : return False
|
|
|
|
if err2[1] == 0 : shp2gpkg(pathshp, fname + "_fixed", outputspath, fname)
|
|
|
|
else :
|
|
print(f'{Colors.ERROR}ERROR: in file {Colors.ENDC}{(str(pathshp + fname + '.shp'))} {Colors.ERROR} please fix it manually with QGis... {Colors.ENDC}')
|
|
return False
|
|
|
|
else :
|
|
shp2gpkg(pathshp, fname, outputspath, fname)
|
|
|
|
|
|
print(f"{Colors.HEADER}{Colors.UNDERLINE}Step 2: Adapte drawing files for Qgis in the folder:{Colors.ENDC} {safe_relpath(outputspath)}")
|
|
#1- Read the outline shapefile
|
|
|
|
outlines = gpd.read_file(outputspath + 'outline2d.gpkg')
|
|
|
|
## Work with lines
|
|
cutLines(outputspath, outlines, outputspath)
|
|
|
|
## Work with Areas
|
|
|
|
if areaOK == True :
|
|
cutareas(outputspath, outlines, outputspath)
|
|
poly_rock(outputspath + "lines2d.gpkg", outputspath + "areas2dMasekd.gpkg")
|
|
os.remove(outputspath + "areas2d.gpkg")
|
|
os.remove(outputspath + "lines2d.gpkg")
|
|
|
|
if modifications == 0 :
|
|
print(f'{Colors.GREEN}Update point, areas and lines done without error {Colors.ENDC}')
|
|
|
|
else :
|
|
print(f'{Colors.GREEN}Update point, areas and lines done with warning {Colors.ENDC}{modifications}{Colors.GREEN} to be checked{Colors.ENDC}')
|
|
|
|
else :
|
|
os.remove(outputspath + "lines2d.gpkg")
|
|
|
|
if modifications == 0 :
|
|
print(f'{Colors.HEADER}Update point and lines done without error {Colors.ENDC}')
|
|
|
|
else :
|
|
print(f'{Colors.GREEN}Update point and lines done with warning {Colors.ENDC}{modifications}{Colors.GREEN} to be checked{Colors.ENDC}')
|
|
|
|
print(f"{Colors.HEADER}{Colors.UNDERLINE}Step 3: Adapte polygonal files for Qgis in the folder:{Colors.ENDC} {safe_relpath(outputspath)}")
|
|
|
|
if os.path.isfile(pathshp + 'stations3d.shp') :
|
|
fname = "stations3d"
|
|
shp2gpkg(globalDat.pathshp, fname , globalDat.outputspath, fname)
|
|
|
|
if os.path.isfile(pathshp + 'shots3d.shp') :
|
|
fname = "shots3d"
|
|
shp2gpkg(globalDat.pathshp, fname , globalDat.outputspath, fname)
|
|
|
|
if os.path.isfile(pathshp + 'walls3d.shp') :
|
|
fname = "walls3d"
|
|
shp2gpkg(globalDat.pathshp, fname , globalDat.outputspath, fname)
|
|
|
|
#1- Read the outline shapefile
|
|
|
|
|
|
|
|
#####################################################################################################################################
|
|
# #
|
|
# Main #
|
|
# #
|
|
#####################################################################################################################################
|
|
if __name__ == u'__main__':
|
|
###################################################
|
|
|
|
|
|
#################################################################################################
|
|
# Parse arguments #
|
|
#################################################################################################
|
|
parser = argparse.ArgumentParser(
|
|
description=f"{Colors.HEADER}Script to generate QGis (.gpkg) files from Therion (.shp) files with auto-correction if possible",
|
|
formatter_class=argparse.RawTextHelpFormatter)
|
|
parser.print_help = colored_help.__get__(parser)
|
|
parser.add_argument(
|
|
'--option',
|
|
default="auto",
|
|
choices=["auto", "manual", "test"],
|
|
help=(
|
|
f"Execution options for pyThtoQgis.py\n"
|
|
f"auto\t-> Execution from the folder {globalDat.pathshp} (défaut)\n"
|
|
f"manual\t-> Manual selection for the input folder\n"
|
|
f"test\t-> Tests fonction (debug)\n"
|
|
)
|
|
)
|
|
|
|
parser.epilog = (
|
|
f"{Colors.HEADER}to generate shp files with therion, add in .thconfig : "
|
|
f"-> {Colors.ENDC}export model -fmt esri -o Outputs/SHP/ -enc UTF-8"
|
|
)
|
|
|
|
# Analyser les arguments de ligne de commande
|
|
args = parser.parse_args()
|
|
|
|
if os.name == 'posix': os.system('clear') # Linux, MacOS
|
|
elif os.name == 'nt': os.system('cls')# Windows
|
|
else: print("\n" * 100)
|
|
|
|
print(f'{Colors.HEADER}*********************************************************************************************************')
|
|
print(f'{Colors.HEADER}Script to generate QGis (.gpkg) files from Therion (.shp) files with auto-correction if possible')
|
|
print(f'{Colors.HEADER} Original written by X. Robert, ISTerre : {Colors.ENDC}October 2022')
|
|
print(f'{Colors.HEADER} Updated by : {Colors.ENDC}alexandre.pont@yahoo.fr')
|
|
print(f'{Colors.HEADER} Version : {Colors.ENDC}{globalDat.Version}')
|
|
|
|
|
|
if args.option == "auto" :
|
|
|
|
print(f'{Colors.HEADER} auto mode')
|
|
print(f'{Colors.HEADER} input folder : {Colors.ENDC}{globalDat.pathshp}')
|
|
print(f'{Colors.HEADER} output folder : {Colors.ENDC}{globalDat.outputspath}')
|
|
print(f'{Colors.HEADER}*********************************************************************************************************')
|
|
|
|
ThtoQGis(globalDat.pathshp, globalDat.outputspath)
|
|
|
|
|
|
elif args.option == "manual" :
|
|
root = tk.Tk()
|
|
root.withdraw() # Cacher la fenêtre principale de Tkinter
|
|
input_folder_name = filedialog.askdirectory( title="Choose the shp folder")
|
|
|
|
if not input_folder_name:
|
|
print(f"{Colors.ERROR}No folder selected. The program will terminate")
|
|
sys.exit()
|
|
|
|
|
|
input_folder = input_folder_name + "\\"
|
|
print(f'{Colors.HEADER} manual mode')
|
|
print(f'{Colors.HEADER} input folder : {Colors.ENDC}{safe_relpath(input_folder)}')
|
|
print(f'{Colors.HEADER} output folder : {Colors.ENDC}{globalDat.outputspath}')
|
|
print(f'{Colors.HEADER}*********************************************************************************************************')
|
|
|
|
ThtoQGis(input_folder, globalDat.outputspath)
|
|
|
|
|
|
elif args.option == "test" :
|
|
print(f'{Colors.HEADER} test mode')
|
|
print(f'{Colors.HEADER} input folder : {Colors.ENDC}{globalDat.pathshp}')
|
|
print(f'{Colors.HEADER} output folder : {Colors.ENDC}{globalDat.outputspath}')
|
|
print(f'{Colors.HEADER}*********************************************************************************************************')
|
|
|
|
fname = "stations3d"
|
|
shp2gpkg(globalDat.pathshp, fname , globalDat.outputspath, fname)
|
|
|
|
fname = "shots3d"
|
|
shp2gpkg(globalDat.pathshp, fname , globalDat.outputspath, fname)
|
|
|
|
fname = "walls3d"
|
|
shp2gpkg(globalDat.pathshp, fname , globalDat.outputspath, fname)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|