From 86a01322ec06e3087d8ecc3785a036df98ca322e Mon Sep 17 00:00:00 2001 From: ='fauz <='fauzgabriel@gmail.com> Date: Wed, 2 Jul 2025 11:21:46 +0700 Subject: [PATCH] map-new fetch --- app/__pycache__/apiController.cpython-313.pyc | Bin 38677 -> 39441 bytes app/__pycache__/mapController.cpython-313.pyc | Bin 3527 -> 6743 bytes app/apiController.py | 25 +++++- app/mapController.py | 85 ++++++++++++++---- 4 files changed, 90 insertions(+), 20 deletions(-) diff --git a/app/__pycache__/apiController.cpython-313.pyc b/app/__pycache__/apiController.cpython-313.pyc index d7d30f6bea8cc0236303ace7f1f173a96b90ba87..dac7bd806bf04be24bd959fe43c5de34cb851db9 100644 GIT binary patch delta 483 zcmbQbj%nf+Cce+Syj%=G(6c%vL+H##zGfRn_RW)Qyct;&pE5EqO#av?HCZ7_X7Yh1 zj>+%sL?{2Y6`d?)FF84Yhh?&{y$YBPw~qtUTkVy=^mY44Fm2$#Ir$<#MD2kD*2$Kv zLX$1+q*)XHQf@NW3i#}Ni?sqH_j`AG1LY)rK`H`(Du9$fQ!H3v97y5h#jX{Mx|?O(f>@dUGj1;M zzQD?8x7jhUiiNRz^PbRpR>nn}ZK4;mGM?J}AVGnZk#{p=+Cx@WEoKIW&y!zksc+`X zdBe=;y7_m$8!Kb-WQ#uS&AFwsm>KIfb5sH~Pukp3w~Co@?d0G2vYP{%PO~wd*(}p3 z&C2*{vqj${Rz}v%(o@zjGAeIAF|~n--Iq80iX8#$R8TkVR8O1&^fXE^dV3+{_ D^^R%S diff --git a/app/__pycache__/mapController.cpython-313.pyc b/app/__pycache__/mapController.cpython-313.pyc index 41df45f0eb5bfa7f5aa9097a99cec104e3b06c4e..abe74f795e37371bbc9ce0a8fadb834f7a45bdd9 100644 GIT binary patch literal 6743 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 delta 1140 zcmah|&uiR96n?W>Nh@hr8*kQ*e*j}{F>#?JDIsB#U`4cGj8UaoXIpus zjI>GI%Z8pp>BWd#dP#aH^zQc5TNB71;6p%72{{%*F){5qZ{$^50;L0*H}C2DzWLrT z^X$yi33I>liyP_Er;?_&b4K;=XH&R8pj1J(*j{wPljOE$5{(x(7U9l$EEqAjgR$@_Y} zzbH1sF%8!&1W7qa01p2Ad$x*p;S5cYQ;U z)QNkX{!WqyBPWk$fYJsHHfhYaQXTz-l*e5fHMQiK-stm+kkBsPb!ixP1D47m@3J^m zoG_%!YgW>t8!#;U^fvWVBXl@r@wyv$G?m;QPu0Np)}vcK9}z+l5ALKz?C@p=ji|St zC8is2PTe@q3FXHHzM^&x{6@k@^5cL1`@6u;;zd=~i56fmmE-007fby*vJK0f(y z{@eLSH+IM8?_YU-tntId?y<%DSO3xzeQZ^|Lg3Kk-u1*EyV(C_Z07uO;qkJ5;e`A| ztzVdtpUlX(=Hk*cWiD=CI)rVuoUnU7U@;GTpK>mqTy`p4IN{>$b5l&>nxv(3n}OH! j>4$s?Q*;QCXnap1g!~G|Z}8r0#U#p~C$vupQ8MBi4`}Nq diff --git a/app/apiController.py b/app/apiController.py index 63e4220..e6022b3 100644 --- a/app/apiController.py +++ b/app/apiController.py @@ -394,9 +394,26 @@ class apiController: cur.execute(""" SELECT id, kode, nama, null as lat, null as lng, koordinat as path FROM wil_provinsi """) elif level == 'kabupaten': - cur.execute("SELECT id, kode, nama, null as lat, null as lng, koordinat as path FROM wil_kabupatenkota WHERE provinsi_id = %s", (parent_code,)) + cur.execute(""" + SELECT + id, kode, nama, null as lat, null as lng, + CONCAT( + REPEAT('[', 4 - (LENGTH(path) - LENGTH(REPLACE(path, '[', '')))), + path, + REPEAT(']', 4 - (LENGTH(path) - LENGTH(REPLACE(path, ']', '')))) + ) AS path + FROM wil_kabupatenkota WHERE provinsi_id = %s""", (parent_code,)) elif level == 'kecamatan': - cur.execute("SELECT id, kode, nama, null as lat, null as lng, koordinat as path FROM wil_kecamatan WHERE kabupaten_kota_id = %s", (parent_code,)) + cur.execute(""" + SELECT + id, kode, nama, null as lat, null as lng, + CONCAT( + REPEAT('[', 4 - (LENGTH(path) - LENGTH(REPLACE(path, '[', '')))), + path, + REPEAT(']', 4 - (LENGTH(path) - LENGTH(REPLACE(path, ']', '')))) + ) AS path FROM wil_kecamatan WHERE + kabupaten_kota_id = %s + """, (parent_code,)) elif level == 'desa': # cur.execute("SELECT id, kode, nama, null as lat, null as lng,path FROM wil_desa WHERE kecamatan_id = %s", (parent_code,)) cur.execute((""" @@ -433,8 +450,8 @@ class apiController: continue geometry = { - "type": "MultiPolygon", # karena bentuk datamu seperti itu - "coordinates": corrected_path + "type": "MultiPolygon", + "coordinates": parsed_path } feature = { diff --git a/app/mapController.py b/app/mapController.py index 855db90..d93d84f 100644 --- a/app/mapController.py +++ b/app/mapController.py @@ -9,21 +9,79 @@ def swap_latlon(coords): # [ [lon, lat] for lat, lon in ring ] # for ring in coords # ] - return coords + return [ - [ # Polygon - [ # Ring + [ + [ [lon, lat] for lat, lon in ring ] for ring in polygon ] for polygon in coords ] +def swap_latlon2(coords): + try: + sample = coords[0][0][0] + if not (-180 <= sample[0] <= 180 and -90 <= sample[1] <= 90): + return [ + [ + [ + [lon, lat] for lat, lon in ring + ] for ring in polygon + ] for polygon in coords + ] + else: + # Sudah benar [lon, lat] + return coords + except Exception as e: + print(f"[swap_latlon] Error during coordinate swap: {e}") + return None + +def normalize_to_multipolygon(coords): + if isinstance(coords, list): + if ( + isinstance(coords[0], list) + and isinstance(coords[0][0], list) + and isinstance(coords[0][0][0], list) + and isinstance(coords[0][0][0][0], (int, float)) + ): + return coords # Sudah valid + else: + # Kurang dimensi → bungkus + return [[[coord for coord in ring] for ring in coords]] + return coords + +def audit_geojson_paths(conn, table): + cur = conn.cursor() + cur.execute(f"SELECT id, path FROM {table}") + errors = [] + for row in cur.fetchall(): + try: + coords = json.loads(row[1]) + sample_point = coords[0][0][0] + if not (-180 <= sample_point[0] <= 180 and -90 <= sample_point[1] <= 90): + errors.append(row[0]) + except Exception as e: + errors.append(row[0]) + print(f"[AUDIT] Invalid GeoJSON paths found in {table}: {errors}") + return errors #MAP DATA #========================================================= def fetch_geojson(conn, level, parent_code=None): - # cur = conn.cursor() - # cur.execute(f"SELECT id, kode, nama, lat, lng, path, iso FROM {table_name} WHERE id = 1 LIMIT 1") + + table_name = { + 'provinsi': 'wil_provinsi', + 'kabupaten': 'wil_kabupatenkota', + 'kecamatan': 'wil_kecamatan', + 'desa': 'wil_desa' + }.get(level) + + if not table_name: + return {"type": "FeatureCollection", "features": []} + + # Jalankan audit + invalid_ids = audit_geojson_paths(conn, table_name) + cur = conn.cursor() if level == 'provinsi': old_sql = """ @@ -68,27 +126,22 @@ def fetch_geojson(conn, level, parent_code=None): for row in cur.fetchall(): path = row[5] - # path = swap_latlon(path) - # if not path: - # continue - # geometry = { - # "type": "Polygon", - # "coordinates": json.loads(path) - # } - + #Multi poligons try: parsed_path = json.loads(path) - corrected_path = swap_latlon(parsed_path) + normalized = normalize_to_multipolygon(parsed_path) + corrected_path = swap_latlon2(normalized) + except Exception as e: print(f"Error parsing path for row {row[0]}: {e}") continue if not corrected_path: continue - + geometry = { - "type": "MultiPolygon", # karena bentuk datamu seperti itu + "type": "MultiPolygon", "coordinates": corrected_path }