From 421d0cf90bcb460233e48b3ba02ffb4470b61dec Mon Sep 17 00:00:00 2001 From: DmsAnhr Date: Wed, 26 Nov 2025 14:18:46 +0700 Subject: [PATCH] update fix geometry --- .DS_Store | Bin 0 -> 8196 bytes full_cleansing_service.py | 38 +++++++++-- main.py | 130 +++++++++++++++++++++++++------------- qgis_bootstrap.py | 118 +++++++++++++++++----------------- 4 files changed, 178 insertions(+), 108 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..1bbc47580fecf4e2877e4913fcc75a599d7f1c85 GIT binary patch literal 8196 zcmeHM%}(Pm5dI967SRhxdw|1A;RRap1_7}LB(y?8doD>S1yo2ODH6Bb_Z{{Xcs@?> z&DgEd5X6rV0x?tBUt-UA#-FEg+yZdhlcP;w4ZsqM;7XfSgGj%WN3sxFR)~)F*d1m4 zbd;sMmo^h)z!)$Fi~(c782Ee)aL*Q5dSc)Ay;d6o#=y5^K%Nf~i(nkE^r(*xR(b>= zw%Dw~Ykl=6M$$lx1C}1yLUG=d=uKTXVz_W}aNeB#anLV4dUH7S%yEKe7fvWH96Vg4 z>Tt0~tBnC;;4uU8b!nl4Ax=@$&hNME>)flT;qp|ExdeR?X@sHAmD;=q0DO3;OHvYCwBcslb@uo$kQ3j+89N98X;0fDr?uUVhh7syz4l|giMae>zi^wbLoF2Yk%k(N)9#n zhvaxwd$qEzDOWtx)h@XD3G=OC1kL#B2+On>aJ~g+FlLT4*+`x~YxC|a))-kO{~G%l zscgA%z|y1c!mM-`Cffc_UQCREuf)Kj4|Yw?|G%I9{r@XX$znAIjDc4&;95HeJKHqU yUA@6rcKL|qkVQoFN{?#7YVJjV_p?6?c^y$zY~p~WN48MxhXAF)Dr4Y78F&D7Pzj{~ literal 0 HcmV?d00001 diff --git a/full_cleansing_service.py b/full_cleansing_service.py index d0c49db..c92362c 100644 --- a/full_cleansing_service.py +++ b/full_cleansing_service.py @@ -1,4 +1,5 @@ from qgis.core import ( + QgsFeature, QgsVectorLayer, QgsVectorLayerExporter, QgsVectorFileWriter @@ -428,19 +429,42 @@ def cleansing_layer(layer: QgsVectorLayer) -> Dict: print(" - Invalid geometries found:", len(invalid_ids)) # ======================================================== - # 1.5 CHECK SELF INTERSECTION + # 1.5 DETECT GEOMETRY ERRORS (MANUAL GEOS VALIDATION) # ======================================================== - print("\nStep 1.5: Check self-intersection") + print("\nStep 1.5: Detect geometry errors (universal GEOS-safe method)") - self_inter = processing.run( - "native:checkgeometryselfintersection", + errors = [] + + for f in layer.getFeatures(): + geom = f.geometry() + if not geom.isGeosValid(): + # Kita hanya tandai invalid (tanpa reason) + errors.append(f.id()) + + summary["step1_5_geometry_errors"] = len(errors) + + print(" - Geometry errors detected:", len(errors)) + print(" - Invalid feature IDs (first 10):", errors[:10]) + + + + # ======================================================== + # 1.6 FIX INVALID GEOMETRIES (Native FixGeometries) + # ======================================================== + print("\nStep 1.6: Fix invalid geometries (FixGeometries)") + + fixed_pre = processing.run( + "native:fixgeometries", {"INPUT": layer, "OUTPUT": "memory:"} )["OUTPUT"] - self_inter_count = self_inter.featureCount() - summary["step1_5_self_intersections"] = self_inter_count + summary["step1_6_after_fixgeometries"] = fixed_pre.featureCount() + + print(" - Features after FixGeometries:", fixed_pre.featureCount()) + + layer = fixed_pre + - print(" - Features with self-intersection:", self_inter_count) # ======================================================== # 2. FIX GEOMETRIES (INCLUDES SELF-INTERSECTION FIX) diff --git a/main.py b/main.py index 02f65a9..aa24752 100644 --- a/main.py +++ b/main.py @@ -1,37 +1,6 @@ -# from fastapi import FastAPI -# from qgis.core import QgsVectorLayer -# from qgis_bootstrap import start_qgis - -# app = FastAPI() - -# # Start QGIS headless -# qgs = start_qgis() - -# @app.get("/") -# def root(): -# return {"status": "QGIS API Ready"} - -# @app.get("/extent") -# def extent(): -# layer = QgsVectorLayer("data/exmpl.geojson", "jalan", "ogr") - -# if not layer.isValid(): -# return {"error": "Layer tidak valid"} - -# ext = layer.extent() -# return { -# "xmin": ext.xMinimum(), -# "ymin": ext.yMinimum(), -# "xmax": ext.xMaximum(), -# "ymax": ext.yMaximum(), -# } - - - - - from fastapi import FastAPI, BackgroundTasks from qgis_bootstrap import start_qgis +from uuid import uuid4 # from cleansing_service import load_layer, cleansing_layer from full_cleansing_service import load_layer, cleansing_layer @@ -65,30 +34,103 @@ def clean_table(table_name: str): @app.post("/process/{table_name}") def process_table(table_name: str, background: BackgroundTasks): - background.add_task(run_clean_table, table_name) - return {"status": "ACCEPTED", "table": table_name} + job_id = uuid4().hex + background.add_task(run_clean_table, table_name, job_id) + return { + "status": "ACCEPTED", + "job_id": job_id, + "table": table_name + } -def run_clean_table(table_name: str): + +def run_clean_table(table_name: str, job_id: str): print(f"\n=== Mulai cleansing untuk tabel: {table_name} ===") layer = load_layer(table_name) if not layer.isValid(): - print(f"[ERROR] Table '{table_name}' tidak valid atau tidak ditemukan.") + print(f"[ERROR] Table '{table_name}' tidak valid.") return - print("[OK] Layer valid, mulai cleansing...") - result = cleansing_layer(layer) - summary = result["summary"] clean_layer = result["clean_layer"] - print("\n=== RINGKASAN CLEANSING ===") - for k, v in summary.items(): - print(f"{k}: {v}") + # STEP 1 — simpan hasil ke PostGIS + save_to_postgis(clean_layer, table_name) - # TODO: save back ke PostGIS - # save_to_postgis(clean_layer, table_name) + # STEP 2 — kirim callback ke backend utama + callback_payload = { + "job_id": job_id, + "table": table_name, + "summary": summary, + "status": "FINISHED" + } + + import requests + requests.post( + "http://backend-utama:8000/jobs/callback", + json=callback_payload + ) print(f"=== Cleansing selesai untuk tabel: {table_name} ===\n") + + + + + + + + + +from qgis.core import ( + QgsVectorLayer, + QgsVectorLayerExporter, + QgsDataSourceUri +) +from database import POSTGIS + + +def save_to_postgis(clean_layer: QgsVectorLayer, table_name: str): + """ + Menghapus isi tabel dan menulis ulang hasil cleansing ke PostGIS. + Geometry harus MULTIPOLYGON dan SRID sudah benar. + """ + + print(f"[DB] Menyimpan hasil cleansing ke tabel {table_name}") + + # ------------------------------------------- + # 1. Build URI PostGIS target + # ------------------------------------------- + uri = QgsDataSourceUri() + uri.setConnection( + POSTGIS['host'], + str(POSTGIS['port']), + POSTGIS['db'], + POSTGIS['user'], + POSTGIS['password'] + ) + + # Nama schema & tabel + schema = "public" + uri.setDataSource(schema, table_name, "geom") # geometry column = geom + + # ------------------------------------------- + # 2. Export layer ke PostGIS (replace mode) + # ------------------------------------------- + + options = QgsVectorLayerExporter.ExportOptions() + options.actionOnExistingFile = QgsVectorLayerExporter.ActionOnExistingFile.OverwriteLayer + + err_code, err_msg = QgsVectorLayerExporter.exportLayer( + clean_layer, # layer input + uri.uri(), # postgis connection uri + "postgres", # provider + clean_layer.crs(), # CRS layer + options + ) + + if err_code != QgsVectorLayerExporter.NoError: + print("[DB][ERROR] Gagal menyimpan:", err_msg) + else: + print("[DB] Berhasil update tabel", table_name) diff --git a/qgis_bootstrap.py b/qgis_bootstrap.py index 36b2e77..5250e23 100644 --- a/qgis_bootstrap.py +++ b/qgis_bootstrap.py @@ -1,76 +1,80 @@ -# import os -# import sys - -# QGIS_APP = "/Applications/QGIS-LTR.app/Contents" -# QGIS_PREFIX = f"{QGIS_APP}/Resources" - -# # ==== FIX VERY IMPORTANT ==== -# os.environ["QGIS_PREFIX_PATH"] = QGIS_PREFIX -# os.environ["PROJ_LIB"] = f"{QGIS_PREFIX}/proj" -# os.environ["GDAL_DATA"] = f"{QGIS_PREFIX}/gdal" -# os.environ["QT_PLUGIN_PATH"] = f"{QGIS_PREFIX}/plugins" -# # ============================= - -# os.environ["QT_QPA_PLATFORM"] = "offscreen" - -# # Python path -# sys.path.append(f"{QGIS_PREFIX}/python") -# sys.path.append(f"{QGIS_PREFIX}/python/plugins") - -# from qgis.core import QgsApplication -# from qgis.analysis import QgsNativeAlgorithms - -# import processing -# from processing.core.Processing import Processing - -# def start_qgis(): -# qgs = QgsApplication([], False) -# qgs.initQgis() - -# # === WAJIB: initialize processing === -# Processing.initialize() -# qgs.processingRegistry().addProvider(QgsNativeAlgorithms()) - -# return qgs - - - - - - - - - import os import sys -# QGIS environment -os.environ["QGIS_PREFIX_PATH"] = "/usr" -os.environ["QGIS_HOME"] = "/usr" +QGIS_APP = "/Applications/QGIS-LTR.app/Contents" +QGIS_PREFIX = f"{QGIS_APP}/Resources" -os.environ["PROJ_LIB"] = "/usr/share/proj" -os.environ["GDAL_DATA"] = "/usr/share/gdal" -os.environ["QT_PLUGIN_PATH"] = "/usr/lib/x86_64-linux-gnu/qt5/plugins" +# ==== FIX VERY IMPORTANT ==== +os.environ["QGIS_PREFIX_PATH"] = QGIS_PREFIX +os.environ["PROJ_LIB"] = f"{QGIS_PREFIX}/proj" +os.environ["GDAL_DATA"] = f"{QGIS_PREFIX}/gdal" +os.environ["QT_PLUGIN_PATH"] = f"{QGIS_PREFIX}/plugins" +# ============================= os.environ["QT_QPA_PLATFORM"] = "offscreen" -# QGIS Python plugins (THIS IS THE MISSING PART) -sys.path.append("/usr/share/qgis/python") -sys.path.append("/usr/share/qgis/python/plugins") - -# Python modules (from system) -sys.path.append("/usr/lib/python3/dist-packages") -sys.path.append("/usr/lib/python3/dist-packages/qgis") - +# Python path +sys.path.append(f"{QGIS_PREFIX}/python") +sys.path.append(f"{QGIS_PREFIX}/python/plugins") from qgis.core import QgsApplication from qgis.analysis import QgsNativeAlgorithms + import processing from processing.core.Processing import Processing def start_qgis(): qgs = QgsApplication([], False) qgs.initQgis() + + # === WAJIB: initialize processing === Processing.initialize() qgs.processingRegistry().addProvider(QgsNativeAlgorithms()) + return qgs + + + + + + + + + + + + +# DEPLOYMENT +# import os +# import sys + +# # QGIS environment +# os.environ["QGIS_PREFIX_PATH"] = "/usr" +# os.environ["QGIS_HOME"] = "/usr" + +# os.environ["PROJ_LIB"] = "/usr/share/proj" +# os.environ["GDAL_DATA"] = "/usr/share/gdal" +# os.environ["QT_PLUGIN_PATH"] = "/usr/lib/x86_64-linux-gnu/qt5/plugins" + +# os.environ["QT_QPA_PLATFORM"] = "offscreen" + +# # QGIS Python plugins (THIS IS THE MISSING PART) +# sys.path.append("/usr/share/qgis/python") +# sys.path.append("/usr/share/qgis/python/plugins") + +# # Python modules (from system) +# sys.path.append("/usr/lib/python3/dist-packages") +# sys.path.append("/usr/lib/python3/dist-packages/qgis") + + +# from qgis.core import QgsApplication +# from qgis.analysis import QgsNativeAlgorithms +# import processing +# from processing.core.Processing import Processing + +# def start_qgis(): +# qgs = QgsApplication([], False) +# qgs.initQgis() +# Processing.initialize() +# qgs.processingRegistry().addProvider(QgsNativeAlgorithms()) +# return qgs