quickinteg 2.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- quickinteg/__about__.py +34 -0
- quickinteg/__init__.py +0 -0
- quickinteg/cfg_reader.py +104 -0
- quickinteg/cli.py +144 -0
- quickinteg/excel_shortcut.py +163 -0
- quickinteg/external/__init__.py +0 -0
- quickinteg/mcd_check.py +368 -0
- quickinteg/processing_logging.py +107 -0
- quickinteg/rop_grace.py +585 -0
- quickinteg/spatialiteio.py +646 -0
- quickinteg-2.5.0.dist-info/METADATA +75 -0
- quickinteg-2.5.0.dist-info/RECORD +16 -0
- quickinteg-2.5.0.dist-info/WHEEL +5 -0
- quickinteg-2.5.0.dist-info/entry_points.txt +6 -0
- quickinteg-2.5.0.dist-info/licenses/LICENSE +674 -0
- quickinteg-2.5.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
#! python3 # noqa: E265
|
|
2
|
+
|
|
3
|
+
################################################################################
|
|
4
|
+
# This file is part of quickinteg.
|
|
5
|
+
|
|
6
|
+
# quickinteg is free software: you can redistribute it and/or modify
|
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
# (at your option) any later version.
|
|
10
|
+
#
|
|
11
|
+
# quickinteg is distributed in the hope that it will be useful,
|
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
# GNU General Public License for more details.
|
|
15
|
+
#
|
|
16
|
+
# You should have received a copy of the GNU General Public License
|
|
17
|
+
# along with quickinteg. If not, see <https://www.gnu.org/licenses/>.
|
|
18
|
+
################################################################################
|
|
19
|
+
|
|
20
|
+
# ############################################################################
|
|
21
|
+
# ########## Libraries #############
|
|
22
|
+
# ##################################
|
|
23
|
+
|
|
24
|
+
# standard library
|
|
25
|
+
import logging
|
|
26
|
+
import os
|
|
27
|
+
import sqlite3
|
|
28
|
+
import string
|
|
29
|
+
import subprocess
|
|
30
|
+
import time
|
|
31
|
+
import unicodedata
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
|
|
34
|
+
from . import processing_logging
|
|
35
|
+
|
|
36
|
+
# 3rd party - try embedded first (for QGIS), fallback to normal import (for CLI)
|
|
37
|
+
try:
|
|
38
|
+
from .external import xlsxwriter
|
|
39
|
+
except ImportError:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
# ############################################################################
|
|
43
|
+
# ########## Globals ###############
|
|
44
|
+
# ##################################
|
|
45
|
+
|
|
46
|
+
module_logger = logging.getLogger(__name__)
|
|
47
|
+
module_logger.setLevel(logging.DEBUG)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ############################################################################
|
|
51
|
+
# ########## Functions #############
|
|
52
|
+
# ##################################
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def log(message, logLevel="info"):
|
|
56
|
+
processing_logging.log(str(message), module_logger, logLevel)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def spatialite_connect(*args, **kwargs):
|
|
60
|
+
"""returns a dbapi2.Connection to a SpatiaLite db
|
|
61
|
+
using the "mod_spatialite" extension (python3)
|
|
62
|
+
|
|
63
|
+
WARNING !! it messes up subprocesses on MacOS. Use sqlite3.connect when possible"""
|
|
64
|
+
import sqlite3
|
|
65
|
+
|
|
66
|
+
con = sqlite3.connect(*args, **kwargs)
|
|
67
|
+
con.enable_load_extension(True)
|
|
68
|
+
cur = con.cursor()
|
|
69
|
+
libs = [
|
|
70
|
+
# SpatiaLite >= 4.2 and Sqlite >= 3.7.17, should work on all platforms
|
|
71
|
+
("mod_spatialite", "sqlite3_modspatialite_init"),
|
|
72
|
+
# SpatiaLite >= 4.2 and Sqlite < 3.7.17 (Travis)
|
|
73
|
+
("mod_spatialite.so", "sqlite3_modspatialite_init"),
|
|
74
|
+
# SpatiaLite < 4.2 (linux)
|
|
75
|
+
("libspatialite.so", "sqlite3_extension_init"),
|
|
76
|
+
]
|
|
77
|
+
found = False
|
|
78
|
+
for lib, entry_point in libs:
|
|
79
|
+
try:
|
|
80
|
+
cur.execute(f"select load_extension('{lib}', '{entry_point}')")
|
|
81
|
+
except sqlite3.OperationalError as err:
|
|
82
|
+
if __debug__:
|
|
83
|
+
log(err, "debug")
|
|
84
|
+
continue
|
|
85
|
+
else:
|
|
86
|
+
found = True
|
|
87
|
+
break
|
|
88
|
+
if not found:
|
|
89
|
+
raise RuntimeError("Cannot find any suitable spatialite module")
|
|
90
|
+
cur.close()
|
|
91
|
+
con.enable_load_extension(False)
|
|
92
|
+
return con
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def create_spatialite_db(file_name):
|
|
96
|
+
if Path(file_name).exists():
|
|
97
|
+
raise ValueError(f"La base de donnée {file_name} existe déjà")
|
|
98
|
+
else:
|
|
99
|
+
# Trick to create an empty spatialite db with ogr2ogr
|
|
100
|
+
cmd = f'echo \'{{"type": "FeatureCollection","features": []}}\' | ogr2ogr -f SQLite -dsco SPATIALITE=YES "{file_name}" /vsistdin/ -nln _qi_dummy'
|
|
101
|
+
|
|
102
|
+
# Run the process and wait for it to complete
|
|
103
|
+
result = subprocess.run(
|
|
104
|
+
cmd,
|
|
105
|
+
stdout=subprocess.PIPE,
|
|
106
|
+
stderr=subprocess.PIPE,
|
|
107
|
+
encoding="utf-8",
|
|
108
|
+
shell=True,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def table_exists(db_file, tbl_name):
|
|
113
|
+
"""
|
|
114
|
+
Return true if table "tbl_name" exists in base "db_file"
|
|
115
|
+
"""
|
|
116
|
+
with sqlite3.connect(db_file) as con:
|
|
117
|
+
# Use a normal try/finally block to ensure the cursor is closed.
|
|
118
|
+
try:
|
|
119
|
+
cur = con.cursor()
|
|
120
|
+
# Use parameterized query to prevent SQL injection
|
|
121
|
+
req = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?;"
|
|
122
|
+
cur.execute(req, (tbl_name,))
|
|
123
|
+
res = cur.fetchone()[0]
|
|
124
|
+
finally:
|
|
125
|
+
# Ensure the cursor is closed after use
|
|
126
|
+
cur.close()
|
|
127
|
+
|
|
128
|
+
# The connection will be closed automatically due to the 'with' statement.
|
|
129
|
+
return res != 0
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def column_exists(db_file, tbl_name, column_name):
|
|
133
|
+
"""
|
|
134
|
+
Return true if column "column_name" exists in table "tbl_name"
|
|
135
|
+
"""
|
|
136
|
+
if table_exists(db_file, tbl_name):
|
|
137
|
+
with sqlite3.connect(db_file) as con:
|
|
138
|
+
cur = con.cursor()
|
|
139
|
+
req_search_for_column = (
|
|
140
|
+
f"PRAGMA table_info('{tbl_name}');" # Use parameter substitution for safety
|
|
141
|
+
)
|
|
142
|
+
cur.execute(req_search_for_column)
|
|
143
|
+
results = cur.fetchall()
|
|
144
|
+
columns = [row[1] for row in results] # second column of PRAGMA INFO is column name
|
|
145
|
+
return column_name in columns
|
|
146
|
+
|
|
147
|
+
return False # If the table does not exist, return False
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def add_column(db_file, tbl_name, column_name):
|
|
151
|
+
if table_exists(db_file, tbl_name) and not column_exists(db_file, tbl_name, column_name):
|
|
152
|
+
log(f"Ajout de la colonne {column_name} à la table {tbl_name}", "debug")
|
|
153
|
+
|
|
154
|
+
with sqlite3.connect(db_file) as con:
|
|
155
|
+
cur = con.cursor()
|
|
156
|
+
|
|
157
|
+
# Execute the ALTER TABLE statement to add the column
|
|
158
|
+
req = f"ALTER TABLE {tbl_name} ADD COLUMN {column_name}"
|
|
159
|
+
cur.execute(req)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def get_srs(db_file, tbl_name):
|
|
163
|
+
"""
|
|
164
|
+
Return CRS of the table
|
|
165
|
+
"""
|
|
166
|
+
with sqlite3.connect(db_file) as con:
|
|
167
|
+
cur = con.cursor()
|
|
168
|
+
log(f"Vérification du SRS de la table {tbl_name} dans la base", "debug")
|
|
169
|
+
req = f"SELECT srid FROM geometry_columns WHERE f_table_name = '{tbl_name}';"
|
|
170
|
+
cur.execute(req)
|
|
171
|
+
out_sql = cur.fetchone()
|
|
172
|
+
|
|
173
|
+
if out_sql is None:
|
|
174
|
+
log("La table n'a pas de géometrie dans spatialite.", "debug")
|
|
175
|
+
return None
|
|
176
|
+
else:
|
|
177
|
+
srid = out_sql[0]
|
|
178
|
+
return f"EPSG:{srid}"
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def geom_type(db_file, tbl_name):
|
|
182
|
+
"""
|
|
183
|
+
Return geom type in text. Returns None in no geometry
|
|
184
|
+
Spatialites geoms :
|
|
185
|
+
1 = POINT
|
|
186
|
+
2 = LINESTRING
|
|
187
|
+
3 = POLYGON
|
|
188
|
+
4 = MULTIPOINT
|
|
189
|
+
5 = MULTILINESTRING
|
|
190
|
+
6 = MULTIPOLYGON
|
|
191
|
+
7 = GEOMETRYCOLLECTION
|
|
192
|
+
"""
|
|
193
|
+
with sqlite3.connect(db_file) as con: # Use context manager for the connection
|
|
194
|
+
cur = con.cursor() # Cursor managed by connection's context manager
|
|
195
|
+
log(f"Vérification de la géometrie de la table {tbl_name} dans la base", "debug")
|
|
196
|
+
|
|
197
|
+
# Using string formatting here because SQLite does not support parameter
|
|
198
|
+
# substitution for table names. Ensure that 'tbl_name' is safe to include.
|
|
199
|
+
req = f"SELECT geometry_type FROM geometry_columns WHERE f_table_name = '{tbl_name}';"
|
|
200
|
+
cur.execute(req)
|
|
201
|
+
out_sql = cur.fetchone()
|
|
202
|
+
|
|
203
|
+
if out_sql is None:
|
|
204
|
+
log("La table n'a pas de géometrie dans spatialite.", "debug")
|
|
205
|
+
return None
|
|
206
|
+
else:
|
|
207
|
+
dict_geoms = {
|
|
208
|
+
1: "POINT",
|
|
209
|
+
2: "LINESTRING",
|
|
210
|
+
3: "POLYGON",
|
|
211
|
+
4: "MULTIPOINT",
|
|
212
|
+
5: "MULTILINESTRING",
|
|
213
|
+
6: "MULTIPOLYGON",
|
|
214
|
+
7: "GEOMETRYCOLLECTION",
|
|
215
|
+
}
|
|
216
|
+
geom_num = out_sql[0]
|
|
217
|
+
try:
|
|
218
|
+
return dict_geoms[geom_num]
|
|
219
|
+
except IndexError:
|
|
220
|
+
raise ValueError("Undefined geometry (%s)" % (geom_num))
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def import_file_to_spl(ogr_target, shp_csv_file, tbl_name, options="", prefix=None):
|
|
224
|
+
"""
|
|
225
|
+
Import un fichier shp ou csv vers une base spatialite avec ogr2ogr.
|
|
226
|
+
Le nom de la table sera le nom du fichier en minuscule sans l'extension
|
|
227
|
+
|
|
228
|
+
option : ajouter des options à la commande ogr2ogr
|
|
229
|
+
(par exemple -skipfailures)
|
|
230
|
+
prefix : si différent de '', ajoutera une colonne "import_prefix" à la
|
|
231
|
+
table. Chaque enregistrement prendra la valeur donnée en paramètre.
|
|
232
|
+
"""
|
|
233
|
+
log(
|
|
234
|
+
"# Import du fichier %s dans la table %s de la bdd %s"
|
|
235
|
+
% (shp_csv_file, tbl_name, ogr_target)
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
file_type = os.path.splitext(os.path.basename(shp_csv_file))[1]
|
|
239
|
+
if file_type not in (".csv", ".shp"):
|
|
240
|
+
raise ValueError("Le type de fichier %s n'est pas supporté" % (file_type))
|
|
241
|
+
|
|
242
|
+
# Construction de la commande ogr2ogr
|
|
243
|
+
# On commence par les options passées en paramètre
|
|
244
|
+
ogr_options = options
|
|
245
|
+
|
|
246
|
+
if file_type == ".shp":
|
|
247
|
+
# ajout d'une option OGR
|
|
248
|
+
ogr_options += " -dim XY"
|
|
249
|
+
if not table_exists(ogr_target, tbl_name):
|
|
250
|
+
log(
|
|
251
|
+
"La table " + tbl_name + " n'existe pas. Elle sera créée par org2ogr",
|
|
252
|
+
logLevel="debug",
|
|
253
|
+
)
|
|
254
|
+
# On ajoute l'option pour une geometrie multi et on donne le nom "geom" pour la
|
|
255
|
+
# colonne de géometrie (sinon nom arbitraire OGR moche)
|
|
256
|
+
# ajout d'une option OGR
|
|
257
|
+
ogr_options += " -nlt PROMOTE_TO_MULTI -lco GEOMETRY_NAME=geom"
|
|
258
|
+
# Sinon la table existe, on vérifie sa geometry pour les parametrès d'import OGR
|
|
259
|
+
else:
|
|
260
|
+
log(
|
|
261
|
+
"La table %s existe déjà. Les données seront ajoutées" % (tbl_name),
|
|
262
|
+
logLevel="debug",
|
|
263
|
+
)
|
|
264
|
+
geom_type_ = geom_type(ogr_target, tbl_name)
|
|
265
|
+
if geom_type_ in ("POINT", "LINESTRING", "POLYGON"):
|
|
266
|
+
pass
|
|
267
|
+
elif geom_type_ in ("MULTIPOINT", "MULTILINESTRING", "MULTIPOLYGON"):
|
|
268
|
+
# ajout d'une option OGR
|
|
269
|
+
log(
|
|
270
|
+
"La table de destination à une géometrie multi. "
|
|
271
|
+
"Ajout du paramètre PROMOTE_TO_MULTI",
|
|
272
|
+
logLevel="debug",
|
|
273
|
+
)
|
|
274
|
+
ogr_options += " -nlt PROMOTE_TO_MULTI"
|
|
275
|
+
ogr_options += ' -t_srs "%s"' % (get_srs(ogr_target, tbl_name))
|
|
276
|
+
# Other option is :
|
|
277
|
+
# ogr_options += ' -nlt %s' %(geom_type_)
|
|
278
|
+
# but this option will create invalid geoms without warning
|
|
279
|
+
elif file_type == ".csv":
|
|
280
|
+
# ajout d'une option OGR
|
|
281
|
+
ogr_options += " -oo EMPTY_STRING_AS_NULL=YES"
|
|
282
|
+
|
|
283
|
+
if prefix is not None and prefix != "":
|
|
284
|
+
if table_exists(ogr_target, tbl_name) and not column_exists(
|
|
285
|
+
ogr_target, tbl_name, "import_prefix"
|
|
286
|
+
):
|
|
287
|
+
add_column(ogr_target, tbl_name, "import_prefix")
|
|
288
|
+
|
|
289
|
+
# S'il y a un préfixe on ajoute une option -sql pour ajouter une colonne
|
|
290
|
+
shp_name_without_extension = os.path.splitext(os.path.basename(shp_csv_file))[0]
|
|
291
|
+
# ajout d'une option OGR
|
|
292
|
+
ogr_options += " -sql \"SELECT *, '%s' AS import_prefix FROM %s\"" % (
|
|
293
|
+
prefix,
|
|
294
|
+
shp_name_without_extension,
|
|
295
|
+
)
|
|
296
|
+
# Avec l'ajout de cette option, la requête plante si le nom du shp contient des charactères spéciaux
|
|
297
|
+
# On gère alors le cas proprement avec une exception
|
|
298
|
+
# Liste des charactères interdits (underscore _ autorisé)
|
|
299
|
+
invalidChars = set(string.punctuation.replace("_", ""))
|
|
300
|
+
if any(char in invalidChars for char in shp_name_without_extension):
|
|
301
|
+
raise ValueError(
|
|
302
|
+
"Impossible d'ajouter un préfixe d'import si les "
|
|
303
|
+
"noms de shp contiennent des caractères spéciaux. "
|
|
304
|
+
"Essayer en renommant les shapes à importer."
|
|
305
|
+
)
|
|
306
|
+
# On assemble la commande d'appel à OGR2OGR
|
|
307
|
+
cmd = f'ogr2ogr -f sqlite "{ogr_target}" "{shp_csv_file}" -nln {tbl_name} -append {ogr_options}'
|
|
308
|
+
cmd_skipfailures = f'ogr2ogr -f sqlite "{ogr_target}" "{shp_csv_file}" -nln {tbl_name} -append {ogr_options} -skipfailures'
|
|
309
|
+
log("OGR2OGR : %s" % (cmd), logLevel="debug")
|
|
310
|
+
# Si Erreur ogr, on ressaie avec skipfailures
|
|
311
|
+
# os.system(f'{cmd} || {{ echo "toto"; {cmd_skipfailures}; }}')
|
|
312
|
+
|
|
313
|
+
# Run the process and wait for it to complete
|
|
314
|
+
result = subprocess.run(
|
|
315
|
+
cmd,
|
|
316
|
+
stdout=subprocess.PIPE,
|
|
317
|
+
stderr=subprocess.PIPE,
|
|
318
|
+
encoding="utf-8",
|
|
319
|
+
shell=True,
|
|
320
|
+
)
|
|
321
|
+
# Access the stdout and stderr
|
|
322
|
+
stdout_output = result.stdout
|
|
323
|
+
stderr_output = result.stderr
|
|
324
|
+
|
|
325
|
+
# Get the exit status
|
|
326
|
+
exit_status = result.returncode
|
|
327
|
+
if stderr_output:
|
|
328
|
+
log(stderr_output, logLevel="warning")
|
|
329
|
+
if exit_status != 0:
|
|
330
|
+
log(
|
|
331
|
+
"L'import ogr2ogr a échoué. Nouvelle tentative avec -skipfailures. Une partie des donnée sera ignorée.",
|
|
332
|
+
logLevel="warning",
|
|
333
|
+
)
|
|
334
|
+
result = subprocess.run(
|
|
335
|
+
cmd_skipfailures,
|
|
336
|
+
stdout=subprocess.PIPE,
|
|
337
|
+
stderr=subprocess.PIPE,
|
|
338
|
+
encoding="utf-8",
|
|
339
|
+
shell=True,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def import_folder_to_spl(
|
|
344
|
+
folder,
|
|
345
|
+
ogr_target=None,
|
|
346
|
+
prefix=None,
|
|
347
|
+
include_csv=False,
|
|
348
|
+
ignore_grace_list=True,
|
|
349
|
+
recursive=False,
|
|
350
|
+
):
|
|
351
|
+
if recursive:
|
|
352
|
+
shp_glob_pattern = "**/*.shp"
|
|
353
|
+
csv_glob_pattern = "**/*.csv"
|
|
354
|
+
else:
|
|
355
|
+
shp_glob_pattern = "*.shp"
|
|
356
|
+
csv_glob_pattern = "*.csv"
|
|
357
|
+
|
|
358
|
+
if ogr_target is None:
|
|
359
|
+
log("Aucune base de donnée renseignée. Création d'une nouvelle BDD")
|
|
360
|
+
ogr_target_ = str(Path(folder) / "base.sqlite")
|
|
361
|
+
create_spatialite_db(ogr_target_)
|
|
362
|
+
elif Path(ogr_target).exists():
|
|
363
|
+
# log("Création d'une sauvegarde de la BDD")
|
|
364
|
+
# copyfile(ogr_target, ogr_target + "_backup")
|
|
365
|
+
ogr_target_ = ogr_target
|
|
366
|
+
else:
|
|
367
|
+
raise ValueError("La base de donnée %s n'existe pas" % (ogr_target))
|
|
368
|
+
|
|
369
|
+
if include_csv:
|
|
370
|
+
list_files = list(Path(folder).glob(shp_glob_pattern)) + list(
|
|
371
|
+
Path(folder).glob(csv_glob_pattern)
|
|
372
|
+
)
|
|
373
|
+
log("Option CSV activée. Liste des fichier à importer :")
|
|
374
|
+
for i in list_files:
|
|
375
|
+
log(str(i))
|
|
376
|
+
else:
|
|
377
|
+
list_files = list(Path(folder).glob(shp_glob_pattern))
|
|
378
|
+
log("Liste des fichier à importer :")
|
|
379
|
+
for i in list_files:
|
|
380
|
+
log(str(i))
|
|
381
|
+
|
|
382
|
+
for file in list_files:
|
|
383
|
+
if ignore_grace_list and file.name.startswith("l_"):
|
|
384
|
+
log(
|
|
385
|
+
f"le fichier {os.path.basename(file)} sera ignoré car il correspond une liste de valeur GRACE THD",
|
|
386
|
+
logLevel="debug",
|
|
387
|
+
)
|
|
388
|
+
else:
|
|
389
|
+
# On prend le nom de fichier, sans l'extension et en minuscule, normalisé
|
|
390
|
+
# sera le nom de la table sqlite
|
|
391
|
+
tbl_name = file.stem.lower()
|
|
392
|
+
tbl_name = (
|
|
393
|
+
unicodedata.normalize("NFD", tbl_name).encode("ascii", "ignore").decode("utf-8")
|
|
394
|
+
)
|
|
395
|
+
# On remplace les espaces par _ dans le nom
|
|
396
|
+
tbl_name = tbl_name.replace(" ", "_")
|
|
397
|
+
if (not prefix) and recursive:
|
|
398
|
+
prefix_ = Path(file).parent.stem
|
|
399
|
+
else:
|
|
400
|
+
prefix_ = prefix
|
|
401
|
+
import_file_to_spl(ogr_target_, file, tbl_name, prefix=prefix_)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def extract_table_to_file(
|
|
405
|
+
spl_db, view_name, dir_name=None, file_name=None, sheet_name=None, file_type="csv"
|
|
406
|
+
):
|
|
407
|
+
log(
|
|
408
|
+
f"Extraction de la vue {view_name} au format {file_type} vers {dir_name}/{file_name}.{file_type}"
|
|
409
|
+
)
|
|
410
|
+
start_time = time.time()
|
|
411
|
+
ogr_mapping = {
|
|
412
|
+
"csv": "-f CSV",
|
|
413
|
+
"xlsx": "-f XLSX",
|
|
414
|
+
"shp": "-f 'ESRI Shapefile'",
|
|
415
|
+
"sqlite": "-f sqlite -dsco SPATIALITE=YES",
|
|
416
|
+
}
|
|
417
|
+
if not dir_name:
|
|
418
|
+
dir_name = Path(spl_db).parent
|
|
419
|
+
if not file_name:
|
|
420
|
+
file_name = view_name
|
|
421
|
+
if not sheet_name:
|
|
422
|
+
sheet_name = view_name
|
|
423
|
+
|
|
424
|
+
targetfilepath = str(Path(dir_name) / (file_name + "." + file_type))
|
|
425
|
+
# if target file is the source DB, we do not overwrite to preserve database structure
|
|
426
|
+
# this allow some template tricks
|
|
427
|
+
if (
|
|
428
|
+
Path(spl_db).resolve() # source db
|
|
429
|
+
== (Path(dir_name) / (file_name + "." + file_type)).resolve() # target file
|
|
430
|
+
):
|
|
431
|
+
overwrite_ = "-append"
|
|
432
|
+
else:
|
|
433
|
+
overwrite_ = "-overwrite"
|
|
434
|
+
|
|
435
|
+
ogr_command = f'ogr2ogr {ogr_mapping[file_type]} "{targetfilepath}" "{spl_db}" -sql "SELECT * FROM {view_name}" -nln {sheet_name} {overwrite_}'
|
|
436
|
+
log(ogr_command, logLevel="debug")
|
|
437
|
+
|
|
438
|
+
# Run the process and wait for it to complete
|
|
439
|
+
result = subprocess.run(
|
|
440
|
+
ogr_command,
|
|
441
|
+
stdout=subprocess.PIPE,
|
|
442
|
+
stderr=subprocess.PIPE,
|
|
443
|
+
encoding="utf-8",
|
|
444
|
+
shell=True,
|
|
445
|
+
)
|
|
446
|
+
# Access the stdout and stderr
|
|
447
|
+
stdout_output = result.stdout
|
|
448
|
+
stderr_output = result.stderr
|
|
449
|
+
|
|
450
|
+
# Get the exit status
|
|
451
|
+
if stderr_output:
|
|
452
|
+
log(stderr_output, logLevel="warning")
|
|
453
|
+
|
|
454
|
+
end_time = time.time()
|
|
455
|
+
duration = end_time - start_time
|
|
456
|
+
log(f"Elapsed time: {duration:.2f} seconds")
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def execute_query_to_spl(sql_query, spl_db):
|
|
460
|
+
con = spatialite_connect(spl_db)
|
|
461
|
+
# on execute le fichier sql en tant que script
|
|
462
|
+
log("Exécution du script sur la base de donnée")
|
|
463
|
+
with con:
|
|
464
|
+
cur = con.cursor()
|
|
465
|
+
cur.executescript(sql_query)
|
|
466
|
+
con.close()
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def execute_file_to_spl(filename, spl_db):
|
|
470
|
+
"""Exécute le script .sql cible dans la base de donnée spl_db.
|
|
471
|
+
si le fichier est un dossier, execute chaque fichier .sql dans l'ordre alphabétique
|
|
472
|
+
"""
|
|
473
|
+
# si le fichier est un fichier .sql
|
|
474
|
+
if Path(filename).suffix == ".sql":
|
|
475
|
+
# on lit le fichier sql
|
|
476
|
+
log("Lecture du fichier %s" % (filename))
|
|
477
|
+
with open(filename) as f:
|
|
478
|
+
sql_script = f.read()
|
|
479
|
+
log("Connexion à la base de donnée %s" % (spl_db))
|
|
480
|
+
con = spatialite_connect(spl_db)
|
|
481
|
+
# on execute le fichier sql en tant que script
|
|
482
|
+
log("Exécution du script sur la base de donnée")
|
|
483
|
+
with con:
|
|
484
|
+
cur = con.cursor()
|
|
485
|
+
cur.executescript(sql_script)
|
|
486
|
+
con.close()
|
|
487
|
+
# si le fichier est un dossier, on liste les fichier *.sql et on execute la fonction
|
|
488
|
+
elif Path(filename).is_dir():
|
|
489
|
+
list_file_sql = [file for file in Path(filename).glob("*.sql")]
|
|
490
|
+
# tri par ordre alphabétique
|
|
491
|
+
list_file_sql.sort()
|
|
492
|
+
log("Le fichier cible est un dossier. Liste des fichiers *.sql trouvés dans ce dossier : ")
|
|
493
|
+
for f in list_file_sql:
|
|
494
|
+
log(f)
|
|
495
|
+
for f in list_file_sql:
|
|
496
|
+
execute_file_to_spl(f, spl_db)
|
|
497
|
+
# sinon on renvoit une erreur
|
|
498
|
+
else:
|
|
499
|
+
raise ValueError("Le fichier cible n'est ni un fichier *.sql ni un dossier")
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def export_preset_tables(spatialite_db, config_table_name=None):
|
|
503
|
+
"""
|
|
504
|
+
On récupère le contenu de la table config_table_name
|
|
505
|
+
Cette table doit avoir le format suivant :
|
|
506
|
+
view_name VARCHAR(254), --> nom de la table ou vue à exporter
|
|
507
|
+
dest_file VARCHAR(254), --> nom du fichier de destination sans extention
|
|
508
|
+
dest_sheet VARCHAR(254), --> nom de l'onglet du fichier (si plusieurs fois le même fichier en xlsx)
|
|
509
|
+
dest_folder VARCHAR(254), --> chemin du sous-dossier d'export (chemin relatif). Doit commencer par "/"
|
|
510
|
+
file_type VARCHAR(254) --> type (csv, xlsx, shp)
|
|
511
|
+
"""
|
|
512
|
+
|
|
513
|
+
log("Export des vues calculées")
|
|
514
|
+
|
|
515
|
+
# Default table of views to export changed name
|
|
516
|
+
if not config_table_name:
|
|
517
|
+
if table_exists(spatialite_db, "tactis_list_views_to_export"):
|
|
518
|
+
config_table_name = "tactis_list_views_to_export"
|
|
519
|
+
elif table_exists(spatialite_db, "_qi_exports"):
|
|
520
|
+
config_table_name = "_qi_exports"
|
|
521
|
+
|
|
522
|
+
with sqlite3.connect(spatialite_db) as con:
|
|
523
|
+
con.row_factory = sqlite3.Row
|
|
524
|
+
cur = con.cursor()
|
|
525
|
+
req = "SELECT * FROM %s;" % (config_table_name)
|
|
526
|
+
cur.execute(req)
|
|
527
|
+
rows = cur.fetchall()
|
|
528
|
+
|
|
529
|
+
for row in rows:
|
|
530
|
+
# On traite ligne par ligne.
|
|
531
|
+
# Récupération des paramètres
|
|
532
|
+
file_name = row["dest_file"]
|
|
533
|
+
view_name = row["view_name"]
|
|
534
|
+
sheet_name = row["dest_sheet"]
|
|
535
|
+
file_type = row["file_type"]
|
|
536
|
+
# On construit le chemin absolu dans dest_folder
|
|
537
|
+
dest_folder = str(
|
|
538
|
+
Path(spatialite_db).parents[0].joinpath(row["dest_folder"].strip("/").strip("\\"))
|
|
539
|
+
)
|
|
540
|
+
if not os.path.exists(dest_folder):
|
|
541
|
+
os.makedirs(dest_folder)
|
|
542
|
+
extract_table_to_file(
|
|
543
|
+
spatialite_db,
|
|
544
|
+
view_name,
|
|
545
|
+
dir_name=dest_folder,
|
|
546
|
+
file_name=file_name,
|
|
547
|
+
sheet_name=sheet_name,
|
|
548
|
+
file_type=file_type,
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def check_preset_tables(spatialite_db, config_table_name):
|
|
553
|
+
"""
|
|
554
|
+
Appelle les tables/vues définies dans la table "config_table_name" et
|
|
555
|
+
vérifie que leur appel de génère pas une erreur SQL. Fonction à effectuer
|
|
556
|
+
sur un template vide, sinon il y aura un temps de calcul des vues selon
|
|
557
|
+
la quantité de données.
|
|
558
|
+
|
|
559
|
+
Rappel structure table "config_table_name"
|
|
560
|
+
view_name VARCHAR(254), --> nom de la table ou vue à exporter
|
|
561
|
+
dest_file VARCHAR(254), --> nom du fichier de destination sans extention
|
|
562
|
+
dest_sheet VARCHAR(254), --> nom de l'onglet du fichier (si plusieurs fois le même fichier en xlsx)
|
|
563
|
+
dest_folder VARCHAR(254), --> chemin du sous-dossier d'export (chemin relatif). Doit commencer par "/"
|
|
564
|
+
file_type VARCHAR(254) --> type (csv, xlsx, shp)
|
|
565
|
+
"""
|
|
566
|
+
log("Vérification de la validité des tables/vues listées dans la table%s" % (config_table_name))
|
|
567
|
+
con = spatialite_connect(spatialite_db)
|
|
568
|
+
con.row_factory = sqlite3.Row
|
|
569
|
+
cur = con.cursor()
|
|
570
|
+
req = "SELECT * FROM %s;" % (config_table_name)
|
|
571
|
+
cur.execute(req)
|
|
572
|
+
rows = cur.fetchall()
|
|
573
|
+
# Fermeture de la connexion sql
|
|
574
|
+
cur.close()
|
|
575
|
+
con.close()
|
|
576
|
+
for row in rows:
|
|
577
|
+
view_name = row["view_name"]
|
|
578
|
+
try:
|
|
579
|
+
con = spatialite_connect(spatialite_db)
|
|
580
|
+
cur = con.cursor()
|
|
581
|
+
req = "SELECT * FROM '%s'" % (view_name)
|
|
582
|
+
cur.execute(req)
|
|
583
|
+
cur.close()
|
|
584
|
+
con.close()
|
|
585
|
+
log("%s : OK" % (view_name))
|
|
586
|
+
except Exception as e:
|
|
587
|
+
log("%s : NOK" % (view_name), "warning")
|
|
588
|
+
log(e, "warning")
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def delete_rows(db_file, value, table=None, column="import_prefix", condition="CONTIENT"):
|
|
592
|
+
con = spatialite_connect(db_file)
|
|
593
|
+
cur = con.cursor()
|
|
594
|
+
req = " PRAGMA foreign_keys = off; "
|
|
595
|
+
cur.executescript(req)
|
|
596
|
+
|
|
597
|
+
if table is None:
|
|
598
|
+
tables = list_tables(db_file, column_name=column)
|
|
599
|
+
else:
|
|
600
|
+
tables = [table]
|
|
601
|
+
|
|
602
|
+
log("Suppression des lignes répondant à la condition %s %s %s" % (column, condition, value))
|
|
603
|
+
log("Dans les tables suivantes : %s" % (tables))
|
|
604
|
+
for table_ in tables:
|
|
605
|
+
if condition == "EGAL":
|
|
606
|
+
req_del = " DELETE FROM '%s' WHERE %s = '%s' " % (table_, column, value)
|
|
607
|
+
req_count = " SELECT COUNT(*) FROM '%s' WHERE %s = '%s';" % (
|
|
608
|
+
table_,
|
|
609
|
+
column,
|
|
610
|
+
value,
|
|
611
|
+
)
|
|
612
|
+
elif condition == "CONTIENT":
|
|
613
|
+
req_del = " DELETE FROM '%s' WHERE %s LIKE '%%%s%%' " % (
|
|
614
|
+
table_,
|
|
615
|
+
column,
|
|
616
|
+
value,
|
|
617
|
+
)
|
|
618
|
+
req_count = " SELECT COUNT(*) FROM '%s' WHERE %s LIKE '%%%s%%';" % (
|
|
619
|
+
table_,
|
|
620
|
+
column,
|
|
621
|
+
value,
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
cur.execute(req_count)
|
|
625
|
+
n_lines_to_delete = cur.fetchone()[0]
|
|
626
|
+
log("Table %s : %s lignes à supprimer" % (table_, n_lines_to_delete))
|
|
627
|
+
cur.executescript(req_del)
|
|
628
|
+
|
|
629
|
+
cur.close()
|
|
630
|
+
con.close()
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def list_tables(db_file, column_name=None):
|
|
634
|
+
with sqlite3.connect(db_file) as con:
|
|
635
|
+
cur = con.cursor()
|
|
636
|
+
# 'SpatialIndex', 'ElementaryGeometries', 'KNN' are not queriable without spatialite extension
|
|
637
|
+
req = "SELECT name FROM sqlite_master WHERE type='table' AND name NOT IN ('SpatialIndex', 'ElementaryGeometries', 'KNN', 'KNN2');"
|
|
638
|
+
cur.execute(req)
|
|
639
|
+
rows = cur.fetchall()
|
|
640
|
+
|
|
641
|
+
if column_name is None:
|
|
642
|
+
tables = [row[0] for row in rows]
|
|
643
|
+
else:
|
|
644
|
+
tables = [row[0] for row in rows if column_exists(db_file, row[0], column_name)]
|
|
645
|
+
|
|
646
|
+
return tables
|