2026-01-27 02:52:02 +00:00
import json
import os
2026-02-10 01:54:35 +00:00
import random
import subprocess
import uuid
import asyncpg
2026-01-27 02:52:02 +00:00
import pandas as pd
import geopandas as gpd
import numpy as np
import re
import zipfile
import tempfile
import asyncio
from pyproj import CRS
from shapely . geometry . base import BaseGeometry
from shapely . geometry import base as shapely_base
from fastapi import Depends , File , Form , UploadFile , HTTPException
2026-01-29 03:16:34 +00:00
from api . routers . datasets_router import cleansing_data , publish_layer , query_cleansing_data , upload_to_main
2026-02-10 01:54:35 +00:00
from core . config import DB_DSN , DB_HOST , DB_NAME , DB_PASS , DB_PORT , DB_USER , UPLOAD_FOLDER , MAX_FILE_MB , GEONETWORK_URL
2026-01-27 02:52:02 +00:00
from services . upload_file . ai_generate import send_metadata
from services . upload_file . readers . reader_csv import read_csv
from services . upload_file . readers . reader_shp import read_shp
from services . upload_file . readers . reader_gdb import read_gdb
from services . upload_file . readers . reader_mpk import read_mpk
from services . upload_file . readers . reader_pdf import convert_df , read_pdf
2026-02-10 01:54:35 +00:00
from services . upload_file . utils . df_validation import process_dataframe_synchronous
2026-01-27 02:52:02 +00:00
from services . upload_file . utils . geometry_detector import detect_and_build_geometry , attach_polygon_geometry_auto
from database . connection import engine , sync_engine
from pydantic import BaseModel
from typing import Any , Dict , List , Optional
from shapely import MultiLineString , MultiPolygon , wkt
from sqlalchemy import text
from datetime import datetime
from response import successRes , errorRes
from utils . logger_config import log_activity
# Base.metadata.create_all(bind=engine)
def is_geom_empty ( g ) :
if g is None :
return True
if isinstance ( g , float ) and pd . isna ( g ) :
return True
if isinstance ( g , BaseGeometry ) :
return g . is_empty
return False
def safe_json ( value ) :
""" Konversi aman untuk semua tipe numpy/pandas/shapely ke tipe JSON-serializable """
if isinstance ( value , ( np . int64 , np . int32 ) ) :
return int ( value )
if isinstance ( value , ( np . float64 , np . float32 ) ) :
return float ( value )
if isinstance ( value , pd . Timestamp ) :
return value . isoformat ( )
if isinstance ( value , shapely_base . BaseGeometry ) :
return str ( value ) # convert to WKT string
if pd . isna ( value ) :
return None
return value
def detect_zip_type ( zip_path : str ) - > str :
with zipfile . ZipFile ( zip_path , " r " ) as zip_ref :
files = zip_ref . namelist ( )
if any ( f . lower ( ) . endswith ( " .gdb/ " ) or " .gdb/ " in f . lower ( ) for f in files ) :
return " gdb "
if any ( f . lower ( ) . endswith ( ext ) for ext in [ " .gdbtable " , " .gdbtablx " , " .gdbindexes " , " .spx " ] for f in files ) :
return " gdb "
if any ( f . lower ( ) . endswith ( " .shp " ) for f in files ) :
return " shp "
return " unknown "
2026-02-10 01:54:35 +00:00
async def process_data ( df : pd . DataFrame , ext : str , filename : str , fileDesc : str ) :
2026-01-27 02:52:02 +00:00
result = detect_and_build_geometry ( df , master_polygons = None )
if not hasattr ( result , " geometry " ) or result . geometry . isna ( ) . all ( ) :
result = attach_polygon_geometry_auto ( result )
def normalize_geom_type ( geom_type ) :
if geom_type . startswith ( " Multi " ) :
return geom_type . replace ( " Multi " , " " )
return geom_type
if isinstance ( result , gpd . GeoDataFrame ) and " geometry " in result . columns :
geom_types = (
result . geometry
. dropna ( )
. geom_type
. apply ( normalize_geom_type )
. unique ( )
)
geom_type = geom_types [ 0 ] if len ( geom_types ) > 0 else " None "
null_geom = result . geometry . isna ( ) . sum ( )
2026-02-23 04:58:02 +00:00
print ( f " [INFO] Tipe Geometry: { geom_type } " )
print ( f " [INFO] Jumlah geometry kosong: { null_geom } " )
2026-01-27 02:52:02 +00:00
else :
res = {
" message " : " Tidak menemukan tabel yang relevan. " ,
" file_type " : ext ,
" rows " : 0 ,
" columns " : 0 ,
" geometry_valid " : 0 ,
" geometry_empty " : 0 ,
" geometry_valid_percent " : 0 ,
" warnings " : [ ] ,
" warning_examples " : [ ] ,
" preview " : [ ]
}
return errorRes ( message = " Tidak berhasil mencocokan geometry pada tabel. " , details = res , status_code = 422 )
result = result . replace ( [ pd . NA , float ( ' inf ' ) , float ( ' -inf ' ) ] , None )
if isinstance ( result , gpd . GeoDataFrame ) and ' geometry ' in result . columns :
result [ ' geometry ' ] = result [ ' geometry ' ] . apply (
lambda g : g . wkt if g is not None else None
)
empty_count = result [ ' geometry ' ] . apply ( is_geom_empty ) . sum ( )
valid_count = len ( result ) - empty_count
match_percentage = ( valid_count / len ( result ) ) * 100
warnings = [ ]
if empty_count > 0 :
warnings . append (
f " { empty_count } dari { len ( result ) } baris tidak memiliki geometry yang valid "
f " ( { 100 - match_percentage : .2f } % data gagal cocok). "
)
if empty_count > 0 :
examples = result [ result [ ' geometry ' ] . apply ( is_geom_empty ) ] . head ( 500 )
warning_examples = examples . to_dict ( orient = " records " )
else :
warning_examples = [ ]
# preview_data = result.head(15).to_dict(orient="records")
preview_data = result . to_dict ( orient = " records " )
preview_safe = [
{ k : safe_json ( v ) for k , v in row . items ( ) } for row in preview_data
]
warning_safe = [
{ k : safe_json ( v ) for k , v in row . items ( ) } for row in warning_examples
]
ai_context = {
" nama_file_peta " : filename ,
" nama_opd " : " Badan Penanggulangan Bencana Daerah (BPBD) Provinsi Jatim " ,
" tipe_data_spasial " : geom_type ,
" deskripsi_singkat " : fileDesc ,
" struktur_atribut_data " : { } ,
}
2026-01-29 03:16:34 +00:00
ai_suggest = send_metadata ( ai_context )
# ai_suggest = {'judul': 'Peta Risiko Letusan Gunung Arjuna di Provinsi Jawa Timur', 'abstrak': 'Peta ini menggambarkan wilayah berisiko letusan Gunung Arjuna yang berada di Provinsi Jawa Timur. Data disajikan dalam bentuk poligon yang menunjukkan zona risiko berdasarkan analisis potensi aktivitas vulkanik.', 'tujuan': 'Data dapat digunakan untuk perencanaan mitigasi bencana dan pengambilan keputusan di wilayah Jawa Timur.', 'keyword': ['Risiko letusan', 'Gunung Arjuna', 'Bencana alam', 'Provinsi Jawa Timur', 'Geologi'], 'kategori': ['Geoscientific information', 'Environment'], 'kategori_mapset': 'Lingkungan Hidup'}
2026-02-10 01:54:35 +00:00
tmp_file = generate_unique_filename ( )
await asyncio . to_thread ( process_dataframe_synchronous , result , tmp_file )
2026-01-27 02:52:02 +00:00
response = {
" message " : " File berhasil dibaca dan dianalisis. " ,
" file_name " : filename ,
" file_type " : ext ,
" rows " : int ( len ( result ) ) ,
" columns " : list ( map ( str , result . columns ) ) ,
" geometry_valid " : int ( valid_count ) ,
" geometry_empty " : int ( empty_count ) ,
" geometry_valid_percent " : float ( round ( match_percentage , 2 ) ) ,
" geometry_type " : geom_type ,
" warnings " : warnings ,
" warning_rows " : warning_safe ,
" preview " : preview_safe ,
2026-02-10 01:54:35 +00:00
" metadata_suggest " : ai_suggest ,
" tmp_path " : tmp_file
2026-01-27 02:52:02 +00:00
}
# return successRes(content=response)
return response
async def handle_upload_file ( file : UploadFile = File ( . . . ) , page : Optional [ str ] = Form ( " " ) , sheet : Optional [ str ] = Form ( " " ) , fileDesc : Optional [ str ] = Form ( " " ) ) :
fname = file . filename
ext = os . path . splitext ( fname ) [ 1 ] . lower ( )
contents = await file . read ( )
size_mb = len ( contents ) / ( 1024 * 1024 )
if size_mb > MAX_FILE_MB :
raise errorRes ( status_code = 413 , message = " Ukuran File Terlalu Besar " )
tmp_path = UPLOAD_FOLDER / fname
with open ( tmp_path , " wb " ) as f :
f . write ( contents )
try :
df = None
2026-02-23 04:58:02 +00:00
print ( ' ext ' , ext )
2026-01-27 02:52:02 +00:00
if ext == " .csv " :
df = read_csv ( str ( tmp_path ) )
elif ext == " .xlsx " :
df = read_csv ( str ( tmp_path ) , sheet )
elif ext == " .mpk " :
df = read_mpk ( str ( tmp_path ) )
elif ext == " .pdf " :
tbl = read_pdf ( tmp_path , page )
if len ( tbl ) == 0 :
res = {
" message " : " Tidak ditemukan tabel valid pada halaman yang dipilih " ,
" tables " : { } ,
" file_type " : ext
}
return successRes ( message = " Tidak ditemukan tabel valid pada halaman yang dipilih " , data = res )
elif len ( tbl ) > 1 :
res = {
" message " : " File berhasil dibaca dan dianalisis. " ,
" tables " : tbl ,
" file_type " : ext
}
return successRes ( data = res , message = " File berhasil dibaca dan dianalisis. " )
else :
df = convert_df ( tbl [ 0 ] )
elif ext == " .zip " :
zip_type = detect_zip_type ( str ( tmp_path ) )
if zip_type == " shp " :
2026-02-23 04:58:02 +00:00
print ( " [INFO] ZIP terdeteksi sebagai Shapefile. " )
2026-01-27 02:52:02 +00:00
df = read_shp ( str ( tmp_path ) )
elif zip_type == " gdb " :
2026-02-23 04:58:02 +00:00
print ( " [INFO] ZIP terdeteksi sebagai Geodatabase (GDB). " )
2026-01-27 02:52:02 +00:00
df = read_gdb ( str ( tmp_path ) )
else :
return successRes ( message = " ZIP file tidak mengandung SHP / GDB valid. " )
else :
raise errorRes ( status_code = 400 , message = " Unsupported file type " )
if df is None or ( hasattr ( df , " empty " ) and df . empty ) :
return successRes ( message = " File berhasil dibaca, Tetapi tidak ditemukan tabel valid " )
2026-02-10 01:54:35 +00:00
res = await process_data ( df , ext , fname , fileDesc )
2026-01-27 02:52:02 +00:00
tmp_path . unlink ( missing_ok = True )
return successRes ( data = res )
except Exception as e :
print ( f " [ERROR] { e } " )
return errorRes (
message = " Internal Server Error " ,
details = str ( e ) ,
status_code = 500
)
# finally:
# db_session.close()
class PdfRequest ( BaseModel ) :
title : str
columns : List [ str ]
rows : List [ List ]
fileName : str
fileDesc : str
async def handle_process_pdf ( payload : PdfRequest ) :
try :
df = convert_df ( payload . model_dump ( ) )
if df is None or ( hasattr ( df , " empty " ) and df . empty ) :
return errorRes ( message = " Tidak ada tabel " )
2026-02-10 01:54:35 +00:00
res = await process_data ( df , ' .pdf ' , payload . fileName , payload . fileDesc )
2026-01-27 02:52:02 +00:00
return successRes ( data = res )
except Exception as e :
print ( f " [ERROR] { e } " )
return errorRes ( message = " Internal Server Error " , details = str ( e ) , status_code = 500 )
# finally:
# db_session.close()
class UploadRequest ( BaseModel ) :
title : str
2026-02-10 01:54:35 +00:00
path : str
2026-01-27 02:52:02 +00:00
rows : List [ dict ]
columns : List [ str ]
author : Dict [ str , Any ]
style : str
# generate _2 if exist
async def generate_unique_table_name ( base_name : str ) :
base_name = base_name . lower ( ) . replace ( " " , " _ " ) . replace ( " - " , " _ " )
table_name = base_name
counter = 2
async with engine . connect ( ) as conn :
while True :
result = await conn . execute (
text ( " SELECT to_regclass(:tname) " ) ,
{ " tname " : table_name }
)
exists = result . scalar ( )
if not exists :
return table_name
table_name = f " { base_name } _ { counter } "
counter + = 1
def str_to_date ( raw_date : str ) :
if raw_date :
try :
return datetime . strptime ( raw_date , " % Y- % m- %d " ) . date ( )
except Exception as e :
print ( " [WARNING] Tidak bisa parse dateCreated: " , e )
return None
2026-02-10 01:54:35 +00:00
def generate_unique_filename ( folder = " tmp " , ext = " parquet " , digits = 6 ) :
os . makedirs ( folder , exist_ok = True )
while True :
file_id = file_id = uuid . uuid4 ( ) . int
filename = f " { folder } / { file_id } . { ext } "
if not os . path . exists ( filename ) :
return filename
2026-01-27 02:52:02 +00:00
def generate_job_id ( user_id : str ) - > str :
timestamp = datetime . now ( ) . strftime ( " % Y % m %d % H % M % S " )
return f " { user_id } _ { timestamp } "
def save_xml_to_sld ( xml_string , filename ) :
folder_path = ' style_temp '
os . makedirs ( folder_path , exist_ok = True )
file_path = os . path . join ( folder_path , f " { filename } .sld " )
with open ( file_path , " w " , encoding = " utf-8 " ) as f :
f . write ( xml_string )
return file_path
2026-02-10 01:54:35 +00:00
async def process_parquet_upload ( filename : str , table_name : str ) :
from main import db_pool
file_path = os . path . join ( " tmp " , filename )
if not os . path . exists ( file_path ) :
2026-02-23 04:58:02 +00:00
print ( f " File { file_path } tidak ditemukan " )
2026-02-10 01:54:35 +00:00
return
2026-01-27 02:52:02 +00:00
2026-02-10 01:54:35 +00:00
try :
loop = asyncio . get_running_loop ( )
df = await loop . run_in_executor ( None , pd . read_parquet , file_path )
2026-01-27 02:52:02 +00:00
2026-02-10 01:54:35 +00:00
# =====================================================================
# 1. CLEANING NAMA KOLOM (PENTING!)
# =====================================================================
df . columns = [ str ( col ) . strip ( ) . upper ( ) for col in df . columns ]
2026-01-27 02:52:02 +00:00
2026-02-10 01:54:35 +00:00
# Cek kolom GEOM (bisa GEOM atau geom setelah upper)
# Kita standarkan nama kolom GEOM di DF menjadi "GEOM" untuk memudahkan logic
if " GEOM " in df . columns :
df . rename ( columns = { " GEOM " : " GEOM " } , inplace = True )
if " GEOM " not in df . columns :
raise Exception ( " Kolom GEOM tidak ditemukan " )
# =====================================================================
# 2. PERSIAPAN DATA (Row Processing)
# =====================================================================
clean_rows = [ ]
geom_types = set ( )
# Ambil semua kolom atribut selain GEOM
# Pastikan list ini yang dipakai untuk CREATE TABLE dan COPY (SINKRON)
attr_columns = [ col for col in df . columns if col != " GEOM " ]
for row in df . itertuples ( index = False ) :
# --- Handle GEOM ---
raw_geom = getattr ( row , " GEOM " , None )
if not raw_geom : continue
try :
geom = None
if isinstance ( raw_geom , str ) :
geom = wkt . loads ( raw_geom )
elif isinstance ( raw_geom , bytes ) :
from shapely import wkb
geom = wkb . loads ( raw_geom )
if not geom : continue
if not geom . is_valid : geom = geom . buffer ( 0 )
gtype = geom . geom_type . upper ( )
if gtype == " POLYGON " : geom = MultiPolygon ( [ geom ] )
elif gtype == " LINESTRING " : geom = MultiLineString ( [ geom ] )
geom_types . add ( geom . geom_type )
ewkt = f " SRID=4326; { geom . wkt } "
except Exception :
continue
# --- Handle Attributes (FORCE STRING) ---
row_data = [ ]
for col in attr_columns :
# getattr menggunakan nama kolom uppercase dari attr_columns
val = getattr ( row , col , None )
if val is not None :
row_data . append ( str ( val ) ) # Convert int/float ke string
else :
row_data . append ( None )
row_data . append ( ewkt )
clean_rows . append ( tuple ( row_data ) )
if not clean_rows :
raise Exception ( " Data valid kosong " )
# =====================================================================
# 3. DATABASE OPERATIONS
# =====================================================================
final_geom_type = list ( geom_types ) [ 0 ] . upper ( ) if geom_types else " GEOM "
if " MULTI " not in final_geom_type and final_geom_type != " GEOM " :
final_geom_type = " MULTI " + final_geom_type
# A. BUILD DDL (CREATE TABLE)
# Kita pakai f-string quotes f'"{col}"' agar di DB jadi UPPERCASE ("ID", "NAMA")
col_defs = [ f ' " { col } " TEXT ' for col in attr_columns ]
create_sql = f """
CREATE TABLE { table_name } (
_id SERIAL PRIMARY KEY , - - lowercase default
{ ' , ' . join ( col_defs ) } , - - UPPERCASE ( Hasil loop attr_columns )
geom TEXT - - lowercase
) ;
"""
async with db_pool . acquire ( ) as conn :
# Drop table jika ada (untuk safety dev, production hati-hati)
# await conn.execute(f"DROP TABLE IF EXISTS {table_name}")
# 1. Create Table
await conn . execute ( create_sql )
# 2. COPY Data
# target_cols harus PERSIS sama dengan attr_columns
# asyncpg akan meng-quote string ini otomatis ("ID", "NAMA", "geom")
target_cols = attr_columns + [ ' geom ' ]
await conn . copy_records_to_table (
table_name ,
records = clean_rows ,
columns = target_cols
)
# 3. Alter ke GEOM 2D
alter_sql = f """
ALTER TABLE { table_name }
ALTER COLUMN geom TYPE geometry ( { final_geom_type } , 4326 )
USING ST_Force2D ( geom : : geometry ) : : geometry ( { final_geom_type } , 4326 ) ;
CREATE INDEX idx_ { table_name } _geom ON { table_name } USING GIST ( geom ) ;
"""
await conn . execute ( alter_sql )
2026-02-23 04:58:02 +00:00
print ( f " Sukses upload { len ( clean_rows ) } baris ke { table_name } . " )
2026-02-10 01:54:35 +00:00
os . remove ( file_path )
except Exception as e :
print ( f " Error processing parquet: { e } " )
# Log error
2026-01-27 02:52:02 +00:00
2026-01-29 03:16:34 +00:00
async def handle_to_postgis ( payload : UploadRequest , user_id : int = 2 ) :
try :
table_name = await generate_unique_table_name ( payload . title )
# DataFrame
df = pd . DataFrame ( payload . rows )
df . columns = [ col . upper ( ) for col in df . columns ]
if " GEOMETRY " not in df . columns :
raise HTTPException ( 400 , " Kolom GEOMETRY tidak ditemukan " )
# =====================================================================
# 1. LOAD WKT → SHAPELY
# =====================================================================
def safe_load_wkt ( g ) :
if not isinstance ( g , str ) :
return None
try :
geom = wkt . loads ( g )
return geom
except :
return None
df [ " GEOMETRY " ] = df [ " GEOMETRY " ] . apply ( safe_load_wkt )
df = df . rename ( columns = { " GEOMETRY " : " geom " } )
# =====================================================================
# 2. DROP ROW geometry NULL
# =====================================================================
df = df [ df [ " geom " ] . notnull ( ) ]
if df . empty :
raise HTTPException ( 400 , " Semua geometry invalid atau NULL " )
# =====================================================================
# 3. VALIDATE geometry (very important)
# =====================================================================
df [ " geom " ] = df [ " geom " ] . apply ( lambda g : g if g . is_valid else g . buffer ( 0 ) )
# =====================================================================
# 4. SERAGAMKAN TIPE GEOMETRY (Polygon→MultiPolygon, Line→MultiLine)
# =====================================================================
def unify_geometry_type ( g ) :
gtype = g . geom_type . upper ( )
if gtype == " POLYGON " :
return MultiPolygon ( [ g ] )
if gtype == " LINESTRING " :
return MultiLineString ( [ g ] )
return g # sudah MULTI atau POINT
df [ " geom " ] = df [ " geom " ] . apply ( unify_geometry_type )
# =====================================================================
# 5. DETEKSI CRS DARI METADATA / INPUT / DEFAULT
# =====================================================================
detected_crs = payload . author . get ( " crs " )
detected = payload . author . get ( " crs " )
2026-02-23 04:58:02 +00:00
print ( ' crs ' , detected )
2026-01-29 03:16:34 +00:00
if not detected_crs :
detected_crs = " EPSG:4326 "
detected_crs = ' EPSG:4326 '
# Buat GeoDataFrame
gdf = gpd . GeoDataFrame ( df , geometry = " geom " , crs = detected_crs )
row_count = len ( gdf )
# =====================================================================
# 6. VERIFY CRS (SRID) VALID di PROJ / PostGIS
# =====================================================================
try :
_ = gdf . to_crs ( gdf . crs ) # test CRS valid
except :
raise HTTPException ( 400 , f " CRS { detected_crs } tidak valid " )
# =====================================================================
2026-02-10 01:54:35 +00:00
# 7. SIMPAN KE POSTGIS
2026-01-29 03:16:34 +00:00
# =====================================================================
2026-02-10 01:54:35 +00:00
job_id = generate_job_id ( str ( user_id ) )
await process_parquet_upload ( payload . path , table_name )
2026-01-29 03:16:34 +00:00
# =====================================================================
# 9. SIMPAN METADATA (geom_type, author metadata)
# =====================================================================
unified_geom_type = list ( gdf . geom_type . unique ( ) )
author = payload . author
async with engine . begin ( ) as conn :
await conn . execute ( text ( """
INSERT INTO backend . author_metadata (
table_title ,
dataset_title ,
dataset_abstract ,
keywords ,
topic_category ,
date_created ,
dataset_status ,
organization_name ,
contact_person_name ,
contact_email ,
contact_phone ,
geom_type ,
user_id ,
process ,
geometry_count
) VALUES (
: table_title ,
: dataset_title ,
: dataset_abstract ,
: keywords ,
: topic_category ,
: date_created ,
: dataset_status ,
: organization_name ,
: contact_person_name ,
: contact_email ,
: contact_phone ,
: geom_type ,
: user_id ,
: process ,
: geometry_count
)
""" ), {
" table_title " : table_name ,
" dataset_title " : payload . title ,
" dataset_abstract " : author . get ( " abstract " ) ,
" keywords " : author . get ( " keywords " ) ,
" topic_category " : " , " . join ( author . get ( " topicCategory " ) ) ,
" date_created " : str_to_date ( author . get ( " dateCreated " ) ) ,
" dataset_status " : author . get ( " status " ) ,
" organization_name " : author . get ( " organization " ) ,
" contact_person_name " : author . get ( " contactName " ) ,
" contact_email " : author . get ( " contactEmail " ) ,
" contact_phone " : author . get ( " contactPhone " ) ,
" geom_type " : json . dumps ( unified_geom_type ) ,
" user_id " : user_id ,
" process " : ' CLEANSING ' ,
" geometry_count " : row_count
} )
# =====================================================================
# 10. LOGGING
# =====================================================================
await log_activity (
user_id = user_id ,
action_type = " UPLOAD " ,
action_title = f " Upload dataset { table_name } " ,
details = { " table_name " : table_name , " rows " : len ( gdf ) }
)
result = {
" job_id " : job_id ,
" job_status " : " wait " ,
" table_name " : table_name ,
" status " : " success " ,
" message " : f " Tabel ' { table_name } ' berhasil dibuat. " ,
" total_rows " : len ( gdf ) ,
" geometry_type " : unified_geom_type ,
" crs " : detected_crs ,
" metadata_uuid " : " "
}
save_xml_to_sld ( payload . style , job_id )
cleansing = await query_cleansing_data ( table_name )
result [ ' job_status ' ] = cleansing
2026-02-10 01:54:35 +00:00
2026-01-29 03:16:34 +00:00
publish = await publish_layer ( table_name , job_id )
result [ ' metadata_uuid ' ] = publish [ ' uuid ' ]
mapset = {
" name " : payload . title ,
" description " : author . get ( " abstract " ) ,
" scale " : " 1:25000 " ,
2026-02-10 01:54:35 +00:00
' projection_system_id ' : ' 0196c746-d1ba-7f1c-9706-5df738679cc7 ' ,
2026-01-29 03:16:34 +00:00
" category_id " : author . get ( " mapsetCategory " ) ,
" data_status " : " sementara " ,
2026-02-10 01:54:35 +00:00
' classification_id ' : ' 01968b4b-d3f9-76c9-888c-ee887ac31ce4 ' ,
' producer_id ' : ' 01968b54-0000-7a67-bd10-975b8923b93e ' ,
2026-01-29 03:16:34 +00:00
" layer_type " : unified_geom_type [ 0 ] ,
2026-02-10 01:54:35 +00:00
' source_id ' : [ ' 019c03ef-35e1-738b-858d-871dc7d1e4d6 ' ] ,
2026-01-29 03:16:34 +00:00
" layer_url " : publish [ ' geos_link ' ] ,
" metadata_url " : f " { GEONETWORK_URL } /srv/eng/catalog.search#/metadata/ { publish [ ' uuid ' ] } " ,
" coverage_level " : " provinsi " ,
" coverage_area " : " kabupaten " ,
" data_update_period " : " Tahunan " ,
" data_version " : " 2026 " ,
" is_popular " : False ,
" is_active " : True ,
2026-02-10 01:54:35 +00:00
' regional_id ' : ' 01968b53-a910-7a67-bd10-975b8923b92e ' ,
2026-01-29 03:16:34 +00:00
" notes " : " Mapset baru dibuat " ,
" status_validation " : " on_verification " ,
}
2026-02-10 01:54:35 +00:00
2026-01-29 03:16:34 +00:00
await upload_to_main ( mapset )
return successRes ( data = result )
except Exception as e :
await log_activity (
user_id = user_id ,
action_type = " ERROR " ,
action_title = " Upload gagal " ,
details = { " error " : str ( e ) }
)
raise HTTPException ( status_code = 500 , detail = str ( e ) )