update fix geometry
This commit is contained in:
parent
a4dd6b910f
commit
421d0cf90b
|
|
@ -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
130
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 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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user