update fix geometry

This commit is contained in:
DmsAnhr 2025-11-26 14:18:46 +07:00
parent a4dd6b910f
commit 421d0cf90b
4 changed files with 178 additions and 108 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@ -1,4 +1,5 @@
from qgis.core import ( from qgis.core import (
QgsFeature,
QgsVectorLayer, QgsVectorLayer,
QgsVectorLayerExporter, QgsVectorLayerExporter,
QgsVectorFileWriter QgsVectorFileWriter
@ -428,19 +429,42 @@ def cleansing_layer(layer: QgsVectorLayer) -> Dict:
print(" - Invalid geometries found:", len(invalid_ids)) 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( errors = []
"native:checkgeometryselfintersection",
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:"} {"INPUT": layer, "OUTPUT": "memory:"}
)["OUTPUT"] )["OUTPUT"]
self_inter_count = self_inter.featureCount() summary["step1_6_after_fixgeometries"] = fixed_pre.featureCount()
summary["step1_5_self_intersections"] = self_inter_count
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) # 2. FIX GEOMETRIES (INCLUDES SELF-INTERSECTION FIX)

130
main.py
View File

@ -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 fastapi import FastAPI, BackgroundTasks
from qgis_bootstrap import start_qgis from qgis_bootstrap import start_qgis
from uuid import uuid4
# from cleansing_service import load_layer, cleansing_layer # from cleansing_service import load_layer, cleansing_layer
from full_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}") @app.post("/process/{table_name}")
def process_table(table_name: str, background: BackgroundTasks): def process_table(table_name: str, background: BackgroundTasks):
background.add_task(run_clean_table, table_name) job_id = uuid4().hex
return {"status": "ACCEPTED", "table": table_name} 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} ===") print(f"\n=== Mulai cleansing untuk tabel: {table_name} ===")
layer = load_layer(table_name) layer = load_layer(table_name)
if not layer.isValid(): if not layer.isValid():
print(f"[ERROR] Table '{table_name}' tidak valid atau tidak ditemukan.") print(f"[ERROR] Table '{table_name}' tidak valid.")
return return
print("[OK] Layer valid, mulai cleansing...")
result = cleansing_layer(layer) result = cleansing_layer(layer)
summary = result["summary"] summary = result["summary"]
clean_layer = result["clean_layer"] clean_layer = result["clean_layer"]
print("\n=== RINGKASAN CLEANSING ===") # STEP 1 — simpan hasil ke PostGIS
for k, v in summary.items(): save_to_postgis(clean_layer, table_name)
print(f"{k}: {v}")
# TODO: save back ke PostGIS # STEP 2 — kirim callback ke backend utama
# save_to_postgis(clean_layer, table_name) 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") 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)

View File

@ -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 os
import sys import sys
# QGIS environment QGIS_APP = "/Applications/QGIS-LTR.app/Contents"
os.environ["QGIS_PREFIX_PATH"] = "/usr" QGIS_PREFIX = f"{QGIS_APP}/Resources"
os.environ["QGIS_HOME"] = "/usr"
os.environ["PROJ_LIB"] = "/usr/share/proj" # ==== FIX VERY IMPORTANT ====
os.environ["GDAL_DATA"] = "/usr/share/gdal" os.environ["QGIS_PREFIX_PATH"] = QGIS_PREFIX
os.environ["QT_PLUGIN_PATH"] = "/usr/lib/x86_64-linux-gnu/qt5/plugins" 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" os.environ["QT_QPA_PLATFORM"] = "offscreen"
# QGIS Python plugins (THIS IS THE MISSING PART) # Python path
sys.path.append("/usr/share/qgis/python") sys.path.append(f"{QGIS_PREFIX}/python")
sys.path.append("/usr/share/qgis/python/plugins") sys.path.append(f"{QGIS_PREFIX}/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.core import QgsApplication
from qgis.analysis import QgsNativeAlgorithms from qgis.analysis import QgsNativeAlgorithms
import processing import processing
from processing.core.Processing import Processing from processing.core.Processing import Processing
def start_qgis(): def start_qgis():
qgs = QgsApplication([], False) qgs = QgsApplication([], False)
qgs.initQgis() qgs.initQgis()
# === WAJIB: initialize processing ===
Processing.initialize() Processing.initialize()
qgs.processingRegistry().addProvider(QgsNativeAlgorithms()) qgs.processingRegistry().addProvider(QgsNativeAlgorithms())
return qgs 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