commit 084b171dbda959bb2e46aa1cf8ac823ff6bda872 Author: dena27 Date: Wed Jun 10 18:48:37 2026 +0700 Initial commit diff --git a/01/api/db.php b/01/api/db.php new file mode 100644 index 0000000..b687965 --- /dev/null +++ b/01/api/db.php @@ -0,0 +1,15 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); +} catch (PDOException $e) { + http_response_code(500); + echo json_encode(['status' => 'error', 'message' => 'Koneksi database gagal: ' . $e->getMessage()]); + exit; +} \ No newline at end of file diff --git a/01/api/jalan.php b/01/api/jalan.php new file mode 100644 index 0000000..66a6a0f --- /dev/null +++ b/01/api/jalan.php @@ -0,0 +1,98 @@ +query("SELECT * FROM jalan ORDER BY createdat DESC")->fetchAll(); + echo json_encode(['status' => 'success', 'data' => $rows]); + break; + + case 'create': + $input = json_decode(file_get_contents('php://input'), true); + $nama = trim($input['nama'] ?? ''); + $status = $input['status'] ?? 'kabupaten'; + $panjang = isset($input['panjang_meter']) ? (float)$input['panjang_meter'] : null; + $geom = $input['geom'] ?? null; + + if (!$nama) { + echo json_encode(['status' => 'error', 'message' => 'Nama jalan wajib diisi.']); + break; + } + if (!in_array($status, ['nasional','provinsi','kabupaten'])) $status = 'kabupaten'; + + // Validasi dan konversi geometri LineString + $geojson = null; + if (is_array($geom)) { + if (isset($geom['type']) && $geom['type'] === 'LineString' && isset($geom['coordinates'])) { + $geojson = json_encode($geom); + } elseif (isset($geom[0]) && is_array($geom[0])) { + // array of points [[lng,lat],...] + $geojson = json_encode([ + 'type' => 'LineString', + 'coordinates' => $geom + ]); + } + } elseif (is_string($geom)) { + $decoded = json_decode($geom, true); + if ($decoded && isset($decoded['type']) && $decoded['type'] === 'LineString') { + $geojson = $geom; + } else { + echo json_encode(['status'=>'error','message'=>'Format geometri tidak valid.']); + break; + } + } + + if (!$geojson) { + echo json_encode(['status' => 'error', 'message' => 'Geometri tidak valid. Harus berupa GeoJSON LineString atau array koordinat.']); + break; + } + + $stmt = $db->prepare("INSERT INTO jalan (nama, status, panjang_meter, geom) VALUES (?, ?, ?, ?)"); + $stmt->execute([$nama, $status, $panjang, $geojson]); + echo json_encode(['status'=>'success','message'=>'Data jalan berhasil disimpan!','id'=>$db->lastInsertId()]); + break; + + case 'update': + $input = json_decode(file_get_contents('php://input'), true); + $id = (int)($input['id'] ?? 0); + $nama = trim($input['nama'] ?? ''); + $status = $input['status'] ?? 'kabupaten'; + + if (!$id) { echo json_encode(['status'=>'error','message'=>'ID tidak valid.']); break; } + if (!$nama) { echo json_encode(['status'=>'error','message'=>'Nama jalan wajib diisi.']); break; } + if (!in_array($status, ['nasional','provinsi','kabupaten'])) $status = 'kabupaten'; + + $stmt = $db->prepare("UPDATE jalan SET nama=?, status=?, updatedat=NOW() WHERE id=?"); + $stmt->execute([$nama, $status, $id]); + echo json_encode(['status'=>'success','message'=>'Data jalan berhasil diperbarui.']); + break; + + case 'delete': + $input = json_decode(file_get_contents('php://input'), true); + $id = (int)($input['id'] ?? 0); + if (!$id) { echo json_encode(['status'=>'error','message'=>'ID tidak valid.']); break; } + $stmt = $db->prepare("DELETE FROM jalan WHERE id=?"); + $stmt->execute([$id]); + echo json_encode(['status'=>'success','message'=>'Data jalan berhasil dihapus.']); + break; + + default: + echo json_encode(['status'=>'error','message'=>'Aksi tidak dikenali.']); + } +} catch (PDOException $e) { + echo json_encode(['status'=>'error','message'=>'Database error: '.$e->getMessage()]); +} \ No newline at end of file diff --git a/01/api/parsil.php b/01/api/parsil.php new file mode 100644 index 0000000..317b8a1 --- /dev/null +++ b/01/api/parsil.php @@ -0,0 +1,126 @@ +query("SELECT * FROM parsil ORDER BY createdat DESC")->fetchAll(); + echo json_encode(['status' => 'success', 'data' => $rows]); + break; + + case 'create': + $input = json_decode(file_get_contents('php://input'), true); + $nama = trim($input['nama'] ?? ''); + $status = $input['status_kepemilikan'] ?? 'SHM'; + $luas = isset($input['luas_m2']) ? (float)$input['luas_m2'] : null; + $geom = $input['geom'] ?? null; + + if (!$nama) { + echo json_encode(['status' => 'error', 'message' => 'Nama parsil wajib diisi.']); + break; + } + $validStatus = ['SHM','HGB','HGU','HP']; + if (!in_array($status, $validStatus)) $status = 'SHM'; + + // Validasi dan konversi geometri Polygon + $geojson = null; + if (is_array($geom)) { + if (isset($geom['type']) && $geom['type'] === 'Polygon' && isset($geom['coordinates'])) { + $geojson = json_encode($geom); + } elseif (isset($geom[0]) && is_array($geom[0])) { + // array of rings [[lng,lat],...] + $geojson = json_encode([ + 'type' => 'Polygon', + 'coordinates' => $geom + ]); + } + } elseif (is_string($geom)) { + $decoded = json_decode($geom, true); + if ($decoded && isset($decoded['type']) && $decoded['type'] === 'Polygon') { + $geojson = $geom; + } else { + echo json_encode(['status'=>'error','message'=>'Format geometri tidak valid.']); + break; + } + } + + if (!$geojson) { + echo json_encode(['status' => 'error', 'message' => 'Geometri tidak valid. Harus berupa GeoJSON Polygon atau array koordinat.']); + break; + } + + $stmt = $db->prepare("INSERT INTO parsil (nama, status_kepemilikan, luas_m2, geom) VALUES (?, ?, ?, ?)"); + $stmt->execute([$nama, $status, $luas, $geojson]); + echo json_encode(['status'=>'success','message'=>'Data parsil berhasil disimpan!','id'=>$db->lastInsertId()]); + break; + + case 'update': + $input = json_decode(file_get_contents('php://input'), true); + $id = (int)($input['id'] ?? 0); + $nama = trim($input['nama'] ?? ''); + $status = $input['status_kepemilikan'] ?? 'SHM'; + $geom = $input['geom'] ?? null; + $luas = isset($input['luas_m2']) ? (float)$input['luas_m2'] : null; + + if (!$id) { echo json_encode(['status'=>'error','message'=>'ID tidak valid.']); break; } + if (!$nama) { echo json_encode(['status'=>'error','message'=>'Nama parsil wajib diisi.']); break; } + $validStatus = ['SHM','HGB','HGU','HP']; + if (!in_array($status, $validStatus)) $status = 'SHM'; + + // If geometry provided, validate/convert to GeoJSON string + $geojson = null; + if ($geom) { + if (is_array($geom)) { + if (isset($geom['type']) && $geom['type'] === 'Polygon' && isset($geom['coordinates'])) { + $geojson = json_encode($geom); + } elseif (isset($geom[0]) && is_array($geom[0])) { + $geojson = json_encode(['type'=>'Polygon','coordinates'=>$geom]); + } + } elseif (is_string($geom)) { + $decoded = json_decode($geom, true); + if ($decoded && isset($decoded['type']) && $decoded['type'] === 'Polygon') $geojson = $geom; + } + if (!$geojson) { echo json_encode(['status'=>'error','message'=>'Format geometri tidak valid untuk update.']); break; } + } + + // Build update query dynamically + $fields = ['nama = ?', 'status_kepemilikan = ?', 'updatedat = NOW()']; + $params = [$nama, $status]; + if ($luas !== null) { $fields[] = 'luas_m2 = ?'; $params[] = $luas; } + if ($geojson !== null) { $fields[] = 'geom = ?'; $params[] = $geojson; } + $params[] = $id; + + $sql = "UPDATE parsil SET " . implode(', ', $fields) . " WHERE id=?"; + $stmt = $db->prepare($sql); + $stmt->execute($params); + echo json_encode(['status'=>'success','message'=>'Data parsil berhasil diperbarui.']); + break; + + case 'delete': + $input = json_decode(file_get_contents('php://input'), true); + $id = (int)($input['id'] ?? 0); + if (!$id) { echo json_encode(['status'=>'error','message'=>'ID tidak valid.']); break; } + $stmt = $db->prepare("DELETE FROM parsil WHERE id=?"); + $stmt->execute([$id]); + echo json_encode(['status'=>'success','message'=>'Data parsil berhasil dihapus.']); + break; + + default: + echo json_encode(['status'=>'error','message'=>'Aksi tidak dikenali.']); + } +} catch (PDOException $e) { + echo json_encode(['status'=>'error','message'=>'Database error: '.$e->getMessage()]); +} \ No newline at end of file diff --git a/01/api/spbu.php b/01/api/spbu.php new file mode 100644 index 0000000..ea64d62 --- /dev/null +++ b/01/api/spbu.php @@ -0,0 +1,141 @@ +query("SELECT id, namaspbu, nomorwa, statusbuka, + JSON_EXTRACT(koordinat, '$.coordinates[1]') as lat, + JSON_EXTRACT(koordinat, '$.coordinates[0]') as lng, + koordinat + FROM spbu ORDER BY createdat DESC")->fetchAll(); + foreach ($rows as &$r) { + $r['lat'] = (float)$r['lat']; + $r['lng'] = (float)$r['lng']; + } + echo json_encode(['status' => 'success', 'data' => $rows]); + break; + + case 'create': + $input = json_decode(file_get_contents('php://input'), true); + $namaspbu = trim($input['namaspbu'] ?? ''); + $nomorwa = trim($input['nomorwa'] ?? ''); + $statusbuka = $input['statusbuka'] ?? 'limited'; + $koordinat = $input['koordinat'] ?? null; + + if (!$namaspbu) { + echo json_encode(['status'=>'error','message'=>'Nama SPBU wajib diisi.']); + break; + } + if (!in_array($statusbuka, ['full','limited'])) $statusbuka = 'limited'; + + // Validasi dan konversi geometri + $geojson = null; + if (is_array($koordinat)) { + // Cek apakah sudah dalam format GeoJSON Point + if (isset($koordinat['type']) && $koordinat['type'] === 'Point' && isset($koordinat['coordinates'])) { + $geojson = json_encode($koordinat); + } + // Mungkin hanya array [lng, lat] + elseif (isset($koordinat[0]) && isset($koordinat[1])) { + $geojson = json_encode([ + 'type' => 'Point', + 'coordinates' => [(float)$koordinat[0], (float)$koordinat[1]] + ]); + } + // Mungkin objek dengan lat,lng + elseif (isset($koordinat['lat']) && isset($koordinat['lng'])) { + $geojson = json_encode([ + 'type' => 'Point', + 'coordinates' => [(float)$koordinat['lng'], (float)$koordinat['lat']] + ]); + } + } elseif (is_string($koordinat)) { + // Coba parse JSON + $decoded = json_decode($koordinat, true); + if ($decoded && isset($decoded['type']) && $decoded['type'] === 'Point') { + $geojson = $koordinat; + } else { + echo json_encode(['status'=>'error','message'=>'Format koordinat tidak valid.']); + break; + } + } + + if (!$geojson) { + echo json_encode(['status'=>'error','message'=>'Geometri tidak valid. Harus berupa GeoJSON Point atau koordinat [lng, lat].']); + break; + } + + $stmt = $db->prepare("INSERT INTO spbu (namaspbu, nomorwa, statusbuka, koordinat) VALUES (?, ?, ?, ?)"); + $stmt->execute([$namaspbu, $nomorwa, $statusbuka, $geojson]); + echo json_encode(['status'=>'success','message'=>'Data berhasil disimpan!','id'=>$db->lastInsertId()]); + break; + + case 'update': + $input = json_decode(file_get_contents('php://input'), true); + $id = (int)($input['id'] ?? 0); + $namaspbu = trim($input['namaspbu'] ?? ''); + $nomorwa = trim($input['nomorwa'] ?? ''); + $statusbuka = $input['statusbuka'] ?? 'limited'; + $koordinat = $input['koordinat'] ?? null; + + if (!$id) { + echo json_encode(['status'=>'error','message'=>'ID tidak valid.']); + break; + } + + // =============================== + // JIKA ADA KOORDINAT → UPDATE KOORDINAT + // =============================== + if ($koordinat) { + $geojson = json_encode($koordinat); + + $stmt = $db->prepare("UPDATE spbu SET koordinat=?, updatedat=NOW() WHERE id=?"); + $stmt->execute([$geojson, $id]); + + echo json_encode(['status'=>'success','message'=>'Koordinat berhasil diperbarui!']); + break; + } + + // =============================== + // UPDATE DATA BIASA + // =============================== + if (!$namaspbu) { + echo json_encode(['status'=>'error','message'=>'Nama SPBU wajib diisi.']); + break; + } + + $stmt = $db->prepare("UPDATE spbu SET namaspbu=?, nomorwa=?, statusbuka=?, updatedat=NOW() WHERE id=?"); + $stmt->execute([$namaspbu, $nomorwa, $statusbuka, $id]); + + echo json_encode(['status'=>'success','message'=>'Data berhasil diperbarui!']); + break; + + case 'delete': + $input = json_decode(file_get_contents('php://input'), true); + $id = (int)($input['id'] ?? 0); + if (!$id) { echo json_encode(['status'=>'error','message'=>'ID tidak valid.']); break; } + $stmt = $db->prepare("DELETE FROM spbu WHERE id=?"); + $stmt->execute([$id]); + echo json_encode(['status'=>'success','message'=>'Data berhasil dihapus.']); + break; + + default: + echo json_encode(['status'=>'error','message'=>'Aksi tidak dikenali.']); + } +} catch (PDOException $e) { + echo json_encode(['status'=>'error','message'=>'Database error: '.$e->getMessage()]); +} \ No newline at end of file diff --git a/01/css/style.css b/01/css/style.css new file mode 100644 index 0000000..6ebafe2 --- /dev/null +++ b/01/css/style.css @@ -0,0 +1,461 @@ +:root { + --bg: #f0f4f8; + --sf: #ffffff; + --sf2: #f7f9fc; + --sf3: #eef2f7; + --bd: #dde3ec; + --bdh: #b0bac8; + --tx: #1a2030; + --txs: #4a5568; + --txd: #8a96a8; + + --spbu-24-fill: #22c55e; + --spbu-24-bg: rgba(34,197,94,.10); + --spbu-24-ring: rgba(34,197,94,.25); + --spbu-ltd-fill: #f87171; + --spbu-ltd-bg: rgba(248,113,113,.10); + --spbu-ltd-ring: rgba(248,113,113,.25); + + --jalan-nas: #f97316; + --jalan-nas-bg: rgba(249,115,22,.10); + --jalan-nas-ring: rgba(249,115,22,.28); + --jalan-prov: #3b82f6; + --jalan-prov-bg: rgba(59,130,246,.10); + --jalan-prov-ring: rgba(59,130,246,.28); + --jalan-kab: #10b981; + --jalan-kab-bg: rgba(16,185,129,.10); + --jalan-kab-ring: rgba(16,185,129,.28); + + --shm-stroke: #d97706; --shm-fill: #fef3c7; --shm-bg: rgba(217,119,6,.10); --shm-ring: rgba(217,119,6,.28); + --hgb-stroke: #8b5cf6; --hgb-fill: #ede9fe; --hgb-bg: rgba(139,92,246,.10); --hgb-ring: rgba(139,92,246,.28); + --hgu-stroke: #0891b2; --hgu-fill: #cffafe; --hgu-bg: rgba(8,145,178,.10); --hgu-ring: rgba(8,145,178,.28); + --hp-stroke: #ec4899; --hp-fill: #fce7f3; --hp-bg: rgba(236,72,153,.10); --hp-ring: rgba(236,72,153,.28); + + --green: #16a34a; + --greenb: rgba(22,163,74,.10); + --greenr: rgba(22,163,74,.25); + --red: #dc2626; + --redb: rgba(220,38,38,.10); + --redr: rgba(220,38,38,.25); + --blue: #2563eb; + --blueb: rgba(37,99,235,.10); + --bluer: rgba(37,99,235,.25); + --amber: #d97706; + --amberb: rgba(217,119,6,.10); + --amberr: rgba(217,119,6,.25); + --purple: #7c3aed; + --purpleb: rgba(124,58,237,.10); + --purpler: rgba(124,58,237,.25); + --indigo: #4f46e5; + --indigob: rgba(79,70,229,.10); + --indigor: rgba(79,70,229,.25); + + --sh-sm: 0 1px 3px rgba(0,0,0,.08), 0 1px 2px rgba(0,0,0,.06); + --sh: 0 4px 16px rgba(0,0,0,.10), 0 1px 4px rgba(0,0,0,.06); + --sh-lg: 0 12px 40px rgba(0,0,0,.14), 0 4px 12px rgba(0,0,0,.08); + + --sidebar: 275px; + --topbar: 52px; + --radius: 10px; + --radius-lg: 14px; +} + +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } +html, body { + height: 100%; overflow: hidden; + font-family: 'Plus Jakarta Sans', sans-serif; + font-size: 14px; background: var(--bg); color: var(--tx); + -webkit-font-smoothing: antialiased; +} +#app { display: flex; height: 100vh; } + +/* ── LOADING ── */ +#loading { position:fixed; inset:0; z-index:9999; background:var(--bg); display:flex; align-items:center; justify-content:center; } +.ld-inner { display:flex; flex-direction:column; align-items:center; gap:14px; } +.ld-inner p { font-size:13px; color:var(--txd); } +.spin { width:32px; height:32px; border:3px solid var(--bd); border-top-color:var(--green); border-radius:50%; animation:rot .7s linear infinite; } +@keyframes rot { to { transform:rotate(360deg); } } + +/* ── SIDEBAR ── */ +#sidebar { width:var(--sidebar); min-width:var(--sidebar); background:var(--sf); border-right:1px solid var(--bd); display:flex; flex-direction:column; box-shadow:var(--sh-sm); z-index:900; } +.sb-header { padding:14px 16px; border-bottom:1px solid var(--bd); } +.sb-brand { display:flex; align-items:center; gap:10px; } +.sb-icon { width:36px; height:36px; border-radius:9px; flex-shrink:0; background:var(--greenb); border:1px solid var(--greenr); display:flex; align-items:center; justify-content:center; color:var(--green); font-size:18px; } +.sb-title { font-size:14px; font-weight:800; } +.sb-sub { font-size:10px; color:var(--txd); margin-top:1px; } +.sb-scroll { flex:1; overflow-y:auto; padding:12px; display:flex; flex-direction:column; gap:14px; } +.sb-scroll::-webkit-scrollbar { width:4px; } +.sb-scroll::-webkit-scrollbar-thumb { background:var(--bd); border-radius:4px; } +.sb-section { display:flex; flex-direction:column; gap:6px; } +.sb-label { font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:.8px; color:var(--txd); } + +/* ── LEGEND CARD ── */ +.legend-card { background:var(--sf2); border:1px solid var(--bd); border-radius:var(--radius); padding:10px 12px; display:flex; flex-direction:column; gap:7px; } +.leg-row { display:flex; align-items:center; gap:9px; font-size:12px; color:var(--txs); font-weight:500; } +.leg-dot { width:12px; height:12px; border-radius:50%; flex-shrink:0; border:2px solid rgba(0,0,0,.08); } +.leg-dot.spbu-24 { background:var(--spbu-24-fill); box-shadow:0 0 0 3px var(--spbu-24-bg); } +.leg-dot.spbu-ltd { background:var(--spbu-ltd-fill); box-shadow:0 0 0 3px var(--spbu-ltd-bg); } +.leg-line { width:24px; height:4px; border-radius:3px; flex-shrink:0; } +.leg-line.jalan-nas { background:var(--jalan-nas); } +.leg-line.jalan-prov { background:var(--jalan-prov); } +.leg-line.jalan-kab { background:var(--jalan-kab); } +.leg-poly { width:18px; height:13px; border-radius:3px; flex-shrink:0; border:2px solid; } +.leg-poly.parsil-shm { background:var(--shm-fill); border-color:var(--shm-stroke); } +.leg-poly.parsil-hgb { background:var(--hgb-fill); border-color:var(--hgb-stroke); } +.leg-poly.parsil-hgu { background:var(--hgu-fill); border-color:var(--hgu-stroke); } +.leg-poly.parsil-hp { background:var(--hp-fill); border-color:var(--hp-stroke); } + +/* ── STATS ── */ +.stat-grid { display:grid; grid-template-columns:1fr 1fr; gap:7px; } +.stat-item { background:var(--sf2); border:1px solid var(--bd); border-radius:var(--radius); padding:10px; text-align:center; } +.stat-item.green { background:var(--greenb); border-color:var(--greenr); } +.stat-item.blue { background:var(--blueb); border-color:var(--bluer); } +.stat-item.purple { background:var(--purpleb);border-color:var(--purpler); } +.stat-num { font-size:22px; font-weight:800; font-family:'Fira Code',monospace; color:var(--tx); } +.stat-item.green .stat-num { color:var(--green); } +.stat-item.blue .stat-num { color:var(--blue); } +.stat-item.purple .stat-num { color:var(--purple); } +.stat-lbl { font-size:10px; color:var(--txd); margin-top:2px; } + +/* ── SPBU LIST ── */ +.spbu-item { background:var(--sf); border:1px solid var(--bd); border-radius:var(--radius); padding:9px 11px; cursor:pointer; transition:border-color .18s, background .18s; margin-bottom:5px; display:flex; flex-direction:column; gap:6px; } +.spbu-item:hover { border-color:var(--green); background:var(--greenb); } +.si-top { display:flex; align-items:center; justify-content:space-between; gap:6px; } +.si-name { font-size:13px; font-weight:700; display:flex; align-items:center; gap:7px; } +.si-dot { width:8px; height:8px; border-radius:50%; flex-shrink:0; } +.si-dot.dot-green { background:var(--spbu-24-fill); box-shadow:0 0 0 2px var(--spbu-24-bg); } +.si-dot.dot-red { background:var(--spbu-ltd-fill); box-shadow:0 0 0 2px var(--spbu-ltd-bg); } +.si-actions { display:flex; gap:4px; flex-shrink:0; } +.empty { text-align:center; padding:24px 0; font-size:12px; color:var(--txd); } + +/* ── ICON BUTTONS ── */ +.ib { width:34px; height:34px; border-radius:8px; background:var(--sf2); border:1px solid var(--bd); color:var(--txs); cursor:pointer; display:flex; align-items:center; justify-content:center; transition:all .18s; flex-shrink:0; font-size:13px; } +.ib.edt { background:linear-gradient(180deg,#fff8f0,#fff1e6); border-color:rgba(217,119,6,.12); color:var(--amber); } +.ib.del { background:linear-gradient(180deg,#fff7f7,#fff0f0); border-color:rgba(220,38,38,.08); color:var(--red); } +.ib:hover { transform:translateY(-2px); box-shadow:0 6px 18px rgba(10,20,40,.06); } + +/* ── BADGE ── */ +.badge { display:inline-flex; align-items:center; font-size:10px; font-weight:700; padding:2px 8px; border-radius:20px; border:1px solid; } +.badge.bg { background:var(--spbu-24-bg); border-color:var(--spbu-24-ring); color:#15803d; } +.badge.br { background:var(--spbu-ltd-bg); border-color:var(--spbu-ltd-ring); color:#dc2626; } +.badge.bb-orange { background:var(--jalan-nas-bg); border-color:var(--jalan-nas-ring); color:#c2410c; } +.badge.bb-blue { background:var(--jalan-prov-bg); border-color:var(--jalan-prov-ring); color:#1d4ed8; } +.badge.bb-green { background:var(--jalan-kab-bg); border-color:var(--jalan-kab-ring); color:#065f46; } +.badge.bp-shm { background:var(--shm-bg); border-color:var(--shm-ring); color:var(--shm-stroke); } +.badge.bp-hgb { background:var(--hgb-bg); border-color:var(--hgb-ring); color:var(--hgb-stroke); } +.badge.bp-hgu { background:var(--hgu-bg); border-color:var(--hgu-ring); color:var(--hgu-stroke); } +.badge.bp-hp { background:var(--hp-bg); border-color:var(--hp-ring); color:var(--hp-stroke); } + +/* ── GEOM SELECTOR ── */ +.geom-selector { display:flex; gap:3px; background:var(--sf2); border:1px solid var(--bd); border-radius:9px; padding:3px; } +.geom-btn { flex:1; display:flex; align-items:center; justify-content:center; gap:5px; padding:6px 8px; border-radius:7px; border:none; background:transparent; color:var(--txs); font-family:'Plus Jakarta Sans',sans-serif; font-size:11px; font-weight:600; cursor:pointer; transition:all .18s; white-space:nowrap; } +.geom-btn:hover { background:var(--sf); color:var(--tx); } +.geom-btn.active { background:var(--green); color:#fff; box-shadow:0 2px 8px rgba(22,163,74,.3); } +.geom-hint { font-size:10px; color:var(--txd); text-align:left; margin-top:4px; min-height:14px; padding-left:2px; transition:color .18s; display:flex; align-items:center; gap:5px; } +.geom-hint .gh-dot { width:6px; height:6px; border-radius:50%; background:var(--bdh); flex-shrink:0; transition:background .18s; } +.geom-hint.on { color:var(--green); font-weight:600; } +.geom-hint.on .gh-dot { background:var(--green); box-shadow:0 0 0 2px rgba(22,163,74,.2); } + +/* ── MAIN ── */ +#main { flex:1; display:flex; flex-direction:column; overflow:hidden; position:relative; } +#topbar { height:var(--topbar); min-height:var(--topbar); background:var(--sf); border-bottom:1px solid var(--bd); display:flex; align-items:center; padding:0 12px; gap:8px; box-shadow:var(--sh-sm); overflow:visible; position:relative; z-index:850; } +.coord-pill { flex:0 0 auto; font-family:'Fira Code',monospace; font-size:11px; color:var(--txd); background:var(--sf2); border:1px solid var(--bd); border-radius:7px; padding:4px 10px; display:flex; align-items:center; gap:5px; white-space:nowrap; } +.pill-sep { color:var(--bdh); } + +/* ── SEARCH ── */ +#search-wrap { + flex: 1; + position: relative; + max-width: 340px; + z-index: 1300; +} + +.search-box { + display: flex; + align-items: center; + gap: 7px; + background: var(--sf2); + border: 1.5px solid var(--bd); + border-radius: 9px; + padding: 0 10px; + height: 34px; + transition: border-color .18s, box-shadow .18s; +} + +.search-box:focus-within { + border-color: var(--green); + box-shadow: 0 0 0 3px rgba(22,163,74,.08); +} + +.search-icon { font-size: 13px; flex-shrink: 0; opacity: .6; } + +#search-input { + flex: 1; + border: none; + background: transparent; + outline: none; + font-family: 'Plus Jakarta Sans', sans-serif; + font-size: 13px; + color: var(--tx); +} + +#search-input::placeholder { color: var(--txd); } + +#search-results { + position: absolute; + top: calc(100% + 6px); + left: 0; + right: 0; + background: var(--sf); + border: 1px solid var(--bd); + border-radius: 11px; + box-shadow: var(--sh-lg); + overflow: hidden; + opacity: 0; + pointer-events: none; + transform: translateY(6px); + transition: opacity .18s, transform .18s; + max-height: 320px; + overflow-y: auto; +} + +#search-results.open { + opacity: 1; + pointer-events: all; + transform: translateY(0); +} + +.sr-item { + display: flex; + align-items: center; + gap: 10px; + padding: 9px 12px; + cursor: pointer; + transition: background .14s; + border-bottom: 1px solid var(--sf3); +} + +.sr-item:last-child { border-bottom: none; } + +.sr-item:hover { background: var(--sf2); } + +.sr-icon { + width: 30px; + height: 30px; + border-radius: 8px; + background: var(--sf2); + border: 1px solid var(--bd); + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + flex-shrink: 0; +} + +.sr-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; } +.sr-label { font-size: 13px; font-weight: 700; color: var(--tx); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.sr-sub { font-size: 10px; color: var(--txd); font-weight: 500; } +.sr-empty { padding: 16px 12px; font-size: 12px; color: var(--txd); text-align: center; } + +/* ── LAYER DROPDOWN ── */ +.layer-wrap { position:relative; flex-shrink:0; z-index:1100; } +.layer-btn { height:34px; padding:0 12px; border-radius:8px; background:var(--sf2); border:1px solid var(--bd); color:var(--txs); font-size:12px; font-weight:600; font-family:'Plus Jakarta Sans',sans-serif; cursor:pointer; display:flex; align-items:center; gap:6px; transition:all .18s; white-space:nowrap; } +.layer-btn:hover { border-color:var(--bdh); color:var(--tx); } +.layer-menu { position:absolute; top:calc(100% + 6px); right:0; background:var(--sf); border:1px solid var(--bd); border-radius:11px; padding:6px; min-width:200px; box-shadow:var(--sh-lg); z-index:1200; opacity:0; pointer-events:none; transform:translateY(6px); transition:opacity .18s, transform .18s; } +.layer-menu.open { opacity:1; pointer-events:all; transform:translateY(0); } +.lm-label { font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:.8px; color:var(--txd); padding:4px 8px 6px; } +.lm-item { width:100%; display:flex; align-items:center; gap:8px; padding:7px 8px; border-radius:7px; border:none; background:transparent; color:var(--txs); font-size:12px; font-family:'Plus Jakarta Sans',sans-serif; cursor:pointer; transition:all .15s; text-align:left; } +.lm-item:hover { background:var(--sf2); color:var(--tx); } +.lm-item.active { color:var(--tx); font-weight:600; } + +/* ── MAP ── */ +#map-wrap { flex:1; position:relative; overflow:hidden; } +#map { width:100%; height:100%; } +.leaflet-container.crosshair-cursor { cursor:crosshair !important; } + +/* ── DRAW TOOLBAR ── */ +#draw-toolbar, #geom-edit-toolbar { + position:absolute; bottom:28px; left:50%; transform:translateX(-50%) translateY(12px); + z-index:1000; display:flex; align-items:center; gap:5px; + background:var(--sf); border:1px solid var(--bd); border-radius:14px; + padding:8px 14px; box-shadow:var(--sh-lg); + pointer-events:all; opacity:0; visibility:hidden; + transition:opacity .22s, transform .22s, visibility .22s; +} +#draw-toolbar.visible, #geom-edit-toolbar.visible { + opacity:1; visibility:visible; transform:translateX(-50%) translateY(0); +} + +/* geom-edit toolbar sits a bit higher so they don't overlap */ +#geom-edit-toolbar { bottom: 28px; } +#draw-toolbar.visible ~ #geom-edit-toolbar.visible { bottom: 90px; } + +.dt-hint { font-size:12px; font-weight:600; color:var(--txs); padding:0 4px; white-space:nowrap; } +.dt-counter { font-family:'Fira Code',monospace; font-size:12px; font-weight:600; color:var(--blue); background:var(--blueb); border:1px solid var(--bluer); border-radius:7px; padding:4px 10px; white-space:nowrap; } +.geom-edit-counter { color:var(--indigo); background:var(--indigob); border-color:var(--indigor); min-width:120px; } +.dt-btn { display:flex; align-items:center; gap:5px; padding:7px 13px; border-radius:9px; border:1.5px solid var(--bd); background:var(--sf2); color:var(--txs); font-family:'Plus Jakarta Sans',sans-serif; font-size:12px; font-weight:600; cursor:pointer; transition:all .18s; white-space:nowrap; flex-shrink:0; } +.dt-btn:hover { border-color:var(--bdh); background:var(--sf); color:var(--tx); } +.dt-btn:disabled { opacity:.4; cursor:not-allowed; } +.dt-btn.undo:hover { background:var(--amberb); border-color:var(--amberr); color:var(--amber); } +.dt-btn.redo:hover { background:var(--blueb); border-color:var(--bluer); color:var(--blue); } +.dt-btn.cancel { background:var(--redb); border-color:var(--redr); color:var(--red); } +.dt-btn.cancel:hover { background:var(--red); color:#fff; border-color:var(--red); } +.dt-btn.finish { background:var(--greenb); border-color:var(--greenr); color:var(--green); } +.dt-btn.finish:hover { background:var(--green); color:#fff; border-color:var(--green); } + +/* ── POPUP ── */ +.leaflet-popup-content-wrapper { background:var(--sf) !important; border:1px solid var(--bd) !important; border-radius:14px !important; box-shadow:var(--sh-lg) !important; padding:0 !important; overflow:hidden !important; color:var(--tx) !important; } +.leaflet-popup-content { margin:0 !important; width:auto !important; } +.leaflet-popup-tip-container { display:none; } +.leaflet-popup-close-button { top:10px !important; right:12px !important; width:26px !important; height:26px !important; font-size:14px !important; line-height:26px !important; color:var(--txd) !important; background:var(--sf2) !important; border-radius:8px !important; border:1px solid var(--bd) !important; } + +/* ── POPUP DETAIL CARD ── */ +.pcard-detail { min-width:280px; max-width:360px; font-family:'Plus Jakarta Sans',sans-serif; } +.pcd-title { font-size:16px; font-weight:800; color:var(--tx); padding:12px 16px 4px; display:flex; align-items:center; gap:8px; border-bottom:1px solid var(--bd); margin-bottom:8px; } +.pcd-title[data-type="spbu"]::before { content:"⛽"; font-size:18px; } +.pcd-title[data-type="jalan"]::before { content:"🛣️"; font-size:18px; } +.pcd-title[data-type="parsil"]::before { content:"🗺️"; font-size:18px; } +.pcd-rows { display:flex; flex-direction:column; gap:10px; padding:0 16px 12px; } +.pcd-row { display:flex; align-items:center; justify-content:space-between; background:var(--sf2); padding:8px 12px; border-radius:12px; border:1px solid var(--bd); transition:all .2s; } +.pcd-row:hover { background:var(--sf); border-color:var(--bdh); } +.pcd-label { font-size:12px; font-weight:600; color:var(--txd); display:flex; align-items:center; gap:6px; } +.pcd-val { font-size:13px; font-weight:700; color:var(--tx); text-align:right; word-break:break-word; max-width:180px; } +.pcd-actions { display:flex; gap:7px; padding:8px 16px 16px; border-top:1px solid var(--bd); margin-top:4px; flex-wrap:wrap; } + +/* ── POPUP BUTTONS ── */ +.pbtn { + flex: 1; + min-width: 80px; + display: flex; + align-items: center; + justify-content: center; + gap: 5px; + padding: 8px 10px; + border-radius: 10px; + font-size: 12px; + font-weight: 700; + cursor: pointer; + transition: all .2s; + border: none; + font-family: 'Plus Jakarta Sans', sans-serif; +} + +.pbtn-edit { + background: linear-gradient(135deg, #fffbeb, #fff3e0); + color: #d97706; + border: 1.5px solid #fde68a; +} +.pbtn-edit:hover { + background: linear-gradient(135deg, #fef3c7, #ffedd5); + transform: translateY(-1px); + box-shadow: 0 4px 10px rgba(217,119,6,.15); +} + +.pbtn-move { + background: linear-gradient(135deg, #eef2ff, #e0e7ff); + color: #4f46e5; + border: 1.5px solid #c7d2fe; +} +.pbtn-move:hover { + background: linear-gradient(135deg, #e0e7ff, #c7d2fe); + transform: translateY(-1px); + box-shadow: 0 4px 10px rgba(79,70,229,.15); +} + +.pbtn-del { + background: linear-gradient(135deg, #fef2f2, #fee2e2); + color: #dc2626; + border: 1.5px solid #fecaca; +} +.pbtn-del:hover { + background: linear-gradient(135deg, #fee2e2, #fecaca); + transform: translateY(-1px); + box-shadow: 0 4px 10px rgba(220,38,38,.15); +} + +/* ── OVERLAY / MODAL ── */ +.overlay { position:fixed; inset:0; z-index:2000; background:rgba(15,20,35,.45); backdrop-filter:blur(6px); display:flex; align-items:center; justify-content:center; opacity:0; pointer-events:none; transition:opacity .22s; } +.overlay.open { opacity:1; pointer-events:all; } +.modal { background:var(--sf); border:1px solid var(--bd); border-radius:var(--radius-lg); padding:22px; width:460px; max-width:96vw; box-shadow:var(--sh-lg); transform:translateY(14px) scale(.98); transition:transform .22s; } +.overlay.open .modal { transform:translateY(0) scale(1); } +.modal-head { display:flex; align-items:center; gap:11px; margin-bottom:16px; } +.modal-head h2 { font-size:15px; font-weight:800; } +.form-group { margin-bottom:12px; } +.form-group label { display:block; font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.5px; color:var(--txd); margin-bottom:5px; } +.form-group input, .form-group select { width:100%; padding:10px 12px; background:var(--sf2); border:1.5px solid var(--bd); border-radius:10px; color:var(--tx); font-size:14px; font-family:'Plus Jakarta Sans',sans-serif; outline:none; transition:border-color .18s, box-shadow .18s; appearance:none; } +.form-group input:focus, .form-group select:focus { border-color:var(--green); box-shadow:0 0 0 3px rgba(22,163,74,.08); } +.form-group input::placeholder { color:var(--txd); } +.form-group select { cursor:pointer; background-image:url('data:image/svg+xml;utf8,'); background-repeat:no-repeat; background-position:right 10px center; background-size:16px; padding-right:32px; } +.modal-foot { display:flex; gap:8px; margin-top:16px; } +.btn-ghost { flex:1; padding:10px; border-radius:8px; border:1.5px solid var(--bd); background:transparent; color:var(--txs); font-size:13px; font-weight:600; font-family:'Plus Jakarta Sans',sans-serif; cursor:pointer; transition:all .18s; } +.btn-ghost:hover { border-color:var(--bdh); color:var(--tx); background:var(--sf2); } +.btn-primary { flex:1.4; padding:10px; border-radius:8px; border:none; color:#fff; font-size:13px; font-weight:700; font-family:'Plus Jakarta Sans',sans-serif; cursor:pointer; transition:opacity .18s, transform .12s; display:flex; align-items:center; justify-content:center; gap:6px; } +.btn-primary:hover { opacity:.9; transform:translateY(-1px); } +.btn-primary.green { background:linear-gradient(135deg,#22c55e,#16a34a); } +.btn-primary.blue { background:linear-gradient(135deg,#3b82f6,#2563eb); } +.btn-primary.purple { background:linear-gradient(135deg,#a78bfa,#7c3aed); } +.btn-primary.red { background:linear-gradient(135deg,#f87171,#dc2626); } + +/* ── CUSTOM CONFIRM DIALOG ── */ +.confirm-modal { + width: 380px; + max-width: 94vw; + text-align: center; + padding: 28px 24px 22px; +} + +.confirm-icon { + font-size: 38px; + margin-bottom: 10px; + line-height: 1; +} + +.confirm-title { + font-size: 16px; + font-weight: 800; + color: var(--tx); + margin-bottom: 6px; +} + +.confirm-message { + font-size: 13px; + color: var(--txs); + margin-bottom: 0; + line-height: 1.5; +} + +.confirm-modal .modal-foot { + margin-top: 20px; + justify-content: center; +} + +.confirm-modal .btn-ghost { flex: 0 0 auto; min-width: 90px; } +.confirm-modal .btn-primary { flex: 0 0 auto; min-width: 120px; } + +/* ── MAP LABELS ── */ +.label-tooltip, .label-tooltip-line, .label-tooltip-polygon { + background: rgba(255,255,255,.95); + border: 1px solid var(--bd); + border-radius: 6px; + padding: 5px 9px; + font-size: 12px; + font-weight: 600; + color: var(--tx); + box-shadow: var(--sh-sm); + white-space: nowrap; + pointer-events: none; +} + +/* ── TOAST ── */ +#toasts { position:fixed; bottom:18px; right:18px; z-index:9999; display:flex; flex-direction:column; gap:6px; } +.toast { background:var(--sf); border:1px solid var(--bd); border-radius:9px; padding:9px 14px; font-size:12px; font-weight:600; display:flex; align-items:center; gap:8px; min-width:220px; box-shadow:var(--sh); animation:tslide .22s ease; } +.toast.ok { border-color:var(--greenr); color:var(--green); } +.toast.er { border-color:var(--redr); color:var(--red); } +@keyframes tslide { from { opacity:0; transform:translateY(8px); } to { opacity:1; transform:translateY(0); } } + +/* ── RESPONSIVE ── */ +@media (max-width:640px) { + .modal { width:92vw; padding:16px; } + .pcard-detail { min-width:200px; max-width:260px; } + #search-wrap { max-width:160px; } +} \ No newline at end of file diff --git a/01/database.sql b/01/database.sql new file mode 100644 index 0000000..4d9321a --- /dev/null +++ b/01/database.sql @@ -0,0 +1,37 @@ +-- Tabel SPBU (Point) +CREATE TABLE `spbu` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `namaspbu` varchar(150) NOT NULL, + `nomorwa` varchar(20) DEFAULT NULL, + `statusbuka` enum('full','limited') DEFAULT 'limited', + `koordinat` JSON DEFAULT NULL COMMENT 'GeoJSON Point', + `lat` decimal(10,8) GENERATED ALWAYS AS (JSON_EXTRACT(`koordinat`, '$.coordinates[1]')) STORED, + `lng` decimal(11,8) GENERATED ALWAYS AS (JSON_EXTRACT(`koordinat`, '$.coordinates[0]')) STORED, + `createdat` timestamp NOT NULL DEFAULT current_timestamp(), + `updatedat` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp(), + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- Tabel Jalan (Polyline) +CREATE TABLE `jalan` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `nama` varchar(150) NOT NULL, + `status` enum('nasional','provinsi','kabupaten') NOT NULL DEFAULT 'kabupaten', + `panjang_meter` decimal(12,2) DEFAULT NULL, + `geom` JSON NOT NULL COMMENT 'GeoJSON LineString', + `createdat` timestamp NOT NULL DEFAULT current_timestamp(), + `updatedat` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp(), + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- Tabel Parsil Tanah (Polygon) +CREATE TABLE `parsil` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `nama` varchar(150) NOT NULL, + `status_kepemilikan` enum('SHM','HGB','HGU','HP') NOT NULL DEFAULT 'SHM', + `luas_m2` decimal(14,2) DEFAULT NULL, + `geom` JSON NOT NULL COMMENT 'GeoJSON Polygon', + `createdat` timestamp NOT NULL DEFAULT current_timestamp(), + `updatedat` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp(), + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; \ No newline at end of file diff --git a/01/index.html b/01/index.html new file mode 100644 index 0000000..d548963 --- /dev/null +++ b/01/index.html @@ -0,0 +1,283 @@ + + + + + + WebGIS SPBU, Jalan & Parsil + + + + + + +
+
+
+

Memuat WebGIS…

+
+
+ +
+ + + + +
+
+
+ 📍 + | +
+ + +
+ +
+
+ +
+ +
+
Tampilkan
+ + + +
+
+
+ +
+
+ + +
+
ℹ️ Klik peta untuk menggambar
+ 0 titik + + + + +
+ + +
+
📐 Drag titik untuk edit geometri
+ + + +
+
+
+
+ + + + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + + + + + + + \ No newline at end of file diff --git a/01/js/main.js b/01/js/main.js new file mode 100644 index 0000000..838842c --- /dev/null +++ b/01/js/main.js @@ -0,0 +1,967 @@ +// =============================== +// KONFIGURASI API +// =============================== +const API = { + spbu: 'api/spbu.php', + jalan: 'api/jalan.php', + parsil: 'api/parsil.php' +}; + +// =============================== +// WARNA TEMA +// =============================== +const COLORS = { + spbu24: { fill: '#22c55e', stroke: '#15803d', glow: 'rgba(34,197,94,.35)' }, + spbuLtd: { fill: '#f87171', stroke: '#dc2626', glow: 'rgba(248,113,113,.35)' }, + jalanNasional: '#f97316', + jalanProvinsi: '#3b82f6', + jalanKabupaten: '#10b981', + SHM: { stroke: '#d97706', fill: '#fef3c7' }, + HGB: { stroke: '#8b5cf6', fill: '#ede9fe' }, + HGU: { stroke: '#0891b2', fill: '#cffafe' }, + HP: { stroke: '#ec4899', fill: '#fce7f3' }, +}; + +// =============================== +// STATE GLOBAL +// =============================== +let map; +let layers = { spbu: null, jalan: null, parsil: null }; + +let currentGeom = null; +let drawPoints = []; +let tempLayer = null; +let savedDrawPoints = []; + +let allSpbu = [], allJalan = [], allParsil = []; +let isDraggingMarker = false; + +let vertexMarkers = []; +let undoStack = []; +let redoStack = []; + +// Edit-in-place state +let editingFeature = null; // { type:'jalan'|'parsil', id, layer, geomLayer, vertexMarkers } + +// =============================== +// CUSTOM CONFIRM DIALOG +// =============================== +function showConfirm(message, title = 'Konfirmasi') { + return new Promise(resolve => { + const overlay = document.getElementById('confirm-overlay'); + document.getElementById('confirm-title').textContent = title; + document.getElementById('confirm-message').textContent = message; + overlay.classList.add('open'); + + const btnOk = document.getElementById('confirm-ok'); + const btnCancel = document.getElementById('confirm-cancel'); + + function cleanup() { + overlay.classList.remove('open'); + btnOk.removeEventListener('click', onOk); + btnCancel.removeEventListener('click', onCancel); + } + function onOk() { cleanup(); resolve(true); } + function onCancel() { cleanup(); resolve(false); } + + btnOk.addEventListener('click', onOk); + btnCancel.addEventListener('click', onCancel); + }); +} + +// =============================== +// TOAST NOTIFICATION +// =============================== +function showToast(msg, type = 'ok') { + const container = document.getElementById('toasts'); + const toast = document.createElement('div'); + toast.className = `toast ${type}`; + toast.innerHTML = (type === 'ok' ? '✅ ' : '❌ ') + msg; + container.appendChild(toast); + setTimeout(() => toast.remove(), 3200); +} + +// =============================== +// BUAT ICON SVG MARKER SPBU +// =============================== +function makeSpbuIcon(is24) { + const c = is24 ? COLORS.spbu24 : COLORS.spbuLtd; + const svg = ` + + + + + + + + + + + + `; + + return L.divIcon({ + html: svg, + className: '', + iconSize: [32, 40], + iconAnchor: [16, 38], + popupAnchor: [0, -38] + }); +} + +// =============================== +// INIT MAP +// =============================== +function initMap() { + map = L.map('map').setView([-0.0263, 109.3425], 14); + + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap', + maxZoom: 19 + }).addTo(map); + + map.on('click', onMapClick); + + map.on('mousemove', e => { + document.getElementById('cur-lat').innerText = e.latlng.lat.toFixed(5); + document.getElementById('cur-lng').innerText = e.latlng.lng.toFixed(5); + }); +} + +// =============================== +// LOAD DATA +// =============================== +async function loadAllData() { + try { + const [spbuRes, jalanRes, parsilRes] = await Promise.all([ + fetch(API.spbu + '?action=list').then(r => r.json()), + fetch(API.jalan + '?action=list').then(r => r.json()), + fetch(API.parsil + '?action=list').then(r => r.json()) + ]); + + allSpbu = spbuRes.data || []; + allJalan = jalanRes.data || []; + allParsil = parsilRes.data || []; + + renderSpbuLayer(); + renderJalanLayer(); + renderParsilLayer(); + renderSpbuList(); + + } catch (e) { + console.error(e); + } finally { + document.getElementById('loading').style.display = 'none'; + } +} + +// =============================== +// RENDER SPBU (POINT) — tanpa tooltip bubble +// =============================== +function renderSpbuLayer() { + if (layers.spbu) map.removeLayer(layers.spbu); + layers.spbu = L.layerGroup(); + + allSpbu.forEach(s => { + const is24 = s.statusbuka === 'full'; + const icon = makeSpbuIcon(is24); + + // ❌ Tidak ada .bindTooltip — bubble nama dihapus + const marker = L.marker([s.lat, s.lng], { icon, draggable: true, title: s.namaspbu }); + + marker.bindPopup(buildSpbuPopup(s)); + + marker.on('click', () => { + if (currentGeom) return; + marker.openPopup(); + }); + + marker.on('dragstart', () => { + isDraggingMarker = true; + marker.setOpacity(0.65); + }); + + marker.on('dragend', async () => { + isDraggingMarker = false; + marker.setOpacity(1); + const newPos = marker.getLatLng(); + await updateSpbuCoordinates(s.id, newPos.lat, newPos.lng); + s.lat = newPos.lat; + s.lng = newPos.lng; + marker.setPopupContent(buildSpbuPopup(s)); + showToast('Koordinat SPBU diperbarui'); + }); + + layers.spbu.addLayer(marker); + }); + + layers.spbu.addTo(map); +} + +function buildSpbuPopup(s) { + const nama = s.namaspbu || '—'; + const is24 = s.statusbuka === 'full'; + const status = is24 ? 'Buka 24 Jam' : 'Tidak 24 Jam'; + const wa = s.nomorwa || '—'; + const coord = (s.lat && s.lng) ? `${Number(s.lat).toFixed(6)}, ${Number(s.lng).toFixed(6)}` : '—'; + const badgeCls = is24 ? 'bg' : 'br'; + + return ` +
+
${nama}
+
+
+ 📍 Koordinat + ${coord} +
+
+ ⏰ Status + ${status} +
+ ${wa !== '—' ? `
📱 WhatsApp${wa}
` : ''} +
+
+ + +
+
`; +} + +// =============================== +// EDIT / DELETE SPBU +// =============================== +function editSpbu(id) { + const s = allSpbu.find(x => x.id == id); + if (!s) return showToast('Data SPBU tidak ditemukan', 'er'); + document.getElementById('e-spbu-id').value = s.id; + document.getElementById('e-spbu-nama').value = s.namaspbu || ''; + document.getElementById('e-spbu-wa').value = s.nomorwa || ''; + document.getElementById('e-spbu-status').value = s.statusbuka || 'limited'; + openModal('m-edit-spbu'); +} + +async function hapusSpbu(id) { + const ok = await showConfirm('Hapus SPBU ini? Tindakan tidak dapat dibatalkan.', 'Hapus SPBU'); + if (!ok) return; + try { + const res = await fetch(API.spbu, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({action:'delete',id}) }); + const j = await res.json(); + if (j.status==='success') { loadAllData(); showToast('SPBU berhasil dihapus'); } + else showToast(j.message||'Gagal hapus', 'er'); + } catch(e) { showToast('Gagal: '+e.message, 'er'); } +} + +async function updateSpbu() { + const id = parseInt(document.getElementById('e-spbu-id').value||0); + const namaspbu = document.getElementById('e-spbu-nama').value; + const nomorwa = document.getElementById('e-spbu-wa').value; + const statusbuka= document.getElementById('e-spbu-status').value; + try { + const res = await fetch(API.spbu, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({action:'update',id,namaspbu,nomorwa,statusbuka}) }); + const j = await res.json(); + if (j.status==='success') { closeModal('m-edit-spbu'); loadAllData(); showToast('SPBU diperbarui'); } + else showToast(j.message||'Gagal update', 'er'); + } catch(e) { showToast('Gagal: '+e.message, 'er'); } +} + +async function updateSpbuCoordinates(id, lat, lng) { + const spbu = allSpbu.find(x => x.id == id); + if (!spbu) return; + await fetch(API.spbu, { + method: 'POST', headers: {'Content-Type':'application/json'}, + body: JSON.stringify({ action:'update', id, namaspbu:spbu.namaspbu, nomorwa:spbu.nomorwa, statusbuka:spbu.statusbuka, koordinat:{type:'Point', coordinates:[lng,lat]} }) + }); +} + +// =============================== +// RENDER LIST & STATS +// =============================== +function renderSpbuList() { + const el = document.getElementById('spbu-list'); + if (!el) return; + + document.getElementById('cnt-total').innerText = allSpbu.length; + document.getElementById('cnt-24').innerText = allSpbu.filter(s=>s.statusbuka==='full').length; + document.getElementById('cnt-jalan').innerText = allJalan.length; + document.getElementById('cnt-parsil').innerText = allParsil.length; + + if (!allSpbu.length) { el.innerHTML = '
Belum ada SPBU tercatat.
'; return; } + + el.innerHTML = allSpbu.map(s => { + const lat = parseFloat(s.lat)||0, lng = parseFloat(s.lng)||0; + const dot = s.statusbuka==='full' ? 'dot-green' : 'dot-red'; + return ` +
+
+
${s.namaspbu||'—'}
+
+ + +
+
+
`; + }).join(''); +} + +function flyTo(lat, lng) { + if (map) map.setView([lat, lng], 16); +} + +// =============================== +// SEARCH +// =============================== +function setupSearch() { + const input = document.getElementById('search-input'); + const results = document.getElementById('search-results'); + if (!input || !results) return; + + input.addEventListener('input', () => { + const q = input.value.trim().toLowerCase(); + results.innerHTML = ''; + if (!q) { results.classList.remove('open'); return; } + + const hits = []; + + allSpbu.forEach(s => { + if ((s.namaspbu||'').toLowerCase().includes(q)) { + hits.push({ label: s.namaspbu, sub: 'SPBU · Point', icon: '⛽', action: () => { flyTo(s.lat, s.lng); input.value=''; results.classList.remove('open'); } }); + } + }); + allJalan.forEach(j => { + if ((j.nama||'').toLowerCase().includes(q)) { + hits.push({ label: j.nama, sub: `Jalan ${j.status||''}`, icon: '🛣️', action: () => { + try { const geom = JSON.parse(j.geom); const coords = geom.coordinates; const mid = coords[Math.floor(coords.length/2)]; map.setView([mid[1],mid[0]],16); } catch(e){} + input.value=''; results.classList.remove('open'); + }}); + } + }); + allParsil.forEach(p => { + if ((p.nama||'').toLowerCase().includes(q)) { + hits.push({ label: p.nama, sub: `Parsil · ${p.status_kepemilikan||'SHM'}`, icon: '🗺️', action: () => { + try { const geom = JSON.parse(p.geom); const coords = geom.coordinates[0]; const cx = coords.reduce((a,c)=>a+c[0],0)/coords.length; const cy = coords.reduce((a,c)=>a+c[1],0)/coords.length; map.setView([cy,cx],17); } catch(e){} + input.value=''; results.classList.remove('open'); + }}); + } + }); + + if (!hits.length) { + results.innerHTML = '
Tidak ada hasil
'; + } else { + results.innerHTML = hits.slice(0,8).map((h,i) => ` +
+ ${h.icon} +
+
${h.label}
+
${h.sub}
+
+
`).join(''); + results.querySelectorAll('.sr-item').forEach((el,i) => { + el.addEventListener('click', () => hits[i].action()); + }); + } + results.classList.add('open'); + }); + + document.addEventListener('click', e => { + if (!e.target.closest('#search-wrap')) results.classList.remove('open'); + }); +} + +// =============================== +// RENDER JALAN (LINE) +// =============================== +function renderJalanLayer() { + if (layers.jalan) map.removeLayer(layers.jalan); + + const geojson = allJalan.map(j => ({ + type: 'Feature', + geometry: JSON.parse(j.geom), + properties: j + })); + + layers.jalan = L.geoJSON(geojson, { + style: f => { + const s = f.properties.status; + let color = COLORS.jalanKabupaten; + if (s === 'nasional') color = COLORS.jalanNasional; + else if (s === 'provinsi') color = COLORS.jalanProvinsi; + return { color, weight: 4, opacity: 0.9 }; + }, + onEachFeature: (f, layer) => { + layer.bindPopup(buildJalanPopup(f.properties)); + layer.on('click', e => { + if (currentGeom) return; + L.DomEvent.stopPropagation(e); + layer.openPopup(); + }); + } + }).addTo(map); +} + +function buildJalanPopup(p) { + const nama = p.nama || '—'; + const status = p.status || 'kabupaten'; + let statusLabel='Kabupaten', badgeClass='bb-green'; + if (status==='nasional') { statusLabel='Nasional'; badgeClass='bb-orange'; } + else if (status==='provinsi') { statusLabel='Provinsi'; badgeClass='bb-blue'; } + const panjang = p.panjang_meter ? Number(p.panjang_meter).toFixed(2)+' m' : '—'; + + return ` +
+
${nama}
+
+
+ 🛣️ Status + ${statusLabel} +
+
+ 📏 Panjang + ${panjang} +
+
+
+ + + +
+
`; +} + +function editJalan(id) { + const j = allJalan.find(x=>x.id==id); + if (!j) return showToast('Data jalan tidak ditemukan', 'er'); + document.getElementById('e-jalan-id').value = j.id; + document.getElementById('e-j-nama').value = j.nama || ''; + document.getElementById('e-j-status').value = j.status || 'kabupaten'; + document.getElementById('e-j-panjang').value = j.panjang_meter || ''; + openModal('m-edit-jalan'); +} + +async function hapusJalan(id) { + const ok = await showConfirm('Hapus jalan ini? Tindakan tidak dapat dibatalkan.', 'Hapus Jalan'); + if (!ok) return; + try { + const res = await fetch(API.jalan, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({action:'delete',id}) }); + const j = await res.json(); + if (j.status==='success') { loadAllData(); showToast('Jalan berhasil dihapus'); } + else showToast(j.message||'Gagal hapus', 'er'); + } catch(e) { showToast('Gagal: '+e.message, 'er'); } +} + +async function updateJalan() { + const id = parseInt(document.getElementById('e-jalan-id').value||0); + const nama = document.getElementById('e-j-nama').value; + const status = document.getElementById('e-j-status').value; + const panjang_meter= parseFloat(document.getElementById('e-j-panjang').value); + try { + const res = await fetch(API.jalan, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({action:'update',id,nama,status,panjang_meter}) }); + const j = await res.json(); + if (j.status==='success') { closeModal('m-edit-jalan'); loadAllData(); showToast('Jalan diperbarui'); } + else showToast(j.message||'Gagal update', 'er'); + } catch(e) { showToast('Gagal: '+e.message, 'er'); } +} + +// =============================== +// EDIT GEOMETRI JALAN (drag vertex) +// =============================== +function startEditGeomJalan(id) { + map.closePopup(); + stopEditGeom(); // clear any existing + + const j = allJalan.find(x => x.id == id); + if (!j) return; + const geom = JSON.parse(j.geom); + let coords = geom.coordinates.map(c => [c[1], c[0]]); // [lat,lng] + + // Highlight line + const geomLayer = L.polyline(coords, { color: '#f97316', weight: 4, dashArray: '8,5', opacity: 1 }).addTo(map); + + const vms = []; + const updatePolyline = () => { + const pts = vms.map(vm => vm.getLatLng()); + geomLayer.setLatLngs(pts); + // Update panjang preview + const turfCoords = pts.map(p => [p.lng, p.lat]); + if (turfCoords.length >= 2) { + const panjang = turf.length(turf.lineString(turfCoords), { units: 'meters' }); + document.getElementById('geom-edit-info').textContent = `Panjang: ${panjang.toFixed(2)} m`; + } + }; + + coords.forEach((c, i) => { + const vm = L.marker(c, { + draggable: true, + icon: vertexIcon() + }).addTo(map); + vm.on('drag', updatePolyline); + vms.push(vm); + }); + + editingFeature = { type: 'jalan', id, geomLayer, vms }; + showEditGeomToolbar('Drag titik untuk edit geometri jalan'); +} + +// =============================== +// RENDER PARSIL (POLYGON) +// =============================== +function renderParsilLayer() { + if (layers.parsil) map.removeLayer(layers.parsil); + + const geojson = allParsil.map(p => ({ + type: 'Feature', + geometry: JSON.parse(p.geom), + properties: p + })); + + layers.parsil = L.geoJSON(geojson, { + style: f => { + const c = COLORS[f.properties.status_kepemilikan] || COLORS.SHM; + return { color: c.stroke, fillColor: c.fill, weight: 2.5, fillOpacity: 0.45 }; + }, + onEachFeature: (f, layer) => { + layer.bindPopup(buildParsilPopup(f.properties)); + layer.on('click', e => { + if (currentGeom) return; + L.DomEvent.stopPropagation(e); + layer.openPopup(); + }); + } + }).addTo(map); +} + +function buildParsilPopup(props) { + const nama = props.nama || '—'; + const status = props.status_kepemilikan || 'SHM'; + const luas = props.luas_m2 ? Number(props.luas_m2).toFixed(2)+' m²' : '—'; + const badgeMap = { SHM:'bp-shm', HGB:'bp-hgb', HGU:'bp-hgu', HP:'bp-hp' }; + const badgeClass = badgeMap[status] || 'bp-shm'; + + return ` +
+
${nama}
+
+
+ 📋 Kepemilikan + ${status} +
+
+ 📐 Luas + ${luas} +
+
+
+ + + +
+
`; +} + +function editParsil(id) { + const p = allParsil.find(x=>x.id==id); + if (!p) return showToast('Data parsil tidak ditemukan', 'er'); + document.getElementById('e-parsil-id').value = p.id; + document.getElementById('e-p-nama').value = p.nama || ''; + document.getElementById('e-p-status').value = p.status_kepemilikan || 'SHM'; + document.getElementById('e-p-luas').value = p.luas_m2 || ''; + openModal('m-edit-parsil'); +} + +async function hapusParsil(id) { + const ok = await showConfirm('Hapus parsil ini? Tindakan tidak dapat dibatalkan.', 'Hapus Parsil'); + if (!ok) return; + try { + const res = await fetch(API.parsil, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({action:'delete',id}) }); + const j = await res.json(); + if (j.status==='success') { loadAllData(); showToast('Parsil berhasil dihapus'); } + else showToast(j.message||'Gagal hapus', 'er'); + } catch(e) { showToast('Gagal: '+e.message, 'er'); } +} + +async function updateParsil() { + const id = parseInt(document.getElementById('e-parsil-id').value||0); + const nama = document.getElementById('e-p-nama').value; + const status_kepemilikan= document.getElementById('e-p-status').value; + const luas_m2 = parseFloat(document.getElementById('e-p-luas').value); + try { + const res = await fetch(API.parsil, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({action:'update',id,nama,status_kepemilikan,luas_m2}) }); + const j = await res.json(); + if (j.status==='success') { closeModal('m-edit-parsil'); loadAllData(); showToast('Parsil diperbarui'); } + else showToast(j.message||'Gagal update', 'er'); + } catch(e) { showToast('Gagal: '+e.message, 'er'); } +} + +// =============================== +// EDIT GEOMETRI PARSIL (drag vertex) +// =============================== +function startEditGeomParsil(id) { + map.closePopup(); + stopEditGeom(); + + const p = allParsil.find(x => x.id == id); + if (!p) return; + const geom = JSON.parse(p.geom); + // outer ring, drop closing duplicate + let coords = geom.coordinates[0].slice(0, -1).map(c => [c[1], c[0]]); + + const geomLayer = L.polygon(coords, { + color: '#8b5cf6', fillColor: '#ede9fe', weight: 2.5, + dashArray: '8,5', fillOpacity: 0.3 + }).addTo(map); + + const vms = []; + const updatePolygon = () => { + const pts = vms.map(vm => vm.getLatLng()); + geomLayer.setLatLngs(pts); + const turfCoords = [...pts.map(p => [p.lng, p.lat])]; + turfCoords.push(turfCoords[0]); + if (turfCoords.length >= 4) { + const luas = turf.area(turf.polygon([turfCoords])); + document.getElementById('geom-edit-info').textContent = `Luas: ${luas.toFixed(2)} m²`; + } + }; + + coords.forEach(c => { + const vm = L.marker(c, { draggable: true, icon: vertexIcon() }).addTo(map); + vm.on('drag', updatePolygon); + vms.push(vm); + }); + + editingFeature = { type: 'parsil', id, geomLayer, vms }; + showEditGeomToolbar('Drag titik untuk edit geometri parsil'); +} + +// =============================== +// VERTEX ICON +// =============================== +function vertexIcon() { + return L.divIcon({ + html: `
`, + className: '', + iconSize: [14, 14], + iconAnchor: [7, 7] + }); +} + +// =============================== +// GEOM EDIT TOOLBAR +// =============================== +function showEditGeomToolbar(hint) { + const tb = document.getElementById('geom-edit-toolbar'); + document.getElementById('geom-edit-hint').textContent = hint; + document.getElementById('geom-edit-info').textContent = ''; + tb.classList.add('visible'); +} + +function stopEditGeom() { + if (!editingFeature) return; + if (editingFeature.geomLayer) map.removeLayer(editingFeature.geomLayer); + editingFeature.vms.forEach(vm => { try { map.removeLayer(vm); } catch(e){} }); + editingFeature = null; + document.getElementById('geom-edit-toolbar').classList.remove('visible'); +} + +async function saveEditGeom() { + if (!editingFeature) return; + const { type, id, vms } = editingFeature; + const pts = vms.map(vm => vm.getLatLng()); + + try { + if (type === 'jalan') { + const coords = pts.map(p => [p.lng, p.lat]); + const panjang = turf.length(turf.lineString(coords), { units: 'meters' }); + const j = allJalan.find(x => x.id == id); + const res = await fetch(API.jalan, { + method: 'POST', headers: {'Content-Type':'application/json'}, + body: JSON.stringify({ + action: 'update', id, + nama: j.nama, status: j.status, + panjang_meter: panjang, + geom: { type: 'LineString', coordinates: coords } + }) + }); + const data = await res.json(); + if (data.status === 'success') { showToast('Geometri jalan diperbarui'); stopEditGeom(); loadAllData(); } + else showToast(data.message || 'Gagal simpan', 'er'); + + } else if (type === 'parsil') { + const coords = pts.map(p => [p.lng, p.lat]); + coords.push(coords[0]); + const luas = turf.area(turf.polygon([coords])); + const p = allParsil.find(x => x.id == id); + const res = await fetch(API.parsil, { + method: 'POST', headers: {'Content-Type':'application/json'}, + body: JSON.stringify({ + action: 'update', id, + nama: p.nama, status_kepemilikan: p.status_kepemilikan, + luas_m2: luas, + geom: { type: 'Polygon', coordinates: [coords] } + }) + }); + const data = await res.json(); + if (data.status === 'success') { showToast('Geometri parsil diperbarui'); stopEditGeom(); loadAllData(); } + else showToast(data.message || 'Gagal simpan', 'er'); + } + } catch(e) { showToast('Gagal: '+e.message, 'er'); } +} + +// =============================== +// HANDLE MAP CLICK (DRAW) +// =============================== +function onMapClick(e) { + if (isDraggingMarker) return; + if (!currentGeom) return; + + if (currentGeom === 'point') { + document.getElementById('spbu-lat').value = e.latlng.lat; + document.getElementById('spbu-lng').value = e.latlng.lng; + openModal('m-add-spbu'); + return; + } + + drawPoints.push({ lat: e.latlng.lat, lng: e.latlng.lng }); + undoStack.push({ lat: e.latlng.lat, lng: e.latlng.lng }); + redoStack = []; + drawTemp(); +} + +// =============================== +// GAMBAR SEMENTARA +// =============================== +function drawTemp() { + if (tempLayer) { map.removeLayer(tempLayer); tempLayer = null; } + vertexMarkers.forEach(m => { try { map.removeLayer(m); } catch(e){} }); + vertexMarkers = []; + + if (drawPoints.length === 0) { updateCounter(); return; } + + const latlngs = drawPoints.map(p => [p.lat, p.lng]); + + if (currentGeom === 'polyline' && drawPoints.length >= 2) { + tempLayer = L.polyline(latlngs, { color: COLORS.jalanProvinsi, weight: 3, dashArray:'7,5', opacity:.85 }).addTo(map); + } + if (currentGeom === 'polygon' && drawPoints.length >= 3) { + tempLayer = L.polygon(latlngs, { color: COLORS.SHM.stroke, weight: 2.5, dashArray:'7,5', fillColor: COLORS.SHM.fill, fillOpacity:.25 }).addTo(map); + } + + drawPoints.forEach(p => { + const vm = L.circleMarker([p.lat, p.lng], { + radius: 6, color:'#fff', weight: 2.5, + fillColor: currentGeom==='polygon' ? COLORS.SHM.stroke : COLORS.jalanProvinsi, + fillOpacity: 1 + }).addTo(map); + vertexMarkers.push(vm); + }); + + updateCounter(); +} + +// =============================== +// UNDO / REDO +// =============================== +function undoDraw() { + if (!drawPoints.length) return; + redoStack.push(drawPoints.pop()); + drawTemp(); +} + +function redoDraw() { + if (!redoStack.length) return; + drawPoints.push(redoStack.pop()); + drawTemp(); +} + +// =============================== +// SELESAI DRAW +// =============================== +function finishDrawing() { + if (currentGeom === 'polyline') { + if (drawPoints.length < 2) { showToast('Minimal 2 titik untuk membuat jalan!', 'er'); return; } + finishJalanDrawing(); + } else if (currentGeom === 'polygon') { + if (drawPoints.length < 3) { showToast('Minimal 3 titik untuk membuat parsil!', 'er'); return; } + finishParsilDrawing(); + } +} + +function finishJalanDrawing() { + savedDrawPoints = drawPoints.map(p => ({ lat: p.lat, lng: p.lng })); + const coords = savedDrawPoints.map(p => [p.lng, p.lat]); + const panjang = turf.length(turf.lineString(coords), { units:'meters' }); + document.getElementById('j-nama').value = ''; + document.getElementById('j-status').value = 'kabupaten'; + document.getElementById('j-panjang').value = panjang.toFixed(2); + openModal('m-add-jalan'); +} + +function finishParsilDrawing() { + savedDrawPoints = drawPoints.map(p => ({ lat: p.lat, lng: p.lng })); + const coords = savedDrawPoints.map(p => [p.lng, p.lat]); + coords.push(coords[0]); + const luas = turf.area(turf.polygon([coords])); + document.getElementById('p-nama').value = ''; + document.getElementById('p-status').value = 'SHM'; + document.getElementById('p-luas').value = luas.toFixed(2); + openModal('m-add-parsil'); +} + +// =============================== +// SAVE SPBU +// =============================== +async function saveSpbu() { + const lat = parseFloat(document.getElementById('spbu-lat').value); + const lng = parseFloat(document.getElementById('spbu-lng').value); + const namaspbu = document.getElementById('spbu-nama').value.trim(); + if (!namaspbu) { showToast('Nama SPBU wajib diisi!', 'er'); return; } + if (isNaN(lat)||isNaN(lng)) { showToast('Koordinat tidak valid!', 'er'); return; } + + try { + const res = await fetch(API.spbu, { method:'POST', headers:{'Content-Type':'application/json'}, + body: JSON.stringify({ action:'create', namaspbu, nomorwa:document.getElementById('spbu-wa').value, + statusbuka:document.getElementById('spbu-status').value, koordinat:{type:'Point',coordinates:[lng,lat]} }) }); + const data = await res.json(); + if (data.status!=='success') { showToast(data.message||'Gagal simpan', 'er'); return; } + closeModal('m-add-spbu'); deactivateGeom(); loadAllData(); showToast('SPBU berhasil ditambahkan'); + } catch(e) { showToast('Error: '+e.message, 'er'); } +} + +// =============================== +// SAVE JALAN +// =============================== +async function saveJalan() { + const nama = document.getElementById('j-nama').value.trim(); + if (!nama) { showToast('Nama jalan wajib diisi!', 'er'); return; } + if (savedDrawPoints.length < 2) { showToast('Data koordinat hilang, silakan gambar ulang.', 'er'); return; } + + try { + const res = await fetch(API.jalan, { method:'POST', headers:{'Content-Type':'application/json'}, + body: JSON.stringify({ action:'create', nama, status:document.getElementById('j-status').value, + panjang_meter:parseFloat(document.getElementById('j-panjang').value), + geom:{ type:'LineString', coordinates:savedDrawPoints.map(p=>[p.lng,p.lat]) } }) }); + const data = await res.json(); + if (data.status!=='success') { showToast(data.message||'Gagal simpan', 'er'); return; } + closeModal('m-add-jalan'); deactivateGeom(); cancelDraw(); loadAllData(); showToast('Jalan berhasil ditambahkan'); + } catch(e) { showToast('Error: '+e.message, 'er'); } +} + +// =============================== +// SAVE PARSIL +// =============================== +async function saveParsil() { + const nama = document.getElementById('p-nama').value.trim(); + if (!nama) { showToast('Nama parsil wajib diisi!', 'er'); return; } + if (savedDrawPoints.length < 3) { showToast('Data koordinat hilang, silakan gambar ulang.', 'er'); return; } + + const coords = savedDrawPoints.map(p => [p.lng, p.lat]); + coords.push(coords[0]); + + try { + const res = await fetch(API.parsil, { method:'POST', headers:{'Content-Type':'application/json'}, + body: JSON.stringify({ action:'create', nama, status_kepemilikan:document.getElementById('p-status').value, + luas_m2:parseFloat(document.getElementById('p-luas').value), + geom:{ type:'Polygon', coordinates:[coords] } }) }); + const data = await res.json(); + if (data.status!=='success') { showToast(data.message||'Gagal simpan', 'er'); return; } + closeModal('m-add-parsil'); deactivateGeom(); cancelDraw(); loadAllData(); showToast('Parsil berhasil ditambahkan'); + } catch(e) { showToast('Error: '+e.message, 'er'); } +} + +// =============================== +// CONTROL DRAW +// =============================== +function cancelDraw() { + drawPoints = []; undoStack = []; redoStack = []; savedDrawPoints = []; + if (tempLayer) { try { map.removeLayer(tempLayer); } catch(e){} tempLayer = null; } + vertexMarkers.forEach(m => { try { map.removeLayer(m); } catch(e){} }); + vertexMarkers = []; + updateCounter(); + document.getElementById('draw-toolbar').classList.remove('visible'); +} + +function deactivateGeom() { + currentGeom = null; + document.querySelectorAll('.geom-btn').forEach(b => b.classList.remove('active')); + document.getElementById('geom-hint').classList.remove('on'); + document.getElementById('map').classList.remove('crosshair-cursor'); + document.getElementById('draw-toolbar').classList.remove('visible'); +} + +// =============================== +// MODAL +// =============================== +function openModal(id) { document.getElementById(id).classList.add('open'); } +function closeModal(id) { document.getElementById(id).classList.remove('open'); } + +// =============================== +// COUNTER +// =============================== +function updateCounter() { + const n = drawPoints.length; + document.getElementById('dt-counter').innerText = n + ' titik'; + document.getElementById('btn-undo').disabled = n === 0; + document.getElementById('btn-redo').disabled = redoStack.length === 0; +} + +// =============================== +// INIT +// =============================== +document.addEventListener('DOMContentLoaded', () => { + initMap(); + loadAllData(); + setupSearch(); + + document.getElementById('btn-finish').onclick = finishDrawing; + document.getElementById('btn-undo').onclick = undoDraw; + document.getElementById('btn-redo').onclick = redoDraw; + document.getElementById('btn-cancel-draw').onclick = () => { cancelDraw(); deactivateGeom(); }; + + // Geom edit toolbar buttons + document.getElementById('btn-geom-save').onclick = saveEditGeom; + document.getElementById('btn-geom-cancel').onclick = stopEditGeom; + + document.querySelectorAll('.geom-btn').forEach(btn => { + btn.addEventListener('click', () => { + const geom = btn.dataset.geom; + if (currentGeom === geom) { cancelDraw(); deactivateGeom(); return; } + cancelDraw(); + currentGeom = geom; + document.querySelectorAll('.geom-btn').forEach(b => b.classList.remove('active')); + btn.classList.add('active'); + document.getElementById('map').classList.add('crosshair-cursor'); + const toolbar = document.getElementById('draw-toolbar'); + toolbar.classList.toggle('visible', geom==='polyline'||geom==='polygon'); + const hint = document.getElementById('geom-hint'); + const hintText = document.getElementById('geom-hint-text'); + hint.classList.add('on'); + if (geom==='point') hintText.innerText = 'Klik peta untuk tambah SPBU'; + if (geom==='polyline') hintText.innerText = 'Klik peta untuk menambah titik jalan, klik Selesai jika sudah'; + if (geom==='polygon') hintText.innerText = 'Klik peta untuk menambah titik parsil, klik Selesai jika sudah'; + }); + }); + + // Layer toggle + const ldbtn = document.getElementById('ldbtn'); + const layerMenu = document.getElementById('layer-menu'); + if (ldbtn && layerMenu) { + ldbtn.addEventListener('click', e => { e.stopPropagation(); layerMenu.classList.toggle('open'); }); + document.addEventListener('click', () => layerMenu.classList.remove('open')); + + document.getElementById('lm-point').addEventListener('click', function() { + this.classList.toggle('active'); + if (this.classList.contains('active')) { if (layers.spbu) layers.spbu.addTo(map); } + else { if (layers.spbu) map.removeLayer(layers.spbu); } + }); + document.getElementById('lm-jalan').addEventListener('click', function() { + this.classList.toggle('active'); + if (this.classList.contains('active')) { if (layers.jalan) layers.jalan.addTo(map); } + else { if (layers.jalan) map.removeLayer(layers.jalan); } + }); + document.getElementById('lm-parsil').addEventListener('click', function() { + this.classList.toggle('active'); + if (this.classList.contains('active')) { if (layers.parsil) layers.parsil.addTo(map); } + else { if (layers.parsil) map.removeLayer(layers.parsil); } + }); + } +}); \ No newline at end of file diff --git a/02/index.html b/02/index.html new file mode 100644 index 0000000..586950d --- /dev/null +++ b/02/index.html @@ -0,0 +1,169 @@ + + + + + + Kepadatan Penduduk Kota Pontianak 2025 + + + + + + + + +
+ + + + + + \ No newline at end of file diff --git a/02/kec-ptk.js b/02/kec-ptk.js new file mode 100644 index 0000000..3d711b2 --- /dev/null +++ b/02/kec-ptk.js @@ -0,0 +1,19407 @@ +var statesData = { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "Id": 0, + "Plan_Area": 113026328.1, + "Ket": "Pontianak Timur", + "Penduduk": 112440 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 109.37369333219321, + -0.06214301526608477 + ], + [ + 109.37360010467371, + -0.062052382843561286 + ], + [ + 109.37360012580149, + -0.062052366924601886 + ], + [ + 109.37359628017359, + -0.062048589620468685 + ], + [ + 109.3735808322504, + -0.06203285901641851 + ], + [ + 109.37352425412847, + -0.061975187148331924 + ], + [ + 109.37342723850422, + -0.06187479652834112 + ], + [ + 109.37327183225035, + -0.06171834339255479 + ], + [ + 109.37312183225464, + -0.06155645276982974 + ], + [ + 109.37304119272487, + -0.0614743956187946 + ], + [ + 109.37299001550814, + -0.06150169629170068 + ], + [ + 109.37291204359632, + -0.061507732142261094 + ], + [ + 109.37282207731775, + -0.06147149991798636 + ], + [ + 109.3728400746274, + -0.061344697351030535 + ], + [ + 109.37284686184168, + -0.061288339832678805 + ], + [ + 109.37285207328483, + -0.061245066695616766 + ], + [ + 109.37282508707493, + -0.061112224508703544 + ], + [ + 109.37269613983209, + -0.06091295854505543 + ], + [ + 109.37264515886812, + -0.0608948422653263 + ], + [ + 109.37255819340335, + -0.06079520861337267 + ], + [ + 109.37257918714315, + -0.060749922428212824 + ], + [ + 109.37256719375219, + -0.060674444027610595 + ], + [ + 109.372531208702, + -0.06061104138778611 + ], + [ + 109.37247422946555, + -0.06060802054229428 + ], + [ + 109.3724442406173, + -0.06059896227099334 + ], + [ + 109.37244124278654, + -0.0605627327248765 + ], + [ + 109.37245024096276, + -0.060514427056179876 + ], + [ + 109.37244724313041, + -0.0604781975100634 + ], + [ + 109.37236927492872, + -0.06036044942995719 + ], + [ + 109.37233028862791, + -0.06037554386060613 + ], + [ + 109.37230629787345, + -0.06035742841087951 + ], + [ + 109.37227031210101, + -0.06031817875304858 + ], + [ + 109.37222832751903, + -0.06031213924662926 + ], + [ + 109.37221033342193, + -0.06033327255128441 + ], + [ + 109.37217134802172, + -0.06031817577239131 + ], + [ + 109.37217134936708, + -0.06027288895925287 + ], + [ + 109.37218634482056, + -0.06024269820155473 + ], + [ + 109.37218934525468, + -0.06019137323643375 + ], + [ + 109.37212936819043, + -0.06015212286311852 + ], + [ + 109.37210237870421, + -0.06012796908625125 + ], + [ + 109.37202441094313, + -0.05999512543625395 + ], + [ + 109.37180249821006, + -0.05976868476291624 + ], + [ + 109.37176951276804, + -0.05968112929032672 + ], + [ + 109.37173652581497, + -0.05964489886432003 + ], + [ + 109.3713136954399, + -0.059098425621009 + ], + [ + 109.37122047282149, + -0.059133877236944944 + ], + [ + 109.37081422518189, + -0.05858790024060032 + ], + [ + 109.37055832678035, + -0.05829269005475569 + ], + [ + 109.37045170230286, + -0.05817460587720062 + ], + [ + 109.37044104197845, + -0.05808872842528063 + ], + [ + 109.37028643634055, + -0.057922336986166716 + ], + [ + 109.37010931799742, + -0.057764171905334304 + ], + [ + 109.36995055908098, + -0.05790622529912179 + ], + [ + 109.36990791055074, + -0.05781497962526583 + ], + [ + 109.36999854773069, + -0.05769153388128367 + ], + [ + 109.36992391160109, + -0.05757345068864537 + ], + [ + 109.36995993923689, + -0.057531137640174104 + ], + [ + 109.37001987985447, + -0.057460739726266176 + ], + [ + 109.37000055471326, + -0.05741998108339436 + ], + [ + 109.36992194891603, + -0.05747620555540714 + ], + [ + 109.36977263309227, + -0.05758300687133053 + ], + [ + 109.36970366198712, + -0.05745016373023001 + ], + [ + 109.36980262679452, + -0.05741997540042863 + ], + [ + 109.36961070668845, + -0.05706673319822197 + ], + [ + 109.36953873308221, + -0.057060692910162386 + ], + [ + 109.36950574586733, + -0.057033519916964426 + ], + [ + 109.36949675050418, + -0.05698521379086362 + ], + [ + 109.36950574782038, + -0.0569640802292875 + ], + [ + 109.36955168869811, + -0.056926641535874 + ], + [ + 109.36956872631536, + -0.05691275703537182 + ], + [ + 109.36957772354585, + -0.056894642589602744 + ], + [ + 109.36951774861797, + -0.056779914441675475 + ], + [ + 109.36952674686157, + -0.056725570593157 + ], + [ + 109.36949975821689, + -0.05667122572411416 + ], + [ + 109.36954174384707, + -0.056638016628158976 + ], + [ + 109.36951475511533, + -0.05658669087675728 + ], + [ + 109.36937081932454, + -0.056666383742614006 + ], + [ + 109.3693537675611, + -0.0566668338496136 + ], + [ + 109.3693149695348, + -0.05665494497836577 + ], + [ + 109.36929135352254, + -0.0566413582846394 + ], + [ + 109.36925592926745, + -0.05662946950978801 + ], + [ + 109.3692188184123, + -0.05660739116960266 + ], + [ + 109.36919857621834, + -0.05659210631962632 + ], + [ + 109.36918845547738, + -0.05657172699756727 + ], + [ + 109.36918845633069, + -0.056541158444264154 + ], + [ + 109.36919857844651, + -0.05651228843001363 + ], + [ + 109.36921038739982, + -0.056485116716152514 + ], + [ + 109.36923500262738, + -0.05646645796749743 + ], + [ + 109.36920027001415, + -0.0563441614333832 + ], + [ + 109.36908894112051, + -0.05614546270836668 + ], + [ + 109.36906989566741, + -0.05615446200268974 + ], + [ + 109.36900628267685, + -0.05618452020112867 + ], + [ + 109.3689742337474, + -0.05611658918888552 + ], + [ + 109.36896917483244, + -0.05605375369410974 + ], + [ + 109.36894724805289, + -0.05595525441911226 + ], + [ + 109.36887302840225, + -0.055836374652402786 + ], + [ + 109.36882748302915, + -0.05581769260072175 + ], + [ + 109.36879037203369, + -0.05580070903796512 + ], + [ + 109.36876338272084, + -0.055770139737505976 + ], + [ + 109.36873807977824, + -0.055758251263308155 + ], + [ + 109.36871615023132, + -0.05575994890399208 + ], + [ + 109.36869084654109, + -0.05577523247082915 + ], + [ + 109.36866385620078, + -0.055782024727581425 + ], + [ + 109.3686183105047, + -0.05577523044626072 + ], + [ + 109.36860144240745, + -0.05574805793598829 + ], + [ + 109.36861493823145, + -0.0557208862728491 + ], + [ + 109.36863349536233, + -0.05566993921552638 + ], + [ + 109.36865036579431, + -0.05561219910027543 + ], + [ + 109.36866048770771, + -0.055590122099236236 + ], + [ + 109.36869928782355, + -0.05552558958169918 + ], + [ + 109.36872098814291, + -0.05551575935551045 + ], + [ + 109.36873302593484, + -0.05551030624593456 + ], + [ + 109.36873977398525, + -0.05549162565493036 + ], + [ + 109.36872121904507, + -0.055462754846077184 + ], + [ + 109.36866916625594, + -0.05542413945802736 + ], + [ + 109.36862506857615, + -0.05539142557305005 + ], + [ + 109.36860145251792, + -0.05537953715115969 + ], + [ + 109.36858121069311, + -0.05535066629854063 + ], + [ + 109.36856096928543, + -0.05530651117463994 + ], + [ + 109.36854072764542, + -0.055270847313661955 + ], + [ + 109.36853229424247, + -0.05523348552740442 + ], + [ + 109.36853229479816, + -0.05521310649856735 + ], + [ + 109.36851205260153, + -0.05519782166771784 + ], + [ + 109.36844457717883, + -0.055199518056012746 + ], + [ + 109.36841421298261, + -0.05520970673099278 + ], + [ + 109.36838553608763, + -0.05520461118173469 + ], + [ + 109.36836866756839, + -0.055192722949778866 + ], + [ + 109.36836023393361, + -0.0551638524283624 + ], + [ + 109.36836023578316, + -0.055095922337957265 + ], + [ + 109.36831469193919, + -0.05502119798422999 + ], + [ + 109.36816962464356, + -0.05484627402273957 + ], + [ + 109.36813925990103, + -0.05487684172639366 + ], + [ + 109.36804442208751, + -0.0547823974408274 + ], + [ + 109.36808640773714, + -0.05474616921564198 + ], + [ + 109.36806841740609, + -0.054631442364380334 + ], + [ + 109.36805342524204, + -0.05454388762899108 + ], + [ + 109.36802043850753, + -0.0544986000090001 + ], + [ + 109.36797845489573, + -0.05445935037434643 + ], + [ + 109.36794846674086, + -0.05442613929709412 + ], + [ + 109.36794246982377, + -0.05439292887422674 + ], + [ + 109.36795746523964, + -0.054359719022806514 + ], + [ + 109.3679814575316, + -0.05432047118649406 + ], + [ + 109.36799945201338, + -0.05428122318622473 + ], + [ + 109.36798445846019, + -0.05424499340399582 + ], + [ + 109.3679304797912, + -0.054184609648456436 + ], + [ + 109.3677715439754, + -0.05395515265432212 + ], + [ + 109.36770856875142, + -0.053888730441802674 + ], + [ + 109.3676725814168, + -0.05390684415531851 + ], + [ + 109.36762160277497, + -0.053804192902714716 + ], + [ + 109.36762460200009, + -0.05379211652744324 + ], + [ + 109.36755862963372, + -0.05365927373785029 + ], + [ + 109.36760523309515, + -0.05362679422657877 + ], + [ + 109.36763660258302, + -0.05360493178029104 + ], + [ + 109.36757062948625, + -0.05349926102155139 + ], + [ + 109.36752904207765, + -0.053526174376274216 + ], + [ + 109.3674446733587, + -0.053580773718233644 + ], + [ + 109.36738769634059, + -0.05349925612048683 + ], + [ + 109.36736670369419, + -0.053511332012543586 + ], + [ + 109.36728273893102, + -0.05333924029257312 + ], + [ + 109.36733876434815, + -0.05330019429189123 + ], + [ + 109.36740647339165, + -0.0532666870277344 + ], + [ + 109.36738398322056, + -0.0532055493762276 + ], + [ + 109.36734574660125, + -0.05322819170935905 + ], + [ + 109.36731200887392, + -0.053230455145119294 + ], + [ + 109.36729326602018, + -0.053241340534591855 + ], + [ + 109.36716431055083, + -0.053318477230226785 + ], + [ + 109.36709852443056, + -0.0532301664114464 + ], + [ + 109.36710527464129, + -0.05312827151970124 + ], + [ + 109.36700912657624, + -0.052965236854100636 + ], + [ + 109.36704013167459, + -0.052929950947984174 + ], + [ + 109.36699919162479, + -0.05287981214257249 + ], + [ + 109.36695008096146, + -0.05280698717442773 + ], + [ + 109.36696339787731, + -0.052790127085587404 + ], + [ + 109.36696508538051, + -0.05276635161514323 + ], + [ + 109.36694484353657, + -0.052737480812089566 + ], + [ + 109.36692122717432, + -0.0527374801881709 + ], + [ + 109.36690971857205, + -0.05274713480576302 + ], + [ + 109.36687061957694, + -0.05277993512599965 + ], + [ + 109.36683688227197, + -0.05276634822623553 + ], + [ + 109.36680820592628, + -0.05274087370404038 + ], + [ + 109.3667980852927, + -0.052715399672623536 + ], + [ + 109.36679808595638, + -0.052689925908604954 + ], + [ + 109.3668098948885, + -0.05266105595431173 + ], + [ + 109.36681158261109, + -0.05262878923086515 + ], + [ + 109.36670530974027, + -0.0525999161642008 + ], + [ + 109.36667663357102, + -0.05256764864267952 + ], + [ + 109.36667494761457, + -0.05253198533087677 + ], + [ + 109.36667157499518, + -0.052487830720567924 + ], + [ + 109.3666783233193, + -0.052457262383101694 + ], + [ + 109.3666850713351, + -0.05243858180124602 + ], + [ + 109.36668844567286, + -0.05241650462891759 + ], + [ + 109.36667832490394, + -0.05239612535319078 + ], + [ + 109.36665133517496, + -0.052380840387364465 + ], + [ + 109.36656193025722, + -0.05238593279308219 + ], + [ + 109.36651301105505, + -0.052370647252767355 + ], + [ + 109.36645059837457, + -0.05229422433619855 + ], + [ + 109.36642529675922, + -0.05223138840033235 + ], + [ + 109.36641517695402, + -0.052173647614773166 + ], + [ + 109.36642867324086, + -0.05212609695027678 + ], + [ + 109.3664539772236, + -0.05209722735032393 + ], + [ + 109.36647253367573, + -0.052068357573561624 + ], + [ + 109.36647770051344, + -0.05199764007774057 + ], + [ + 109.3664809710585, + -0.05195287674852977 + ], + [ + 109.36647591110837, + -0.0519257046062432 + ], + [ + 109.36645398272395, + -0.051883247769634266 + ], + [ + 109.36643880117245, + -0.05186796311928951 + ], + [ + 109.36641419074617, + -0.05189233196996424 + ], + [ + 109.3663358987212, + -0.05196985547709992 + ], + [ + 109.3663038490008, + -0.05192909663006605 + ], + [ + 109.36627179810299, + -0.0519341905474206 + ], + [ + 109.3662330001558, + -0.05192060353398623 + ], + [ + 109.366217818868, + -0.051895129382352724 + ], + [ + 109.36620769840131, + -0.05186286236088518 + ], + [ + 109.36620938593687, + -0.0518373886482706 + ], + [ + 109.36621613398863, + -0.05181700981833038 + ], + [ + 109.36622288186635, + -0.0518034239900091 + ], + [ + 109.3662768630174, + -0.051767762131151356 + ], + [ + 109.36623637996738, + -0.05168454680850228 + ], + [ + 109.36620433011292, + -0.0516488827193091 + ], + [ + 109.3661756536762, + -0.05162680472172792 + ], + [ + 109.36616890692687, + -0.051596236039986816 + ], + [ + 109.36616384697291, + -0.05156906390282594 + ], + [ + 109.36614191849819, + -0.05153000357738719 + ], + [ + 109.36611830262162, + -0.051511322213572824 + ], + [ + 109.36609637354086, + -0.05149603739437467 + ], + [ + 109.36609974782439, + -0.05147565847738446 + ], + [ + 109.36613676542828, + -0.05143735817535653 + ], + [ + 109.36613236349524, + -0.051430109006740526 + ], + [ + 109.36608121801342, + -0.0513549961945744 + ], + [ + 109.36596142386928, + -0.051460370656474075 + ], + [ + 109.36592094077044, + -0.05137885360319964 + ], + [ + 109.36603711100153, + -0.05129022016092634 + ], + [ + 109.36601289473694, + -0.05125465589515443 + ], + [ + 109.3659455197492, + -0.05115426526362563 + ], + [ + 109.36591858224826, + -0.05111448400976074 + ], + [ + 109.36591139473565, + -0.05110634339035007 + ], + [ + 109.36590061350127, + -0.05108734338915829 + ], + [ + 109.36587816036804, + -0.051048452765093136 + ], + [ + 109.36583720092713, + -0.050974873846257256 + ], + [ + 109.36574889116916, + -0.050889753159923445 + ], + [ + 109.36571178074865, + -0.050850692462930586 + ], + [ + 109.36566792399516, + -0.050764080596258 + ], + [ + 109.36564599546827, + -0.05072671853917168 + ], + [ + 109.36561900591197, + -0.050704640603666695 + ], + [ + 109.36554646897017, + -0.05074539675654706 + ], + [ + 109.36551947937285, + -0.05072501707166522 + ], + [ + 109.36550261127941, + -0.05069614639554404 + ], + [ + 109.36550092495118, + -0.05067406910487674 + ], + [ + 109.36550767307406, + -0.050650293778593904 + ], + [ + 109.36540345936187, + -0.05072633498178441 + ], + [ + 109.36531237086038, + -0.05060236043893691 + ], + [ + 109.36543382854853, + -0.050514054532504855 + ], + [ + 109.36532755873333, + -0.050364605868659915 + ], + [ + 109.36528538813147, + -0.050306864315608546 + ], + [ + 109.36528707585802, + -0.050272899365225554 + ], + [ + 109.36528538948316, + -0.05025252032689373 + ], + [ + 109.3652634605717, + -0.05023044252913235 + ], + [ + 109.36521960093397, + -0.050259311667404986 + ], + [ + 109.36517236761837, + -0.050284784220202945 + ], + [ + 109.36514706439615, + -0.05028478358211631 + ], + [ + 109.3651268210581, + -0.050315351562850524 + ], + [ + 109.3650930831758, + -0.050325540208411776 + ], + [ + 109.36506440631887, + -0.05032044473622131 + ], + [ + 109.36505765934272, + -0.05029836732308322 + ], + [ + 109.36507115511233, + -0.050269497422618534 + ], + [ + 109.36506946882214, + -0.05024572188759108 + ], + [ + 109.36505091384467, + -0.050216851179416065 + ], + [ + 109.36504585379112, + -0.0501930755597447 + ], + [ + 109.36503573313557, + -0.05016760156348921 + ], + [ + 109.36496656965095, + -0.05022194380327501 + ], + [ + 109.36487885443925, + -0.0501166501370713 + ], + [ + 109.36499525206592, + -0.05000287035307163 + ], + [ + 109.36498681837365, + -0.049973999901892774 + ], + [ + 109.36495982936421, + -0.0499298447417894 + ], + [ + 109.36492609261954, + -0.04989418066072975 + ], + [ + 109.36491428440883, + -0.049895878614577234 + ], + [ + 109.36486536376238, + -0.049940031871797595 + ], + [ + 109.36474517536185, + -0.04986381992877 + ], + [ + 109.36463510963743, + -0.049731353746288 + ], + [ + 109.36474265055996, + -0.04963965097312014 + ], + [ + 109.36469584095487, + -0.04958488127256761 + ], + [ + 109.36463764237072, + -0.04963327992419144 + ], + [ + 109.36460980880281, + -0.04963455291797378 + ], + [ + 109.36456046925441, + -0.049564498916892234 + ], + [ + 109.36460095533455, + -0.0495262893195945 + ], + [ + 109.36460095574043, + -0.04950973139124559 + ], + [ + 109.36457438802054, + -0.049482983308916914 + ], + [ + 109.36462248799765, + -0.04944237155684561 + ], + [ + 109.36458283224214, + -0.04938892150318567 + ], + [ + 109.36456812405626, + -0.049363661088185504 + ], + [ + 109.36454529231486, + -0.04936070865742849 + ], + [ + 109.36451998925678, + -0.0493543395970772 + ], + [ + 109.36449848183646, + -0.04934160219719774 + ], + [ + 109.36446558806374, + -0.049325043455664616 + ], + [ + 109.36443775508943, + -0.04930211640638561 + ], + [ + 109.36441118690301, + -0.04929447362912222 + ], + [ + 109.36439094424016, + -0.049298294188470204 + ], + [ + 109.36436437577456, + -0.049302114591254324 + ], + [ + 109.36434792868707, + -0.04930211418440339 + ], + [ + 109.36433274679138, + -0.04930084012221121 + ], + [ + 109.36431756517551, + -0.04928810288048515 + ], + [ + 109.36430491440893, + -0.049253713029171836 + ], + [ + 109.36429732478042, + -0.04919894431759307 + ], + [ + 109.3643061822374, + -0.04914417601199173 + ], + [ + 109.36429479653614, + -0.049113607252772244 + ], + [ + 109.36426443320998, + -0.04909195383253283 + ], + [ + 109.36424081720556, + -0.04907857954187717 + ], + [ + 109.36421832496826, + -0.049098957972834284 + ], + [ + 109.36420258014341, + -0.0491238652329105 + ], + [ + 109.36418458630936, + -0.04914197944226545 + ], + [ + 109.36414634990828, + -0.049160093151947054 + ], + [ + 109.36412385794681, + -0.04916914992327563 + ], + [ + 109.36409686780283, + -0.04917141358882463 + ], + [ + 109.36406762876076, + -0.04916235554140821 + ], + [ + 109.3640316434641, + -0.04910121770472563 + ], + [ + 109.3640181496857, + -0.04904913774946706 + ], + [ + 109.36401140265873, + -0.049028758600619914 + ], + [ + 109.36398441323085, + -0.04900158595990615 + ], + [ + 109.36395742418742, + -0.0489585630004327 + ], + [ + 109.36393943222281, + -0.04889968994365108 + ], + [ + 109.36403389999211, + -0.048797797347018015 + ], + [ + 109.36398441964162, + -0.04873665918787473 + ], + [ + 109.36392819127778, + -0.04869589984875183 + ], + [ + 109.36397992391379, + -0.048627971171598144 + ], + [ + 109.36397767577532, + -0.04858494882051545 + ], + [ + 109.3639191987247, + -0.04852381044922508 + ], + [ + 109.36384272540026, + -0.04858268119870154 + ], + [ + 109.36370103007113, + -0.04847398985239627 + ], + [ + 109.36368078745458, + -0.04847625369091305 + ], + [ + 109.36363355627739, + -0.048415115604951466 + ], + [ + 109.36365347184473, + -0.0483729665106313 + ], + [ + 109.36365853361364, + -0.04832584024427618 + ], + [ + 109.36365600405531, + -0.048293998028058276 + ], + [ + 109.36363955715672, + -0.04828635551222406 + ], + [ + 109.36362437517452, + -0.048288902516606835 + ], + [ + 109.36356111604334, + -0.04833602737020205 + ], + [ + 109.36351304108975, + -0.048288899817839855 + ], + [ + 109.36342321271792, + -0.04837296091927028 + ], + [ + 109.36337007718981, + -0.04832328587441462 + ], + [ + 109.36347888374799, + -0.048206109394497265 + ], + [ + 109.3634055037395, + -0.04823667608345396 + ], + [ + 109.3634029730542, + -0.048251960254606464 + ], + [ + 109.36332832840557, + -0.048260874248243386 + ], + [ + 109.3632903734262, + -0.04826851544438784 + ], + [ + 109.36323470520388, + -0.0483181878464101 + ], + [ + 109.36309282370799, + -0.04815781792289147 + ], + [ + 109.36314005731116, + -0.04811706111735236 + ], + [ + 109.36324127187585, + -0.0480423406547835 + ], + [ + 109.36329019130952, + -0.04804573833065513 + ], + [ + 109.3633003132351, + -0.04801856660745773 + ], + [ + 109.36330031380048, + -0.047994791136099994 + ], + [ + 109.36332899140375, + -0.04796761985942407 + ], + [ + 109.36329188166003, + -0.04789968904771145 + ], + [ + 109.36324633554256, + -0.047914972183596125 + ], + [ + 109.36315187035714, + -0.047911573415267895 + ], + [ + 109.36311307204352, + -0.047914968977506585 + ], + [ + 109.36309451628648, + -0.047918365026620925 + ], + [ + 109.36305909209608, + -0.047906476439957 + ], + [ + 109.36303210286827, + -0.04787081258844088 + ], + [ + 109.3630270428335, + -0.04784533875095115 + ], + [ + 109.36303716443312, + -0.04783175301223299 + ], + [ + 109.36305572051131, + -0.04781477098046217 + ], + [ + 109.36303041820193, + -0.047777408922955254 + ], + [ + 109.36303379268459, + -0.04774684054478658 + ], + [ + 109.3630860859529, + -0.047746841798549544 + ], + [ + 109.36315693321113, + -0.0478181699061429 + ], + [ + 109.36318898481053, + -0.047780809222737176 + ], + [ + 109.3631872984533, + -0.047758731960087515 + ], + [ + 109.36313246969034, + -0.047702276503706675 + ], + [ + 109.3630845580381, + -0.04771270481056713 + ], + [ + 109.36287354437866, + -0.04752436626772101 + ], + [ + 109.3628516144256, + -0.047546442962779496 + ], + [ + 109.36282799815359, + -0.04754474415135757 + ], + [ + 109.36281033926299, + -0.047559558350458685 + ], + [ + 109.36269979267662, + -0.047656825421386294 + ], + [ + 109.36265256133896, + -0.047602480373906506 + ], + [ + 109.36265593617856, + -0.04755662777459547 + ], + [ + 109.36268461404676, + -0.04751756876862211 + ], + [ + 109.36270823099683, + -0.04749039737299007 + ], + [ + 109.36268967596365, + -0.047463224971481734 + ], + [ + 109.36261376459707, + -0.047539644293514156 + ], + [ + 109.36254629082798, + -0.047480204026382304 + ], + [ + 109.36259689876168, + -0.047413973586376314 + ], + [ + 109.36258171740722, + -0.04739019776198154 + ], + [ + 109.3626059612719, + -0.047360244858326066 + ], + [ + 109.36256439473327, + -0.04731879651546449 + ], + [ + 109.36249073848812, + -0.0472410152646084 + ], + [ + 109.36243965942221, + -0.04718890560470654 + ], + [ + 109.36240628567953, + -0.04723225659672413 + ], + [ + 109.36237592165773, + -0.04724074711217871 + ], + [ + 109.36235230606685, + -0.04721017810302461 + ], + [ + 109.3623421855872, + -0.0471762129202175 + ], + [ + 109.36233712578392, + -0.04714054961044665 + ], + [ + 109.36229664060882, + -0.04714394514589107 + ], + [ + 109.3622308539165, + -0.04707601370595023 + ], + [ + 109.36226629972205, + -0.04702983577270454 + ], + [ + 109.36217364473383, + -0.046953421502546694 + ], + [ + 109.36213431949564, + -0.04691216671608626 + ], + [ + 109.362247916885, + -0.04678589690344832 + ], + [ + 109.36226590976993, + -0.04680703106771261 + ], + [ + 109.3623228905423, + -0.04673155475766483 + ], + [ + 109.36226891181346, + -0.04667117137208255 + ], + [ + 109.36224192140136, + -0.04668626626861812 + ], + [ + 109.362103975198, + -0.046556441489328686 + ], + [ + 109.36205299383353, + -0.04656247850797079 + ], + [ + 109.36199001807296, + -0.046517190452197654 + ], + [ + 109.3618471383181, + -0.04662854715158911 + ], + [ + 109.36175773848211, + -0.046544640263357695 + ], + [ + 109.36165262911722, + -0.04640807776092082 + ], + [ + 109.36154320934799, + -0.04627955470827421 + ], + [ + 109.36158817264834, + -0.04623338519944871 + ], + [ + 109.36154319054064, + -0.04617602116273778 + ], + [ + 109.36163016076962, + -0.04607639271782129 + ], + [ + 109.36151620662305, + -0.04590732021148967 + ], + [ + 109.3614292388156, + -0.04590127999543613 + ], + [ + 109.36137825643945, + -0.04595260359681559 + ], + [ + 109.36128529263681, + -0.045868066525552385 + ], + [ + 109.36124330736023, + -0.045901275705409576 + ], + [ + 109.36118333084896, + -0.045840892236100377 + ], + [ + 109.36116593752331, + -0.04585451090326423 + ], + [ + 109.36112934979236, + -0.04588315845070381 + ], + [ + 109.36106337549602, + -0.04582277484709599 + ], + [ + 109.361097848471, + -0.04577794929340044 + ], + [ + 109.36102562911356, + -0.04569453087470122 + ], + [ + 109.36099386554808, + -0.04566054843601811 + ], + [ + 109.36086245001734, + -0.045804655595276744 + ], + [ + 109.36076648747668, + -0.045714080275941905 + ], + [ + 109.36089289106617, + -0.045554394636247664 + ], + [ + 109.36091943542759, + -0.045520861143975716 + ], + [ + 109.36087445324777, + -0.04546651624594969 + ], + [ + 109.36083951614694, + -0.04550292126337334 + ], + [ + 109.36070351228639, + -0.04564463944687616 + ], + [ + 109.36062854138588, + -0.045581236554041074 + ], + [ + 109.3607425023366, + -0.045448398607724275 + ], + [ + 109.36067652668, + -0.04544839709989972 + ], + [ + 109.36066453151476, + -0.04543028220484662 + ], + [ + 109.36067352867124, + -0.045409148685935104 + ], + [ + 109.36065853508805, + -0.04536989999837066 + ], + [ + 109.36060755525943, + -0.045309516767303916 + ], + [ + 109.36061990478073, + -0.04529017790078606 + ], + [ + 109.36063454624916, + -0.045267249934205486 + ], + [ + 109.36063754636236, + -0.04521290614055102 + ], + [ + 109.36061955367937, + -0.04518271469730103 + ], + [ + 109.36057157138683, + -0.04518271360703615 + ], + [ + 109.3605441015434, + -0.045210367325904256 + ], + [ + 109.36051091036461, + -0.0451754214978501 + ], + [ + 109.36041806343609, + -0.045085250554122006 + ], + [ + 109.36046361434065, + -0.04504383240769782 + ], + [ + 109.36044562260037, + -0.04497137352450248 + ], + [ + 109.36041863242843, + -0.044977411120267055 + ], + [ + 109.36036165379873, + -0.0449623143161881 + ], + [ + 109.36034649081165, + -0.045018281662073785 + ], + [ + 109.36033466153702, + -0.04506194410416093 + ], + [ + 109.36030167276964, + -0.045104210797491114 + ], + [ + 109.36020870580874, + -0.04516157164135109 + ], + [ + 109.36011274334012, + -0.04506797727932549 + ], + [ + 109.36013373754497, + -0.04498042377761121 + ], + [ + 109.36006776318841, + -0.0449230593358575 + ], + [ + 109.35998379635265, + -0.04482644615987108 + ], + [ + 109.35986684183571, + -0.044723794045026 + ], + [ + 109.35978886862767, + -0.04481436535507459 + ], + [ + 109.35967191231069, + -0.04479322900400903 + ], + [ + 109.35957594985203, + -0.044699634684290605 + ], + [ + 109.35967462339593, + -0.04459549411132331 + ], + [ + 109.35976188549354, + -0.044503397229059403 + ], + [ + 109.35966592241799, + -0.04443697483692993 + ], + [ + 109.35957077162944, + -0.0445361235762819 + ], + [ + 109.359494981651, + -0.044615098015241376 + ], + [ + 109.35943800384756, + -0.044563772008408116 + ], + [ + 109.3595054374608, + -0.044497721701339144 + ], + [ + 109.3595489652392, + -0.044455086832415884 + ], + [ + 109.3595279743957, + -0.04439168522593096 + ], + [ + 109.35949798607824, + -0.04436451264061259 + ], + [ + 109.3594590006206, + -0.04435847356690464 + ], + [ + 109.3594170158733, + -0.04437054903600913 + ], + [ + 109.35938764959116, + -0.04440935024568593 + ], + [ + 109.35932876973332, + -0.044363280875412456 + ], + [ + 109.35932405170118, + -0.04430412672825414 + ], + [ + 109.35934204584595, + -0.04426789791177786 + ], + [ + 109.35935396265728, + -0.04419304276705544 + ], + [ + 109.35929706585489, + -0.044113922737907214 + ], + [ + 109.35924008685502, + -0.044116940574129536 + ], + [ + 109.35924908246989, + -0.04416524639521165 + ], + [ + 109.35922509034634, + -0.04421053238135845 + ], + [ + 109.35920109908426, + -0.044216570049988364 + ], + [ + 109.35917710848543, + -0.04419241670631497 + ], + [ + 109.35915311795297, + -0.04416524426197268 + ], + [ + 109.35916211548653, + -0.044125996146374025 + ], + [ + 109.35920710017575, + -0.04406561512007003 + ], + [ + 109.35915012144285, + -0.044056556552782185 + ], + [ + 109.35911413494838, + -0.04404749845129849 + ], + [ + 109.35911713509356, + -0.043990135596329465 + ], + [ + 109.3591021413637, + -0.04395692515211779 + ], + [ + 109.35912913217336, + -0.04392069653545359 + ], + [ + 109.3590181750506, + -0.04383615925491077 + ], + [ + 109.35888047379092, + -0.043969831592613036 + ], + [ + 109.35884189474129, + -0.04393370274937445 + ], + [ + 109.35872587094693, + -0.04382493987302808 + ], + [ + 109.3587932602837, + -0.04374256217165247 + ], + [ + 109.35867930447888, + -0.043648967547491015 + ], + [ + 109.3586209687918, + -0.04371906012860145 + ], + [ + 109.35853287910868, + -0.043626202755313946 + ], + [ + 109.35837748848934, + -0.043460702748466654 + ], + [ + 109.35835690102246, + -0.043438666501557616 + ], + [ + 109.35830144727299, + -0.043513099747311546 + ], + [ + 109.35826846046449, + -0.04346781252675963 + ], + [ + 109.3583141316925, + -0.043392887412026335 + ], + [ + 109.35834943335297, + -0.04333497390077376 + ], + [ + 109.3582804599925, + -0.04328364769783276 + ], + [ + 109.35825188656847, + -0.043326261983280746 + ], + [ + 109.35819948737063, + -0.0434044099223638 + ], + [ + 109.35804954562704, + -0.04327760446506522 + ], + [ + 109.35812265819503, + -0.04319834066989094 + ], + [ + 109.35820549150938, + -0.043108538278291815 + ], + [ + 109.35817250443534, + -0.043075327465418506 + ], + [ + 109.35813052101533, + -0.043027020960485524 + ], + [ + 109.35808853649827, + -0.04303003915018413 + ], + [ + 109.35807954034642, + -0.043005886158767 + ], + [ + 109.35804055368692, + -0.043057210005723746 + ], + [ + 109.35801656302422, + -0.04303607578920492 + ], + [ + 109.35799084477078, + -0.04306686442293789 + ], + [ + 109.35790560127703, + -0.04316891375270607 + ], + [ + 109.35781563693345, + -0.043060224226745325 + ], + [ + 109.35790527738885, + -0.04297916058847342 + ], + [ + 109.357992574811, + -0.042900215795118185 + ], + [ + 109.35796558500083, + -0.04289115791371068 + ], + [ + 109.35793559623743, + -0.042885119067174314 + ], + [ + 109.35792959890942, + -0.04286398524231856 + ], + [ + 109.35799857413323, + -0.042827757538304936 + ], + [ + 109.35792960038745, + -0.04279454595778683 + ], + [ + 109.35782164281699, + -0.042679817862281144 + ], + [ + 109.35772267973826, + -0.04266773933845376 + ], + [ + 109.35769269142597, + -0.04264056680312994 + ], + [ + 109.35764126221694, + -0.04270384558103825 + ], + [ + 109.35758472861149, + -0.04277340483123276 + ], + [ + 109.35750975761539, + -0.042716040340423346 + ], + [ + 109.35757578516838, + -0.0426351988950238 + ], + [ + 109.35762071937471, + -0.04258018327981124 + ], + [ + 109.35762072014117, + -0.04254395409354648 + ], + [ + 109.35750976336698, + -0.042444321458401796 + ], + [ + 109.3574562889661, + -0.04251026619783059 + ], + [ + 109.35738980470236, + -0.042592254719837644 + ], + [ + 109.35726985153421, + -0.0424805455073499 + ], + [ + 109.3573439674819, + -0.04240229362773604 + ], + [ + 109.35739280869065, + -0.042350726903299434 + ], + [ + 109.35738081529593, + -0.04224807729857012 + ], + [ + 109.35741980122503, + -0.042229963537146364 + ], + [ + 109.35736582294456, + -0.04214844672981821 + ], + [ + 109.3573238380658, + -0.042169579527001205 + ], + [ + 109.3572908508649, + -0.042142406940770774 + ], + [ + 109.3572578623992, + -0.04217561632161648 + ], + [ + 109.35729384842371, + -0.04220580807005448 + ], + [ + 109.35728185217388, + -0.0422390178967156 + ], + [ + 109.35722882077253, + -0.0422857310952192 + ], + [ + 109.3571379028795, + -0.04236581695389916 + ], + [ + 109.35702694650487, + -0.04224806976456387 + ], + [ + 109.35711242437112, + -0.042159813063947775 + ], + [ + 109.35714390566554, + -0.042127308329425996 + ], + [ + 109.35710492088099, + -0.04209107832488808 + ], + [ + 109.35707493231803, + -0.04207598219859871 + ], + [ + 109.35705394073453, + -0.042045790773482604 + ], + [ + 109.35706293783991, + -0.04202465727807277 + ], + [ + 109.357026951186, + -0.04202465651588061 + ], + [ + 109.35700783344568, + -0.042016565871980986 + ], + [ + 109.35698000073705, + -0.041982175870709335 + ], + [ + 109.35696734989729, + -0.04194778619140424 + ], + [ + 109.35697241121075, + -0.041914670568624225 + ], + [ + 109.35698000259656, + -0.04189301813636136 + ], + [ + 109.35700530613619, + -0.04187263975960873 + ], + [ + 109.35702934401627, + -0.041876461312721595 + ], + [ + 109.35703566998079, + -0.04186754567259129 + ], + [ + 109.35697747363722, + -0.0418280603051127 + ], + [ + 109.35695343604897, + -0.04181022825171124 + ], + [ + 109.35692813240462, + -0.041835701356201065 + ], + [ + 109.35696229073436, + -0.04187773357947517 + ], + [ + 109.35691596919956, + -0.041929394231797104 + ], + [ + 109.35683324185041, + -0.04202165690187058 + ], + [ + 109.3567543817726, + -0.041959881665392516 + ], + [ + 109.35685558916335, + -0.0418600757814692 + ], + [ + 109.356919698841, + -0.041796853880987105 + ], + [ + 109.35683535668049, + -0.04172212943689648 + ], + [ + 109.35683704615845, + -0.04159645953204361 + ], + [ + 109.35673077449044, + -0.041528527609941135 + ], + [ + 109.35662116959881, + -0.04161626691970774 + ], + [ + 109.35659076162544, + -0.04164060866577832 + ], + [ + 109.35644400727858, + -0.04146229016429542 + ], + [ + 109.35637315703461, + -0.04153701133300244 + ], + [ + 109.3561825447136, + -0.04132302886424785 + ], + [ + 109.35626976927473, + -0.041235220107534984 + ], + [ + 109.35628375922713, + -0.04122113645534404 + ], + [ + 109.35632086964804, + -0.04126019679256605 + ], + [ + 109.35636472892392, + -0.04123302583243444 + ], + [ + 109.35642882863011, + -0.0413060515748155 + ], + [ + 109.35654016467717, + -0.04119057342485701 + ], + [ + 109.35646763055888, + -0.04111924574946176 + ], + [ + 109.35642883256405, + -0.041114150218875864 + ], + [ + 109.35640859039052, + -0.041098865620631905 + ], + [ + 109.35640521716353, + -0.041073391919711245 + ], + [ + 109.35639340956703, + -0.041047918044270784 + ], + [ + 109.35637485454457, + -0.0410190475455645 + ], + [ + 109.35634955164542, + -0.04100885757014243 + ], + [ + 109.35626858212143, + -0.04098847699250362 + ], + [ + 109.35621966306128, + -0.040974890046181545 + ], + [ + 109.35615218662551, + -0.04104791305160217 + ], + [ + 109.35612350987856, + -0.04104281773257773 + ], + [ + 109.35611507578794, + -0.04102923162342592 + ], + [ + 109.3561117025605, + -0.04100375792641453 + ], + [ + 109.35615893616377, + -0.04094771692173067 + ], + [ + 109.35617074504009, + -0.04091035584449664 + ], + [ + 109.35616231157015, + -0.04086620138212033 + ], + [ + 109.35613026131678, + -0.040849218303328876 + ], + [ + 109.35609483710981, + -0.04084242460637574 + ], + [ + 109.356061099846, + -0.040832234460899855 + ], + [ + 109.35601892721414, + -0.04087129315248423 + ], + [ + 109.35602904797153, + -0.040895068745511895 + ], + [ + 109.35602904766037, + -0.04091035292126121 + ], + [ + 109.35602904714172, + -0.04093582654742574 + ], + [ + 109.35601367744478, + -0.04096129455650488 + ], + [ + 109.35598181395738, + -0.040971488648182355 + ], + [ + 109.35595145002753, + -0.040981677470871634 + ], + [ + 109.35593120706133, + -0.041005452435756096 + ], + [ + 109.3558401179751, + -0.04090355605687003 + ], + [ + 109.35590513731852, + -0.040838755761761665 + ], + [ + 109.35600880873143, + -0.040735433605579525 + ], + [ + 109.35600037508662, + -0.04069977035614433 + ], + [ + 109.35596663799562, + -0.0406810890050579 + ], + [ + 109.3559565174421, + -0.040647123963717245 + ], + [ + 109.35594302272953, + -0.040633537753719605 + ], + [ + 109.35591771966257, + -0.0406318389935541 + ], + [ + 109.35588060865398, + -0.040621648783343615 + ], + [ + 109.3558620533215, + -0.0406080624703651 + ], + [ + 109.35585699325028, + -0.04058089050112751 + ], + [ + 109.35585699373033, + -0.04055711511870642 + ], + [ + 109.3558620548985, + -0.04052994335662643 + ], + [ + 109.35587048947096, + -0.04051975407934933 + ], + [ + 109.35589579335853, + -0.04048069503893047 + ], + [ + 109.35588904654908, + -0.04044673006865346 + ], + [ + 109.35587049152278, + -0.04041785958276018 + ], + [ + 109.35583000690418, + -0.04040087634169963 + ], + [ + 109.35582325954726, + -0.04039408323794678 + ], + [ + 109.35579795695963, + -0.040368609099221346 + ], + [ + 109.35577602757279, + -0.0403703068942384 + ], + [ + 109.3557422903462, + -0.04035841851677734 + ], + [ + 109.35570349284161, + -0.04032954762204012 + ], + [ + 109.35571192761648, + -0.04030916889592067 + ], + [ + 109.35571530211205, + -0.0402718076520238 + ], + [ + 109.35571530289452, + -0.040232748098050906 + ], + [ + 109.35570180831671, + -0.04021236892658816 + ], + [ + 109.35565626325005, + -0.04018689438135809 + ], + [ + 109.3556191524142, + -0.040168212973702724 + ], + [ + 109.35558372824823, + -0.04015972104914026 + ], + [ + 109.35552974824162, + -0.04016311643824366 + ], + [ + 109.35552300030778, + -0.04018519343860443 + ], + [ + 109.35550275766133, + -0.04019368423473552 + ], + [ + 109.35547239401484, + -0.040190287136508525 + ], + [ + 109.35546359086581, + -0.040189020886423246 + ], + [ + 109.35537141322715, + -0.04006462572446814 + ], + [ + 109.35536241777467, + -0.040004243634160656 + ], + [ + 109.35537141527163, + -0.039961976479658226 + ], + [ + 109.3553684172867, + -0.03991668998771423 + ], + [ + 109.35532043620317, + -0.03986234530498238 + ], + [ + 109.35526045874128, + -0.03985026771767318 + ], + [ + 109.35521247604215, + -0.039877438609684755 + ], + [ + 109.35520347956702, + -0.03986838114312391 + ], + [ + 109.35512642954245, + -0.03994594732428466 + ], + [ + 109.35502054465123, + -0.040016313113330884 + ], + [ + 109.35487960097699, + -0.0398200691124397 + ], + [ + 109.35498442636013, + -0.03972536486035229 + ], + [ + 109.35511351815657, + -0.03960873715958665 + ], + [ + 109.355053541473, + -0.03955741134667016 + ], + [ + 109.35504454517566, + -0.039539296597466154 + ], + [ + 109.35503555065822, + -0.039430609000659055 + ], + [ + 109.35487270868978, + -0.039561086080196414 + ], + [ + 109.35474165534406, + -0.03966609252188206 + ], + [ + 109.35469967185922, + -0.03962080526517178 + ], + [ + 109.35470567069908, + -0.03956646168551016 + ], + [ + 109.35468168039866, + -0.039527212979394294 + ], + [ + 109.35466368786618, + -0.03948796439377569 + ], + [ + 109.3546426963301, + -0.03945475393778961 + ], + [ + 109.35458871653599, + -0.039448714674360105 + ], + [ + 109.35456172592748, + -0.03948192417317863 + ], + [ + 109.35455572750637, + -0.039515134089780996 + ], + [ + 109.35451074359374, + -0.03954834322793938 + ], + [ + 109.354462762403, + -0.03950003676524366 + ], + [ + 109.35445376604856, + -0.03948494111566552 + ], + [ + 109.35437579524941, + -0.03947588227982022 + ], + [ + 109.35432781435675, + -0.03941248035283827 + ], + [ + 109.35433381265722, + -0.03938530862748769 + ], + [ + 109.35426483987368, + -0.03930681081855949 + ], + [ + 109.3542678397024, + -0.03925850537798466 + ], + [ + 109.35424984628321, + -0.039264543208823076 + ], + [ + 109.35421086008954, + -0.03930077156001586 + ], + [ + 109.35409090729371, + -0.03917094816000691 + ], + [ + 109.3541448884289, + -0.03910754826262809 + ], + [ + 109.35409690735426, + -0.03905320363231019 + ], + [ + 109.35411190229911, + -0.03902603208630473 + ], + [ + 109.35409390964487, + -0.03899282170336071 + ], + [ + 109.3542288615616, + -0.03888111789325653 + ], + [ + 109.35420487078649, + -0.03886602195402766 + ], + [ + 109.3540639210474, + -0.03898074473920264 + ], + [ + 109.3539919496674, + -0.038887151429243504 + ], + [ + 109.35414697863604, + -0.03872604931665549 + ], + [ + 109.35414117598118, + -0.03871817149985981 + ], + [ + 109.35406806733808, + -0.038626803229589494 + ], + [ + 109.35416889087718, + -0.03851882548335396 + ], + [ + 109.35407592763654, + -0.038407117217263274 + ], + [ + 109.3540219469307, + -0.038449383477822466 + ], + [ + 109.35393498117071, + -0.03835277080541805 + ], + [ + 109.35388733167395, + -0.038389227019876314 + ], + [ + 109.35381618664834, + -0.038300501322366856 + ], + [ + 109.35387500541292, + -0.03825313956973493 + ], + [ + 109.35380003512392, + -0.03815954623718146 + ], + [ + 109.35373621521828, + -0.038201682362018936 + ], + [ + 109.35379386538071, + -0.038272664240292326 + ], + [ + 109.35368007567227, + -0.03837691861149509 + ], + [ + 109.35353313377215, + -0.03820180839052326 + ], + [ + 109.35359911050577, + -0.03813237053050985 + ], + [ + 109.35354513211229, + -0.03805387308190134 + ], + [ + 109.35347315802002, + -0.038102177179261114 + ], + [ + 109.35340418550417, + -0.03800858398624277 + ], + [ + 109.3535200418526, + -0.037935688866241074 + ], + [ + 109.35350070724269, + -0.03791057775026805 + ], + [ + 109.35346014102039, + -0.03785775571184492 + ], + [ + 109.35352855977078, + -0.03779913682269466 + ], + [ + 109.35346783427875, + -0.037699505609606325 + ], + [ + 109.35338114192179, + -0.03775489036581923 + ], + [ + 109.35335087646938, + -0.037774225918392676 + ], + [ + 109.3532968974876, + -0.03772667418504918 + ], + [ + 109.35321142798904, + -0.03779913076773333 + ], + [ + 109.3532271716494, + -0.037824038578771396 + ], + [ + 109.3531844371134, + -0.03784894527209066 + ], + [ + 109.3531731895102, + -0.03794404645782896 + ], + [ + 109.35306972689057, + -0.03800744540509933 + ], + [ + 109.35297526545938, + -0.037830826721719626 + ], + [ + 109.35304948854603, + -0.03779007039970455 + ], + [ + 109.35298426491661, + -0.037681381849606184 + ], + [ + 109.35300675686824, + -0.037663267726783856 + ], + [ + 109.35310346862892, + -0.03777874983533333 + ], + [ + 109.35319343597668, + -0.037731200851446954 + ], + [ + 109.35320243364339, + -0.03767685736460526 + ], + [ + 109.35328349056459, + -0.03762859949676074 + ], + [ + 109.35341160799722, + -0.037552323781896754 + ], + [ + 109.35341385859817, + -0.03747533696698178 + ], + [ + 109.35333513959068, + -0.03738702702605745 + ], + [ + 109.35319119152626, + -0.03747986139003838 + ], + [ + 109.3532114335132, + -0.03750476928351354 + ], + [ + 109.3531949603656, + -0.037515243056152295 + ], + [ + 109.3530832297929, + -0.037586282337168146 + ], + [ + 109.35305399179904, + -0.037527409489781866 + ], + [ + 109.35315420919551, + -0.03746320765453674 + ], + [ + 109.35320243822086, + -0.03743231090188257 + ], + [ + 109.3531642035334, + -0.03737570220243223 + ], + [ + 109.35310960278291, + -0.03741417876958366 + ], + [ + 109.35302925208414, + -0.03747080104818328 + ], + [ + 109.35298427000212, + -0.03740966358764368 + ], + [ + 109.35305570012265, + -0.03735498300737833 + ], + [ + 109.35320019255714, + -0.03724437237432989 + ], + [ + 109.35317095421568, + -0.03720361408104956 + ], + [ + 109.35318669872287, + -0.03718323550521848 + ], + [ + 109.35312822174475, + -0.03711756915532601 + ], + [ + 109.35295698276916, + -0.03724450813337235 + ], + [ + 109.35288081061708, + -0.03730097433298887 + ], + [ + 109.35283582861761, + -0.037235308243478364 + ], + [ + 109.35279084472612, + -0.03727153649520165 + ], + [ + 109.35273461692353, + -0.03720587019785601 + ], + [ + 109.35286178576698, + -0.03710617247073968 + ], + [ + 109.35298877580007, + -0.03700661491648243 + ], + [ + 109.35303375793607, + -0.03706548804909268 + ], + [ + 109.35315521464814, + -0.036956803009181353 + ], + [ + 109.35311248145916, + -0.036909251512424995 + ], + [ + 109.3530225138017, + -0.036974915082741776 + ], + [ + 109.3530000225657, + -0.03695453579279148 + ], + [ + 109.35303376048337, + -0.03692736459578542 + ], + [ + 109.35301576769403, + -0.036900192433431483 + ], + [ + 109.35304500745522, + -0.03686396387588017 + ], + [ + 109.35301576893305, + -0.036832766048856005 + ], + [ + 109.3529145549357, + -0.03692786555371984 + ], + [ + 109.35287631990688, + -0.0368893714219366 + ], + [ + 109.35277594892227, + -0.0369450746058764 + ], + [ + 109.35270088356422, + -0.03698673384848375 + ], + [ + 109.35263341120091, + -0.03685313779888374 + ], + [ + 109.35270873031376, + -0.03680184530290185 + ], + [ + 109.35278635604631, + -0.03674898199038609 + ], + [ + 109.352635665625, + -0.03656556939747276 + ], + [ + 109.35261092518763, + -0.03654745439291519 + ], + [ + 109.35258034329489, + -0.0365425277347652 + ], + [ + 109.3524984674868, + -0.036529337769491785 + ], + [ + 109.352332027713, + -0.03663349332945326 + ], + [ + 109.35207338038336, + -0.03629836950436033 + ], + [ + 109.35221957771644, + -0.03619194924198239 + ], + [ + 109.35211836816019, + -0.03604476672948305 + ], + [ + 109.35207788336001, + -0.036040237356212534 + ], + [ + 109.35202615202775, + -0.03607646549776208 + ], + [ + 109.35210262245002, + -0.03613307483840741 + ], + [ + 109.35205538849519, + -0.03622138236941039 + ], + [ + 109.35196992139664, + -0.03616703717984112 + ], + [ + 109.35187995744752, + -0.03603117647961432 + ], + [ + 109.35195643038745, + -0.03594739811871987 + ], + [ + 109.35200366258377, + -0.03595645624793544 + ], + [ + 109.35207563651596, + -0.03591117120000709 + ], + [ + 109.35207788628402, + -0.03587720647364143 + ], + [ + 109.35205764424879, + -0.03585456292861115 + ], + [ + 109.35203065461177, + -0.03583871221511032 + ], + [ + 109.35200816280957, + -0.03585003339627933 + ], + [ + 109.35193393980244, + -0.0358907897687617 + ], + [ + 109.3518867084984, + -0.035831916654284625 + ], + [ + 109.351999167666, + -0.03576625347743971 + ], + [ + 109.35194968634936, + -0.03575493099443665 + ], + [ + 109.35192719491194, + -0.03574587331732626 + ], + [ + 109.35191145151919, + -0.03570511531559657 + ], + [ + 109.35191595036294, + -0.03567567926762947 + ], + [ + 109.35178999644658, + -0.03573002061543352 + ], + [ + 109.35174726176616, + -0.035766248922356136 + ], + [ + 109.3517045288226, + -0.03570511158016891 + ], + [ + 109.35178774821445, + -0.03567794127272767 + ], + [ + 109.35173826847203, + -0.0355783104134268 + ], + [ + 109.35164830116251, + -0.03563038809340367 + ], + [ + 109.35161456501588, + -0.03556019364977025 + ], + [ + 109.35166404704802, + -0.03553075841408832 + ], + [ + 109.35163930689458, + -0.03549679320994938 + ], + [ + 109.3516640481708, + -0.03546735752861057 + ], + [ + 109.3517045332838, + -0.03545377235031094 + ], + [ + 109.35161906749899, + -0.03532470473268973 + ], + [ + 109.3514976116901, + -0.03539263207770778 + ], + [ + 109.35145937749856, + -0.03530658734334337 + ], + [ + 109.35156059030928, + -0.03527036007484398 + ], + [ + 109.35152460538991, + -0.035177522428570716 + ], + [ + 109.35141439590592, + -0.035216013858021096 + ], + [ + 109.35125696310108, + -0.03473824019359111 + ], + [ + 109.35133227295898, + -0.034711306556190064 + ], + [ + 109.35142790025853, + -0.034677106635772816 + ], + [ + 109.35139641273362, + -0.034636348377003245 + ], + [ + 109.35140091148233, + -0.03461144096834412 + ], + [ + 109.35142790166732, + -0.03459559122128684 + ], + [ + 109.35145264257595, + -0.03458653438571785 + ], + [ + 109.35145489259436, + -0.034536719448857764 + ], + [ + 109.35126918965376, + -0.03460485804713312 + ], + [ + 109.351261300987, + -0.034591546494153545 + ], + [ + 109.35116159786054, + -0.034445952752437295 + ], + [ + 109.35113016035325, + -0.03440073399320486 + ], + [ + 109.35105830097751, + -0.034297624621670846 + ], + [ + 109.35095409785569, + -0.03413303087020534 + ], + [ + 109.35086156660311, + -0.03395759337734832 + ], + [ + 109.3507816291041, + -0.03378395273932479 + ], + [ + 109.35069539473754, + -0.03362931212239536 + ], + [ + 109.35060017597598, + -0.03346742150068235 + ], + [ + 109.35050855098413, + -0.03332001524871884 + ], + [ + 109.35045375410995, + -0.03325851524697974 + ], + [ + 109.35021301972617, + -0.03298810899696801 + ], + [ + 109.35017080099222, + -0.03291757774234873 + ], + [ + 109.35007917597555, + -0.032794577738254276 + ], + [ + 109.34998755097763, + -0.0326733902374387 + ], + [ + 109.34989772285738, + -0.0325395464868247 + ], + [ + 109.34984920722536, + -0.03246448399761651 + ], + [ + 109.34979891036676, + -0.032387608991148266 + ], + [ + 109.34970280097725, + -0.032226640248747655 + ], + [ + 109.34962464472765, + -0.03206204650234349 + ], + [ + 109.34955187910293, + -0.03189564024387037 + ], + [ + 109.34953909893292, + -0.03186650216695205 + ], + [ + 109.34968678017576, + -0.03180007508911912 + ], + [ + 109.3496373001895, + -0.031707237376702765 + ], + [ + 109.34949419965392, + -0.031764134316553096 + ], + [ + 109.34936964881425, + -0.03181365587152746 + ], + [ + 109.34927518781376, + -0.03158722288934819 + ], + [ + 109.34933591558307, + -0.03155325914054559 + ], + [ + 109.34935615838727, + -0.03152835200226175 + ], + [ + 109.34935840807881, + -0.031494387317689033 + ], + [ + 109.34934041510894, + -0.031476272513304177 + ], + [ + 109.34930892684218, + -0.03148080064073616 + ], + [ + 109.34924370076143, + -0.03151476431950121 + ], + [ + 109.34919097213789, + -0.03141261773464891 + ], + [ + 109.34916848039367, + -0.03142393894925475 + ], + [ + 109.34912124892169, + -0.031371858965792164 + ], + [ + 109.34924945237513, + -0.03127449547576941 + ], + [ + 109.34946218142794, + -0.03168833003377781 + ], + [ + 109.34960481372872, + -0.031620941295335395 + ], + [ + 109.34951934775799, + -0.031494138293522204 + ], + [ + 109.34946761696848, + -0.03150545904274788 + ], + [ + 109.34945637200263, + -0.03145337962268769 + ], + [ + 109.34965430009763, + -0.031308466612050674 + ], + [ + 109.34962281203877, + -0.03129940885290489 + ], + [ + 109.34957782903771, + -0.03129035088034157 + ], + [ + 109.34951485267511, + -0.031288085567772225 + ], + [ + 109.34948336458302, + -0.031281292124447836 + ], + [ + 109.34943838158507, + -0.03127223415282583 + ], + [ + 109.3494158905461, + -0.031238269075210587 + ], + [ + 109.34940014669495, + -0.0312224186229485 + ], + [ + 109.34928111651286, + -0.03126423448185429 + ], + [ + 109.34924045606665, + -0.031254116502210445 + ], + [ + 109.34918422894933, + -0.031138635572763376 + ], + [ + 109.34919963590036, + -0.031102842992052914 + ], + [ + 109.34921346904174, + -0.031070706597755696 + ], + [ + 109.34927869483272, + -0.0310548574217731 + ], + [ + 109.3493169301818, + -0.031075236854902863 + ], + [ + 109.3493484186584, + -0.031057122832886325 + ], + [ + 109.34937316043661, + -0.03098919378039388 + ], + [ + 109.34934617142997, + -0.03093258548975254 + ], + [ + 109.34930568723686, + -0.030891827192114904 + ], + [ + 109.34925620589291, + -0.0308850334743609 + ], + [ + 109.34912268409646, + -0.030961843070119937 + ], + [ + 109.34908301929217, + -0.030984660598594664 + ], + [ + 109.34902454307756, + -0.030864651022162827 + ], + [ + 109.34906536239251, + -0.030844767417125492 + ], + [ + 109.34909426745277, + -0.030830687396060593 + ], + [ + 109.34910326463678, + -0.030794458506003892 + ], + [ + 109.34912125816804, + -0.03077634427117202 + ], + [ + 109.34916174295186, + -0.03077860921661227 + ], + [ + 109.34917523743884, + -0.030808045515342438 + ], + [ + 109.3492089749977, + -0.030794460154509935 + ], + [ + 109.34917748736167, + -0.030758230631589594 + ], + [ + 109.34912125851602, + -0.030753701126885243 + ], + [ + 109.34911900956817, + -0.03074011520530303 + ], + [ + 109.34915274726474, + -0.030717472585868797 + ], + [ + 109.3491797376999, + -0.03068124397372978 + ], + [ + 109.34916624355778, + -0.030629164531029502 + ], + [ + 109.3491819881045, + -0.03059972868677074 + ], + [ + 109.34922472235806, + -0.030581614832619692 + ], + [ + 109.3492269723789, + -0.03052500700367551 + ], + [ + 109.34920448164014, + -0.030470663107460168 + ], + [ + 109.34916399699297, + -0.030459340910368997 + ], + [ + 109.34913700663098, + -0.03049104089627849 + ], + [ + 109.34913025864351, + -0.03052500550868511 + ], + [ + 109.34908077713024, + -0.03052953337254728 + ], + [ + 109.3490155513849, + -0.030543118249910725 + ], + [ + 109.34899530956412, + -0.030504624593968473 + ], + [ + 109.34901555245492, + -0.03047292450638783 + ], + [ + 109.34904928990544, + -0.030466132084015277 + ], + [ + 109.34908302739078, + -0.030457075347009924 + ], + [ + 109.34909652274331, + -0.03042990378236154 + ], + [ + 109.34908752632369, + -0.030416317757364354 + ], + [ + 109.34905153954416, + -0.030434431717830668 + ], + [ + 109.34902679902953, + -0.0304208454506079 + ], + [ + 109.34899081256087, + -0.030418580581872433 + ], + [ + 109.3488712543325, + -0.030486844278865128 + ], + [ + 109.34881261347628, + -0.030386265238635062 + ], + [ + 109.34872098847998, + -0.030184593364950274 + ], + [ + 109.34863384786243, + -0.02995940585878649 + ], + [ + 109.34856660511494, + -0.029786471785108147 + ], + [ + 109.3484622693995, + -0.029852494019635346 + ], + [ + 109.34844202780755, + -0.02979815018586825 + ], + [ + 109.34835880968572, + -0.029755126972022004 + ], + [ + 109.34822611329444, + -0.02949925755699754 + ], + [ + 109.34828459200648, + -0.029453972161632657 + ], + [ + 109.34829583748427, + -0.029474351150830552 + ], + [ + 109.3483565652201, + -0.02943812304087221 + ], + [ + 109.34830483577171, + -0.029361135610576725 + ], + [ + 109.3482441081387, + -0.029390570781641846 + ], + [ + 109.34817663513383, + -0.029277354109476968 + ], + [ + 109.34831069388235, + -0.029203222518224495 + ], + [ + 109.34828442598678, + -0.02914639024806457 + ], + [ + 109.34826612664604, + -0.02911523400645183 + ], + [ + 109.34812265576706, + -0.029252445862593037 + ], + [ + 109.34805743151792, + -0.029166400993805984 + ], + [ + 109.34808892008874, + -0.029139229700258847 + ], + [ + 109.34800570308687, + -0.02901921987447825 + ], + [ + 109.34811816210001, + -0.028933177623652575 + ], + [ + 109.3479674718922, + -0.028711272729862722 + ], + [ + 109.3478977483417, + -0.028693157211121978 + ], + [ + 109.34787975552278, + -0.028663720880420025 + ], + [ + 109.34788200516435, + -0.028629756217948037 + ], + [ + 109.3479089954148, + -0.028602584853127937 + ], + [ + 109.347891003144, + -0.02853465520177199 + ], + [ + 109.34783702448652, + -0.028459932093584772 + ], + [ + 109.34785105357396, + -0.02840600863786227 + ], + [ + 109.34784337910256, + -0.02839215586271121 + ], + [ + 109.34776433223652, + -0.02825017148483574 + ], + [ + 109.34770323848595, + -0.02810095273219433 + ], + [ + 109.34769092800255, + -0.028059326574079283 + ], + [ + 109.34770443387248, + -0.02793697026837702 + ], + [ + 109.34776441402758, + -0.027743749752454267 + ], + [ + 109.34781239713007, + -0.027662235161755557 + ], + [ + 109.34781539750391, + -0.027553548183075954 + ], + [ + 109.3478693779733, + -0.027499205425032102 + ], + [ + 109.34793235460715, + -0.02747807271406477 + ], + [ + 109.34791128126543, + -0.027428674152216653 + ], + [ + 109.34800133064428, + -0.02733617671748206 + ], + [ + 109.34810329214433, + -0.027351273551657573 + ], + [ + 109.34813328137764, + -0.02731504495452734 + ], + [ + 109.34802232424245, + -0.027227489976155955 + ], + [ + 109.34757659786439, + -0.027495030858120967 + ], + [ + 109.3474945190357, + -0.027484104783075357 + ], + [ + 109.34732958532628, + -0.027170117835797893 + ], + [ + 109.34712566725464, + -0.026780653338599192 + ], + [ + 109.34716903065204, + -0.0267012850820896 + ], + [ + 109.3472224401756, + -0.02660352933713426 + ], + [ + 109.34714666246175, + -0.026548184241435662 + ], + [ + 109.34705320888149, + -0.026504928016869187 + ], + [ + 109.34697272867572, + -0.026487800247666844 + ], + [ + 109.34693074582505, + -0.026385150876844937 + ], + [ + 109.34689475924192, + -0.026394207642875245 + ], + [ + 109.34676581167784, + -0.026095316768851823 + ], + [ + 109.34681413934253, + -0.026070990886119357 + ], + [ + 109.34673759784948, + -0.025898843369273863 + ], + [ + 109.34666573848037, + -0.025697171481943164 + ], + [ + 109.34658580098623, + -0.025508155860454435 + ], + [ + 109.34650673848056, + -0.02536074960777916 + ], + [ + 109.34648878535894, + -0.025329093362073597 + ], + [ + 109.34644117597969, + -0.02524498399496843 + ], + [ + 109.34638367597734, + -0.02514460899175706 + ], + [ + 109.34633697285048, + -0.02506140586342566 + ], + [ + 109.34630373848833, + -0.024997187112673205 + ], + [ + 109.34627409786178, + -0.02493570274409901 + ], + [ + 109.34626680851193, + -0.024919395026026185 + ], + [ + 109.34641879164849, + -0.02483664859861028 + ], + [ + 109.34644578213589, + -0.024784569777360866 + ], + [ + 109.34644578275315, + -0.02473475492745219 + ], + [ + 109.34647052419675, + -0.024673618830353215 + ], + [ + 109.34648402067914, + -0.02454681756061168 + ], + [ + 109.3464727756343, + -0.02448794532442956 + ], + [ + 109.34647502542525, + -0.024435866190249924 + ], + [ + 109.34648177396365, + -0.024347558129292516 + ], + [ + 109.34651326288358, + -0.02428415779790075 + ], + [ + 109.34650876537353, + -0.024218492710922565 + ], + [ + 109.34649976952718, + -0.024155091880579765 + ], + [ + 109.34651551488511, + -0.02404866943576996 + ], + [ + 109.34649752274542, + -0.02395809675951292 + ], + [ + 109.34649302530592, + -0.023885638739538778 + ], + [ + 109.34644354414702, + -0.023867523649170188 + ], + [ + 109.34640081066541, + -0.023831294150891958 + ], + [ + 109.34635132948151, + -0.023815443373941786 + ], + [ + 109.34633783539722, + -0.02374524956259606 + ], + [ + 109.34632003188607, + -0.023620616786635282 + ], + [ + 109.34634518509604, + -0.023624319862172397 + ], + [ + 109.34637088822397, + -0.023624866743929544 + ], + [ + 109.3463883101009, + -0.023625226114492234 + ], + [ + 109.3464305288465, + -0.023622522989168684 + ], + [ + 109.34645927884489, + -0.023619804247047304 + ], + [ + 109.34646556010193, + -0.023619804236005407 + ], + [ + 109.34646735697916, + -0.023619804240267363 + ], + [ + 109.34648262260914, + -0.02361799174484623 + ], + [ + 109.34653562260743, + -0.023613476121631116 + ], + [ + 109.34666049760996, + -0.02360352299538978 + ], + [ + 109.34697255174807, + -0.023553900016832875 + ], + [ + 109.34714348761898, + -0.0235403161853064 + ], + [ + 109.34731667289863, + -0.023506353566881324 + ], + [ + 109.34733466626686, + -0.023495032219373456 + ], + [ + 109.34733466693132, + -0.023438424409824004 + ], + [ + 109.34725819594846, + -0.02341578037826162 + ], + [ + 109.34726044565922, + -0.02336822984669815 + ], + [ + 109.34739314553016, + -0.02338861023087789 + ], + [ + 109.34743587909442, + -0.023420311112499915 + ], + [ + 109.34750110444394, + -0.023431633449718662 + ], + [ + 109.34754383819585, + -0.02344748414567087 + ], + [ + 109.34760006714244, + -0.023440691875932635 + ], + [ + 109.34763380472845, + -0.023418049149536702 + ], + [ + 109.34765404725397, + -0.023406727826063293 + ], + [ + 109.3476675424739, + -0.02338182054577899 + ], + [ + 109.3477507611232, + -0.023388614470607246 + ], + [ + 109.34775750797822, + -0.02344069374651556 + ], + [ + 109.34777775034576, + -0.023442958299884586 + ], + [ + 109.34778225008507, + -0.023320685457010777 + ], + [ + 109.34787671438244, + -0.023338801077516343 + ], + [ + 109.34788570997003, + -0.023427109391491736 + ], + [ + 109.34789920492688, + -0.023424845238662294 + ], + [ + 109.34790820202394, + -0.023384087710757385 + ], + [ + 109.34800041740995, + -0.023381824490422642 + ], + [ + 109.34801166342399, + -0.02336144580523405 + ], + [ + 109.34818035025432, + -0.023345597608699874 + ], + [ + 109.34821408705608, + -0.023390884275572413 + ], + [ + 109.3483805245367, + -0.023393150561726136 + ], + [ + 109.3483850230066, + -0.023379564733487793 + ], + [ + 109.34866166958784, + -0.0233433389876899 + ], + [ + 109.34874039023147, + -0.023327489720539624 + ], + [ + 109.34877637670148, + -0.023329754459816512 + ], + [ + 109.34883035626238, + -0.023345605296359456 + ], + [ + 109.34885059842965, + -0.023365984362820388 + ], + [ + 109.34885365555115, + -0.023393810696783965 + ], + [ + 109.34898934308455, + -0.02338043869824278 + ], + [ + 109.34899503613862, + -0.02339094659913024 + ], + [ + 109.34901685993871, + -0.023394767888104094 + ], + [ + 109.34905671328094, + -0.023300197855963947 + ], + [ + 109.34918765585657, + -0.023343185996765815 + ], + [ + 109.34922371338158, + -0.02328013941477729 + ], + [ + 109.34938533629902, + -0.02328645663531783 + ], + [ + 109.34940220475715, + -0.023305137430937345 + ], + [ + 109.34947136640776, + -0.0232983453025398 + ], + [ + 109.34946968103813, + -0.023169279337290245 + ], + [ + 109.34994369185873, + -0.023086071307017314 + ], + [ + 109.35006008589313, + -0.023074185010685314 + ], + [ + 109.35013430803346, + -0.023079280587551657 + ], + [ + 109.35013768131931, + -0.02311834007100493 + ], + [ + 109.3501723674874, + -0.02312348825691532 + ], + [ + 109.35022360636408, + -0.0231015179183241 + ], + [ + 109.3502625099951, + -0.023081457950892764 + ], + [ + 109.350280538939, + -0.023034650509123858 + ], + [ + 109.35028433500739, + -0.022981156093397542 + ], + [ + 109.35057848274691, + -0.02297638322225443 + ], + [ + 109.35064585200682, + -0.022981160297336406 + ], + [ + 109.35064716161989, + -0.023004417523381393 + ], + [ + 109.35065628737189, + -0.023166480546938227 + ], + [ + 109.35070942435263, + -0.023112986698282358 + ], + [ + 109.35077774296492, + -0.02307573241754872 + ], + [ + 109.35089635127858, + -0.023045165529900283 + ], + [ + 109.35104342541365, + -0.023023196297060167 + ], + [ + 109.35116203341333, + -0.02302128716101717 + ], + [ + 109.35123622038525, + -0.023032190946303557 + ], + [ + 109.35123915395782, + -0.022905260204295893 + ], + [ + 109.3512395957968, + -0.02288614254827047 + ], + [ + 109.35132680900317, + -0.0228928331616502 + ], + [ + 109.35137630219691, + -0.022885816405784905 + ], + [ + 109.35137454597032, + -0.02283010226418045 + ], + [ + 109.35139647527085, + -0.02283010251737005 + ], + [ + 109.35146057597454, + -0.022858973300604078 + ], + [ + 109.35149991483961, + -0.022867018333820516 + ], + [ + 109.35156853536222, + -0.02288105164160239 + ], + [ + 109.35157190853816, + -0.02293030058059162 + ], + [ + 109.35159046416005, + -0.022925206081895317 + ], + [ + 109.35159215130136, + -0.022901430770171087 + ], + [ + 109.35163094937626, + -0.02289463826755663 + ], + [ + 109.35163094994186, + -0.02284506972707573 + ], + [ + 109.3516309501898, + -0.022823312272387 + ], + [ + 109.3517322074589, + -0.02282527381994762 + ], + [ + 109.35174228357702, + -0.02282330393258229 + ], + [ + 109.3517422833253, + -0.022845390652164798 + ], + [ + 109.35184180868995, + -0.02284029708729674 + ], + [ + 109.3518418091252, + -0.02280206770905832 + ], + [ + 109.35184180973278, + -0.022748592226555238 + ], + [ + 109.35191097138946, + -0.022748593021988478 + ], + [ + 109.35191280531045, + -0.02278491139505032 + ], + [ + 109.35191603085896, + -0.022848789135450045 + ], + [ + 109.35200880875327, + -0.022843695492279905 + ], + [ + 109.35201769525105, + -0.022759353045413017 + ], + [ + 109.35202061811594, + -0.022731611899851824 + ], + [ + 109.35202905244549, + -0.022733310235067136 + ], + [ + 109.35209733751508, + -0.022738738283039815 + ], + [ + 109.35217806106431, + -0.02271784363758568 + ], + [ + 109.35238383031903, + -0.022760380542293224 + ], + [ + 109.35248504214213, + -0.02279434648083324 + ], + [ + 109.35268971550532, + -0.022814727705132213 + ], + [ + 109.352736948724, + -0.02274000573681177 + ], + [ + 109.35291688162697, + -0.022735479166706447 + ], + [ + 109.35297086171657, + -0.022715100917176788 + ], + [ + 109.35298660643132, + -0.022663021764128786 + ], + [ + 109.3529033875592, + -0.022656227854968042 + ], + [ + 109.35290541599154, + -0.02261437451560746 + ], + [ + 109.35294949757856, + -0.02260428864534096 + ], + [ + 109.35306810495956, + -0.022561689898687627 + ], + [ + 109.35307839227453, + -0.022520036023698946 + ], + [ + 109.35311719081508, + -0.0224741840052387 + ], + [ + 109.35313574667828, + -0.022448710626791366 + ], + [ + 109.35316104979674, + -0.022443616195903317 + ], + [ + 109.35318972658087, + -0.022445314760327646 + ], + [ + 109.35321840334643, + -0.022448711564159222 + ], + [ + 109.35323695881141, + -0.022458901210775692 + ], + [ + 109.35326621862062, + -0.02248414894886784 + ], + [ + 109.35327069561933, + -0.022513245253913322 + ], + [ + 109.35327069529521, + -0.022542115323487766 + ], + [ + 109.35328587696131, + -0.02255739965097175 + ], + [ + 109.35330274618497, + -0.022511547378950236 + ], + [ + 109.35330274629938, + -0.022501357942485655 + ], + [ + 109.35338877677357, + -0.022496264201856086 + ], + [ + 109.35339383767189, + -0.022470790666950617 + ], + [ + 109.35341239346171, + -0.02245211024296296 + ], + [ + 109.35342968395953, + -0.02244583491330966 + ], + [ + 109.3534567266103, + -0.022445835219897934 + ], + [ + 109.35347380616344, + -0.022447268303166446 + ], + [ + 109.3534844809503, + -0.022442253310464084 + ], + [ + 109.35348590444003, + -0.022425058650922935 + ], + [ + 109.35349017450025, + -0.022410013358037915 + ], + [ + 109.35349800271625, + -0.02240284899840693 + ], + [ + 109.35351650561536, + -0.022399983428433388 + ], + [ + 109.35353429679361, + -0.022403565853889296 + ], + [ + 109.35354568306941, + -0.02241287976569471 + ], + [ + 109.35355351102157, + -0.022429358085680313 + ], + [ + 109.35355991570007, + -0.02244368705518842 + ], + [ + 109.35356560876974, + -0.022454433792451545 + ], + [ + 109.35357414841053, + -0.022467329896612192 + ], + [ + 109.35361257741148, + -0.022470196112141374 + ], + [ + 109.35365883468607, + -0.02246088285361175 + ], + [ + 109.35369370557302, + -0.022452285910418878 + ], + [ + 109.35374302054306, + -0.022421545675831548 + ], + [ + 109.3537733845863, + -0.022389279463048486 + ], + [ + 109.35379868773147, + -0.022382486789787687 + ], + [ + 109.35380880920536, + -0.022360409786160145 + ], + [ + 109.35381893064137, + -0.022341729261996367 + ], + [ + 109.35384929441985, + -0.02233323840511791 + ], + [ + 109.35388471873756, + -0.02233154056472849 + ], + [ + 109.35392857745046, + -0.02232644633942419 + ], + [ + 109.35394207266735, + -0.022304369372049316 + ], + [ + 109.35397074951958, + -0.022300973215030346 + ], + [ + 109.35399605261034, + -0.022299275259921012 + ], + [ + 109.35400528104364, + -0.022194947543423365 + ], + [ + 109.35401798467898, + -0.02205133246263072 + ], + [ + 109.35431656095524, + -0.02205133578669613 + ], + [ + 109.35431169978995, + -0.022141505400007956 + ], + [ + 109.35430475043486, + -0.022270408650693747 + ], + [ + 109.35433174017565, + -0.022289089597416347 + ], + [ + 109.35435704315799, + -0.022297581083723424 + ], + [ + 109.35437897247225, + -0.022299279570868503 + ], + [ + 109.35439415439404, + -0.022292486780379452 + ], + [ + 109.35440090210747, + -0.022272107972012564 + ], + [ + 109.35441439723266, + -0.0222585222007882 + ], + [ + 109.35442957909821, + -0.022256824131001433 + ], + [ + 109.35444982156034, + -0.02225682435842423 + ], + [ + 109.35447849853271, + -0.022243238757198643 + ], + [ + 109.35449368047381, + -0.022234747725504808 + ], + [ + 109.35451898345839, + -0.0222432392117503 + ], + [ + 109.35453416521169, + -0.022251730584580343 + ], + [ + 109.35454934732242, + -0.022227955388279496 + ], + [ + 109.35462188281751, + -0.02222795620208202 + ], + [ + 109.35462584427218, + -0.022107023053572098 + ], + [ + 109.35462694513828, + -0.022073416368727988 + ], + [ + 109.35468767256887, + -0.022070020564087862 + ], + [ + 109.35468767310957, + -0.022020771586657984 + ], + [ + 109.35470960268839, + -0.021998694702298496 + ], + [ + 109.35473153213714, + -0.021988505501907587 + ], + [ + 109.35477201708646, + -0.021986807710539107 + ], + [ + 109.35479900704071, + -0.02198680801002782 + ], + [ + 109.35482093634181, + -0.02199020473478408 + ], + [ + 109.35483949174979, + -0.022007187348011117 + ], + [ + 109.35484286536392, + -0.022019075070642803 + ], + [ + 109.35484623867961, + -0.02205813464514822 + ], + [ + 109.35490190540695, + -0.02206322998713502 + ], + [ + 109.35491034004781, + -0.022037756469162728 + ], + [ + 109.35492720901256, + -0.02201567952625445 + ], + [ + 109.35494070408379, + -0.022007188472063382 + ], + [ + 109.3549609466069, + -0.02200209397437606 + ], + [ + 109.35496938091265, + -0.022007188790532293 + ], + [ + 109.35497612789892, + -0.02205304136844078 + ], + [ + 109.35502336034261, + -0.02205134365312821 + ], + [ + 109.35503487081806, + -0.02207369263888375 + ], + [ + 109.3550469760518, + -0.022097196420507722 + ], + [ + 109.35507733966118, + -0.02210568796378792 + ], + [ + 109.35510601647351, + -0.022107386524621023 + ], + [ + 109.35513638021462, + -0.022103990381324892 + ], + [ + 109.35515156219734, + -0.02209210286366813 + ], + [ + 109.35515307764787, + -0.022063115264672608 + ], + [ + 109.35520970376093, + -0.022057687580503676 + ], + [ + 109.35532691352904, + -0.0221019797760023 + ], + [ + 109.35540957015763, + -0.022113868386208848 + ], + [ + 109.35540129929805, + -0.02203984678990159 + ], + [ + 109.35539438979438, + -0.021978008920160535 + ], + [ + 109.3554652385218, + -0.02197121674062034 + ], + [ + 109.3554760255163, + -0.02203303745152784 + ], + [ + 109.35548716648916, + -0.022096886838946752 + ], + [ + 109.3554939136623, + -0.022125757016188744 + ], + [ + 109.35550403441198, + -0.02216991140302209 + ], + [ + 109.3555124683446, + -0.022208971047427778 + ], + [ + 109.35552259023727, + -0.02214953271469325 + ], + [ + 109.35553608553757, + -0.022120662762903785 + ], + [ + 109.35555970192495, + -0.022105378854276807 + ], + [ + 109.35560524792051, + -0.02206631981015308 + ], + [ + 109.35561035701484, + -0.022021318358579952 + ], + [ + 109.35561199606381, + -0.02200688143644948 + ], + [ + 109.35560862268991, + -0.021972916571179172 + ], + [ + 109.35563055244516, + -0.02193555550316196 + ], + [ + 109.35569802743494, + -0.021928763283899734 + ], + [ + 109.35575032019959, + -0.021955935726524763 + ], + [ + 109.35581104771985, + -0.021947445191174315 + ], + [ + 109.35584815931587, + -0.021911782528466206 + ], + [ + 109.35586840219952, + -0.021874421436646786 + ], + [ + 109.35587683710034, + -0.021825172522667687 + ], + [ + 109.35587683733998, + -0.021803095381583086 + ], + [ + 109.35592406974959, + -0.021806492384289496 + ], + [ + 109.35592069559763, + -0.021843853663678504 + ], + [ + 109.35593756410734, + -0.021864232749561868 + ], + [ + 109.35594768508717, + -0.021888008244742033 + ], + [ + 109.35596286663062, + -0.02191687852115135 + ], + [ + 109.3559982907634, + -0.021935559571638855 + ], + [ + 109.35603708892096, + -0.021928767033827356 + ], + [ + 109.35605733197319, + -0.02187612176260644 + ], + [ + 109.35607926169476, + -0.021842157168612 + ], + [ + 109.35610625159286, + -0.02184895043318306 + ], + [ + 109.3561214333785, + -0.021855743567811926 + ], + [ + 109.35613830216691, + -0.021850649028158656 + ], + [ + 109.35614673695753, + -0.02181158955850124 + ], + [ + 109.35615348480057, + -0.02177932303737416 + ], + [ + 109.35616698013636, + -0.021747056590071333 + ], + [ + 109.35618047545353, + -0.02171648838418016 + ], + [ + 109.356205778572, + -0.02171479041932045 + ], + [ + 109.35623445532734, + -0.02172328194297678 + ], + [ + 109.35627325341554, + -0.021723282367952297 + ], + [ + 109.35631373859766, + -0.02170290390741168 + ], + [ + 109.35633904193772, + -0.021680827038083605 + ], + [ + 109.35637277951716, + -0.021670637954612982 + ], + [ + 109.35641832479048, + -0.021699508567682945 + ], + [ + 109.35655158758372, + -0.021719888932005535 + ], + [ + 109.35660725415777, + -0.021743664933004173 + ], + [ + 109.35662749636664, + -0.02176913878886774 + ], + [ + 109.35663928908248, + -0.021792883820426913 + ], + [ + 109.35665111208313, + -0.021816689831985255 + ], + [ + 109.35665617228007, + -0.021855749460315956 + ], + [ + 109.35666629306064, + -0.021898205629253906 + ], + [ + 109.35667472711465, + -0.021927075841545553 + ], + [ + 109.35668990868197, + -0.021954247886440847 + ], + [ + 109.3567067771043, + -0.021983118192778407 + ], + [ + 109.35671183740962, + -0.02201198836858258 + ], + [ + 109.35670340276177, + -0.022037461909912983 + ], + [ + 109.35670171555267, + -0.022068030253100144 + ], + [ + 109.35671183642232, + -0.022101995212438563 + ], + [ + 109.35673039184721, + -0.02211897784265398 + ], + [ + 109.3567438865199, + -0.022147848113344512 + ], + [ + 109.35674725996793, + -0.02217502002884512 + ], + [ + 109.35675232109456, + -0.0221291675414294 + ], + [ + 109.35675400851007, + -0.022079918531436565 + ], + [ + 109.35677087780705, + -0.0220289714475471 + ], + [ + 109.35678943386544, + -0.021988213835902864 + ], + [ + 109.35685184903312, + -0.02191179361715038 + ], + [ + 109.35690582907262, + -0.021905001243314518 + ], + [ + 109.35696295861491, + -0.021889476890299384 + ], + [ + 109.3570475266186, + -0.02189311510931463 + ], + [ + 109.35705576463286, + -0.021915231936590517 + ], + [ + 109.35706776851531, + -0.021947459097166985 + ], + [ + 109.35712174833837, + -0.021961045635766334 + ], + [ + 109.35717404134438, + -0.021969537428128266 + ], + [ + 109.35720609206531, + -0.02195934832644031 + ], + [ + 109.35720897336599, + -0.021920677766842613 + ], + [ + 109.3572111534285, + -0.02189141867186808 + ], + [ + 109.35722127517225, + -0.02184556622866807 + ], + [ + 109.3572499524027, + -0.021811601688433904 + ], + [ + 109.35728706402288, + -0.021775938996904796 + ], + [ + 109.35730055918238, + -0.02176065495941853 + ], + [ + 109.35732248838285, + -0.02177593938561787 + ], + [ + 109.3573410435769, + -0.02181499917536621 + ], + [ + 109.35734947763528, + -0.021843869397084253 + ], + [ + 109.35736803293908, + -0.021872739730580872 + ], + [ + 109.35738844364118, + -0.021905617745652146 + ], + [ + 109.35740176983458, + -0.021927083876093698 + ], + [ + 109.35742519094266, + -0.021900430487997766 + ], + [ + 109.35743382091005, + -0.02188462815633717 + ], + [ + 109.35745464126342, + -0.021896273273761896 + ], + [ + 109.35749454801953, + -0.021918593686109013 + ], + [ + 109.3575299722167, + -0.021933878264637177 + ], + [ + 109.35758563896401, + -0.021944068338305374 + ], + [ + 109.35759744747436, + -0.021908405363882694 + ], + [ + 109.35763961954152, + -0.02188972515493048 + ], + [ + 109.35766998345038, + -0.021874441301369625 + ], + [ + 109.35769528673558, + -0.021859157391605648 + ], + [ + 109.35771215574, + -0.02183538217264724 + ], + [ + 109.35772227722732, + -0.021813305122379095 + ], + [ + 109.35772227756785, + -0.02178186811339726 + ], + [ + 109.35772227807277, + -0.021735185935127282 + ], + [ + 109.35770540950992, + -0.021718203318524084 + ], + [ + 109.35770034965466, + -0.021646877049444256 + ], + [ + 109.35770035005639, + -0.02160951569945272 + ], + [ + 109.35802254304357, + -0.021618010423977832 + ], + [ + 109.35810351325887, + -0.02159763238330132 + ], + [ + 109.3581642407586, + -0.02159763304387673 + ], + [ + 109.35821316755613, + -0.021605652096165474 + ], + [ + 109.3583093117596, + -0.021621410033642773 + ], + [ + 109.35837678682093, + -0.021616316037067415 + ], + [ + 109.3584442617203, + -0.021626506234473087 + ], + [ + 109.35849824156493, + -0.021641791017105127 + ], + [ + 109.35854884815285, + -0.021611223178220947 + ], + [ + 109.35855991479193, + -0.021596847729105143 + ], + [ + 109.35860029786207, + -0.021610162335581017 + ], + [ + 109.35871922256666, + -0.02161143731260865 + ], + [ + 109.35876603315666, + -0.021630543068006165 + ], + [ + 109.35881663913548, + -0.021657290964235673 + ], + [ + 109.3588697755385, + -0.02167384942439538 + ], + [ + 109.35892417729133, + -0.021672576334777094 + ], + [ + 109.35897984423013, + -0.02166875589249179 + ], + [ + 109.35900894297322, + -0.02165601937764128 + ], + [ + 109.35902159471912, + -0.021639461633517387 + ], + [ + 109.35902792074957, + -0.021616535404099604 + ], + [ + 109.35902962133255, + -0.021592570432149964 + ], + [ + 109.35906666709522, + -0.02159154158140557 + ], + [ + 109.35907852693994, + -0.02162417805440521 + ], + [ + 109.35909244344629, + -0.021644557138328604 + ], + [ + 109.359129132872, + -0.021656020687818068 + ], + [ + 109.35919618620126, + -0.021654747735380307 + ], + [ + 109.35928727844464, + -0.021566864573214224 + ], + [ + 109.35929853469428, + -0.021533436032004947 + ], + [ + 109.3593125823877, + -0.021491717526050405 + ], + [ + 109.35930625718632, + -0.02143694907068606 + ], + [ + 109.35930625757926, + -0.021400012251617525 + ], + [ + 109.35932143957129, + -0.02138982294767738 + ], + [ + 109.3593518032719, + -0.021396191691781294 + ], + [ + 109.35941379599352, + -0.02139364499238522 + ], + [ + 109.35945807670734, + -0.021373266532899334 + ], + [ + 109.35951325398607, + -0.02136797673718414 + ], + [ + 109.3595377816862, + -0.021365625288765743 + ], + [ + 109.3595643501349, + -0.02135161505503027 + ], + [ + 109.35958332762712, + -0.021338878422978123 + ], + [ + 109.3595860807311, + -0.021311857591137472 + ], + [ + 109.35958838878152, + -0.021289204817004134 + ], + [ + 109.35959851048264, + -0.02124717336644931 + ], + [ + 109.35961622293831, + -0.02122297356716902 + ], + [ + 109.35966050354746, + -0.02121278457085517 + ], + [ + 109.35968707195617, + -0.021202595385177974 + ], + [ + 109.35973854573473, + -0.021194370442924968 + ], + [ + 109.35973929605727, + -0.021194250549615684 + ], + [ + 109.35976677699723, + -0.021189859398388713 + ], + [ + 109.3597794285164, + -0.02119495426815221 + ], + [ + 109.35979331976182, + -0.02121109099566886 + ], + [ + 109.35981232220368, + -0.021233165131178667 + ], + [ + 109.35985382410382, + -0.021229951607962632 + ], + [ + 109.35989455749723, + -0.021226797590718428 + ], + [ + 109.35992871674611, + -0.0212267979555168 + ], + [ + 109.35996414115324, + -0.02122679833381821 + ], + [ + 109.36000715645166, + -0.021231893528650497 + ], + [ + 109.3600387851998, + -0.02124972544086323 + ], + [ + 109.36006788364645, + -0.021266283642720412 + ], + [ + 109.36009571686888, + -0.021289210251095533 + ], + [ + 109.36011975460558, + -0.02131341050336229 + ], + [ + 109.36013797164047, + -0.02132420451078047 + ], + [ + 109.36015391357519, + -0.021340158232731967 + ], + [ + 109.36023488371217, + -0.021335064365897043 + ], + [ + 109.36024374004462, + -0.021313411832629154 + ], + [ + 109.36024505004862, + -0.02129297330839595 + ], + [ + 109.360246270777, + -0.02127392765514671 + ], + [ + 109.36022476347426, + -0.02123826427281713 + ], + [ + 109.3601868091367, + -0.02120132703190478 + ], + [ + 109.36015138495354, + -0.021179674026805477 + ], + [ + 109.36012987735593, + -0.021172031693963356 + ], + [ + 109.36006282367414, + -0.021203873077367198 + ], + [ + 109.36002360363268, + -0.0212191568655351 + ], + [ + 109.35997299736236, + -0.021216608957646564 + ], + [ + 109.3599578158621, + -0.021179671964383712 + ], + [ + 109.35995022519874, + -0.021152924523056064 + ], + [ + 109.35995022551954, + -0.021122356111135065 + ], + [ + 109.35995781661079, + -0.021108345669640274 + ], + [ + 109.36015372937712, + -0.021032728661524714 + ], + [ + 109.36037892708269, + -0.021065846830725845 + ], + [ + 109.36034729780835, + -0.021097688597704615 + ], + [ + 109.36018535703518, + -0.021155002658308084 + ], + [ + 109.36024861452523, + -0.021191940168016708 + ], + [ + 109.36031187243242, + -0.021189393474221385 + ], + [ + 109.36035994838224, + -0.021193215038885906 + ], + [ + 109.36040168006778, + -0.021210939769237287 + ], + [ + 109.36040300678245, + -0.021210210076276236 + ], + [ + 109.36065867078148, + -0.021153395854905922 + ], + [ + 109.36072351074678, + -0.021142280433045484 + ], + [ + 109.36079386749175, + -0.021076315880293184 + ], + [ + 109.36090014081401, + -0.02107122226876771 + ], + [ + 109.36090723300279, + -0.02111078518918942 + ], + [ + 109.36092037923925, + -0.021460120802333813 + ], + [ + 109.36090013645787, + -0.021483896026784507 + ], + [ + 109.3609035098684, + -0.02151616273569497 + ], + [ + 109.36093218643879, + -0.021548429718912136 + ], + [ + 109.36094399414411, + -0.02158918774976487 + ], + [ + 109.3609473674438, + -0.021631643935302557 + ], + [ + 109.36095580148415, + -0.021663910700417677 + ], + [ + 109.36095411415009, + -0.021706366831120313 + ], + [ + 109.36098110438941, + -0.021687686419796123 + ], + [ + 109.36102573416646, + -0.021649074522554847 + ], + [ + 109.36107802778693, + -0.021610015432150676 + ], + [ + 109.36114719007435, + -0.021581145999317333 + ], + [ + 109.36119779645581, + -0.021576051809865018 + ], + [ + 109.36122141263321, + -0.021586241543177433 + ], + [ + 109.36124671561552, + -0.02160322427987839 + ], + [ + 109.36126527117851, + -0.021611715712539767 + ], + [ + 109.36127707968583, + -0.021577750916019753 + ], + [ + 109.3612838278136, + -0.021520010616883953 + ], + [ + 109.36128382862785, + -0.021443589535849492 + ], + [ + 109.36126864716208, + -0.02140283146262517 + ], + [ + 109.36122816245923, + -0.02136886610308894 + ], + [ + 109.36115056636045, + -0.02134339157753138 + ], + [ + 109.36112020319328, + -0.021283952638041278 + ], + [ + 109.36112357741403, + -0.021239798275298608 + ], + [ + 109.36112961890633, + -0.0212118817527444 + ], + [ + 109.3612410165569, + -0.02123861718965969 + ], + [ + 109.36163871611092, + -0.02122441363511912 + ], + [ + 109.3618517694434, + -0.021196006524772066 + ], + [ + 109.36197960144342, + -0.02111788696982273 + ], + [ + 109.36199380499916, + -0.021053970970173735 + ], + [ + 109.36207192455434, + -0.021032665637105902 + ], + [ + 109.36224946899792, + -0.021089479859602845 + ], + [ + 109.3622849778868, + -0.02114629408177217 + ], + [ + 109.36223526544215, + -0.02124571897007033 + ], + [ + 109.36221396010872, + -0.021302533192062857 + ], + [ + 109.36242701344096, + -0.021409059858892753 + ], + [ + 109.36251536237965, + -0.021434302412405137 + ], + [ + 109.36264676864195, + -0.02139598439940943 + ], + [ + 109.36271074576592, + -0.021331577479490868 + ], + [ + 109.3627766131193, + -0.021311930465549758 + ], + [ + 109.3628187063564, + -0.021299374830590728 + ], + [ + 109.36284669681532, + -0.021226916566653396 + ], + [ + 109.36288668226457, + -0.021210815089609834 + ], + [ + 109.36301863356715, + -0.02122289292335681 + ], + [ + 109.36303862582753, + -0.02125912242159696 + ], + [ + 109.36307061405458, + -0.021259122763074453 + ], + [ + 109.3630826102326, + -0.021202766224049194 + ], + [ + 109.36311060018541, + -0.021178613664395756 + ], + [ + 109.36320256672244, + -0.021142385354205005 + ], + [ + 109.36326654322426, + -0.021138360556739896 + ], + [ + 109.36331852393067, + -0.02115446301519294 + ], + [ + 109.36335850922022, + -0.021154463439864782 + ], + [ + 109.36337050522849, + -0.021114208799261163 + ], + [ + 109.3634264848457, + -0.0210940820080561 + ], + [ + 109.36345447459179, + -0.02109005682748434 + ], + [ + 109.36346247190258, + -0.021065904050240086 + ], + [ + 109.36363440841292, + -0.021090058732449807 + ], + [ + 109.36375436371301, + -0.021146416688394745 + ], + [ + 109.36376635849992, + -0.02122290089014747 + ], + [ + 109.3638063433749, + -0.02126315609290306 + ], + [ + 109.36381833815776, + -0.02133964029728148 + ], + [ + 109.36385032635475, + -0.021343666117663897 + ], + [ + 109.3638503275852, + -0.021226927262361608 + ], + [ + 109.36389431166909, + -0.021202774863782293 + ], + [ + 109.3639063077238, + -0.021158494734713432 + ], + [ + 109.36393429768744, + -0.021134342164250423 + ], + [ + 109.36397028433258, + -0.021146418980017075 + ], + [ + 109.36401026904423, + -0.02120277609768044 + ], + [ + 109.36406624817302, + -0.021230955040785017 + ], + [ + 109.364134222847, + -0.02126366277653702 + ], + [ + 109.36421219457517, + -0.02122743430196116 + ], + [ + 109.36425717864799, + -0.021170071711186723 + ], + [ + 109.3642871678821, + -0.021145919157816367 + ], + [ + 109.36432015563548, + -0.021157995943977874 + ], + [ + 109.36435539256588, + -0.021169789712936073 + ], + [ + 109.36440599902761, + -0.021164695503755437 + ], + [ + 109.3644178070652, + -0.021176583371566908 + ], + [ + 109.36442961501376, + -0.021196962484179932 + ], + [ + 109.36445829190801, + -0.0212037557849484 + ], + [ + 109.3644701002844, + -0.02118337692301383 + ], + [ + 109.36448190882045, + -0.021147713820078535 + ], + [ + 109.36449034350666, + -0.021120541925888536 + ], + [ + 109.3644717880709, + -0.02109676624376228 + ], + [ + 109.36447178840773, + -0.021064499513449193 + ], + [ + 109.36449034456935, + -0.021018646987095654 + ], + [ + 109.36453420368902, + -0.02099657021235651 + ], + [ + 109.36456962805441, + -0.021008458328781687 + ], + [ + 109.36457974898352, + -0.021042423416609172 + ], + [ + 109.36459493064208, + -0.02106789731293977 + ], + [ + 109.36461517320777, + -0.021067897526884486 + ], + [ + 109.36465228466717, + -0.02105940667351613 + ], + [ + 109.36467421425539, + -0.02104582091209564 + ], + [ + 109.36470457810512, + -0.021045821232659898 + ], + [ + 109.36471301234863, + -0.021061105564234982 + ], + [ + 109.36470963839272, + -0.02107978626937041 + ], + [ + 109.36472819390164, + -0.02109676895745066 + ], + [ + 109.36477880044359, + -0.021084881748438523 + ], + [ + 109.36480579074616, + -0.02106450304289517 + ], + [ + 109.3648496498203, + -0.021047521013436023 + ], + [ + 109.36487157907447, + -0.021066201987329898 + ], + [ + 109.36488844765124, + -0.021088279406707593 + ], + [ + 109.36493568027615, + -0.02109167640501119 + ], + [ + 109.36495929660676, + -0.021091676654842774 + ], + [ + 109.36499134712881, + -0.02111205598677421 + ], + [ + 109.36502845838233, + -0.021123944125811603 + ], + [ + 109.36506050918948, + -0.021117151467505035 + ], + [ + 109.3650976206573, + -0.02110866061304995 + ], + [ + 109.36514991396535, + -0.021108661166634252 + ], + [ + 109.365200520181, + -0.0211290406973283 + ], + [ + 109.36524100536022, + -0.02112564462710155 + ], + [ + 109.36526799561626, + -0.021110360666211064 + ], + [ + 109.36531016762481, + -0.021112059362340087 + ], + [ + 109.36535065248668, + -0.021139231786288525 + ], + [ + 109.365384389949, + -0.02115451639161409 + ], + [ + 109.3654029454105, + -0.021176593835306026 + ], + [ + 109.36541137947788, + -0.02120886067040128 + ], + [ + 109.36541981363403, + -0.02123263625689009 + ], + [ + 109.36542150026526, + -0.021256411771679345 + ], + [ + 109.36540969193649, + -0.021271695893694396 + ], + [ + 109.36541306532315, + -0.02130735917476468 + ], + [ + 109.36542149988995, + -0.021292075016915893 + ], + [ + 109.36543162142729, + -0.021268299628038445 + ], + [ + 109.36545017726317, + -0.021254713827522805 + ], + [ + 109.36546704611082, + -0.021251317507682062 + ], + [ + 109.36553114804337, + -0.02120886194403572 + ], + [ + 109.36555307744628, + -0.02121395692687714 + ], + [ + 109.36556825909241, + -0.021241129086687113 + ], + [ + 109.3655969356637, + -0.021280189140345438 + ], + [ + 109.36561549096457, + -0.02131755083694707 + ], + [ + 109.36563235961653, + -0.021332835266811363 + ], + [ + 109.36581791644731, + -0.021344725002043152 + ], + [ + 109.36595624071686, + -0.021346424732263315 + ], + [ + 109.36605070607169, + -0.021348123993336337 + ], + [ + 109.36610768514471, + -0.021352181534421668 + ], + [ + 109.36618565901014, + -0.02111971075525187 + ], + [ + 109.36663549411452, + -0.021128772853667994 + ], + [ + 109.3666204985631, + -0.02122840340814782 + ], + [ + 109.36644056447263, + -0.021228401494184253 + ], + [ + 109.36649154541611, + -0.021264631384439937 + ], + [ + 109.36655752096321, + -0.02129180409926664 + ], + [ + 109.36669247141043, + -0.02130388198946272 + ], + [ + 109.36677044288948, + -0.021300863708954194 + ], + [ + 109.36683042057776, + -0.02133407459024587 + ], + [ + 109.3669443786326, + -0.02135520959924142 + ], + [ + 109.36698336404189, + -0.0213854011470148 + ], + [ + 109.3670133529362, + -0.02139747792077014 + ], + [ + 109.36705233901665, + -0.021364268093713064 + ], + [ + 109.36712431273672, + -0.021358230637369444 + ], + [ + 109.36717229498453, + -0.02137634583082008 + ], + [ + 109.36722327632728, + -0.021376346376666047 + ], + [ + 109.36728325453872, + -0.02136125145120789 + ], + [ + 109.36732523927171, + -0.021352194559612604 + ], + [ + 109.36737921961668, + -0.021343137795845922 + ], + [ + 109.36741220735642, + -0.021361252830775525 + ], + [ + 109.36746918622411, + -0.021388425464565538 + ], + [ + 109.36750517331464, + -0.021364272939130437 + ], + [ + 109.36755015695543, + -0.02135521607856732 + ], + [ + 109.3675741480843, + -0.021364273677056338 + ], + [ + 109.36764912012039, + -0.02141559941843943 + ], + [ + 109.36769710241221, + -0.021430695503723726 + ], + [ + 109.36775408138332, + -0.02144881080051727 + ], + [ + 109.36779306677501, + -0.021482021476455865 + ], + [ + 109.36780806109978, + -0.02150013632366671 + ], + [ + 109.36780506174703, + -0.021542403891754203 + ], + [ + 109.3678110591679, + -0.02157863332833405 + ], + [ + 109.36784104855627, + -0.021545423394276685 + ], + [ + 109.3678500462609, + -0.021451830945515973 + ], + [ + 109.36787403796832, + -0.021406544486915004 + ], + [ + 109.36794901058995, + -0.021403526175891023 + ], + [ + 109.36798499765723, + -0.021382392759387796 + ], + [ + 109.36800299193887, + -0.021300876857467087 + ], + [ + 109.36807196669372, + -0.021303896707686204 + ], + [ + 109.36808996056114, + -0.021261629293164115 + ], + [ + 109.36826689557905, + -0.02129182232639245 + ], + [ + 109.36838085381183, + -0.021303900001586176 + ], + [ + 109.36867174659433, + -0.02139447657174842 + ], + [ + 109.3685967722487, + -0.021557508006576534 + ], + [ + 109.36860876735246, + -0.02160581398439856 + ], + [ + 109.36868973739239, + -0.021642044247713692 + ], + [ + 109.36879769783773, + -0.021654121880131025 + ], + [ + 109.36887566901187, + -0.021687333000191194 + ], + [ + 109.36891465432477, + -0.021729601047061377 + ], + [ + 109.36896563522585, + -0.021774888342347688 + ], + [ + 109.36901361722184, + -0.021820175607221565 + ], + [ + 109.36904960375986, + -0.02185036716202551 + ], + [ + 109.36915756445127, + -0.021841310993112904 + ], + [ + 109.36924453277678, + -0.021835273710374737 + ], + [ + 109.36934049746505, + -0.021862446808257906 + ], + [ + 109.36940047558053, + -0.02186244746415899 + ], + [ + 109.36946945048123, + -0.021856409984694763 + ], + [ + 109.36950843700646, + -0.02178697072253969 + ], + [ + 109.36957741139054, + -0.021829239112015023 + ], + [ + 109.36953542585852, + -0.021907735693750742 + ], + [ + 109.369568413531, + -0.021934908107996406 + ], + [ + 109.36963139036386, + -0.021953023501321722 + ], + [ + 109.36970336434197, + -0.021931890471236042 + ], + [ + 109.36974834751167, + -0.0219711394888627 + ], + [ + 109.3698083253777, + -0.0219952930865324 + ], + [ + 109.36988929578776, + -0.022001332212089674 + ], + [ + 109.37005723446421, + -0.02201039141269659 + ], + [ + 109.37010821640472, + -0.021962086089101017 + ], + [ + 109.37016219705635, + -0.02193189550308253 + ], + [ + 109.37025216377717, + -0.021977183259702802 + ], + [ + 109.37043809514138, + -0.02205870149628779 + ], + [ + 109.37053106143685, + -0.022043606929205792 + ], + [ + 109.37076197654105, + -0.02211606832405429 + ], + [ + 109.37071999060983, + -0.022227775252422016 + ], + [ + 109.37074698068102, + -0.022236832908565738 + ], + [ + 109.37085794199012, + -0.022079839957647854 + ], + [ + 109.37089692736704, + -0.02211908893455564 + ], + [ + 109.37092091817051, + -0.022161356866039615 + ], + [ + 109.37096890030472, + -0.02219758682649846 + ], + [ + 109.37100188809669, + -0.022215701907282302 + ], + [ + 109.37109485392341, + -0.022245894132040454 + ], + [ + 109.37121181088955, + -0.02228816310449479 + ], + [ + 109.37129877884112, + -0.022324393508032322 + ], + [ + 109.3713617553924, + -0.022372700125369512 + ], + [ + 109.37140973750027, + -0.022411949218492808 + ], + [ + 109.3714787119043, + -0.022457236788660274 + ], + [ + 109.37157167764941, + -0.022496486391597077 + ], + [ + 109.37162265897076, + -0.02250856344476786 + ], + [ + 109.37170162935813, + -0.0225885710178791 + ], + [ + 109.37174561363028, + -0.022564418552008764 + ], + [ + 109.37180959040914, + -0.022560393779219177 + ], + [ + 109.37186157114876, + -0.02259259831627308 + ], + [ + 109.37194554021386, + -0.022628828710479762 + ], + [ + 109.37208548884202, + -0.022673110730036507 + ], + [ + 109.37218145346513, + -0.022717392256710475 + ], + [ + 109.37224542980728, + -0.022753622434623536 + ], + [ + 109.37230540773962, + -0.022777776084902716 + ], + [ + 109.37239737427605, + -0.02278180262575633 + ], + [ + 109.3724613508502, + -0.02279790533395757 + ], + [ + 109.37248934018345, + -0.022842186099502768 + ], + [ + 109.3725293249311, + -0.022906594479616908 + ], + [ + 109.37260529642302, + -0.022987105256563164 + ], + [ + 109.37273324871495, + -0.023095795107919457 + ], + [ + 109.37279322766449, + -0.023031387866767623 + ], + [ + 109.37297316164313, + -0.02309177237745978 + ], + [ + 109.37304913543146, + -0.022971008363941024 + ], + [ + 109.37341700032225, + -0.023111904977240468 + ], + [ + 109.37342899541957, + -0.02316021108035961 + ], + [ + 109.37348497455764, + -0.023208517693349695 + ], + [ + 109.37352895832437, + -0.023232671186303358 + ], + [ + 109.37354895042611, + -0.02328902838032714 + ], + [ + 109.37356894275779, + -0.023325258088735184 + ], + [ + 109.37360492947215, + -0.02334538599463705 + ], + [ + 109.37364891388746, + -0.023313182526843137 + ], + [ + 109.37392081426286, + -0.023401746646182046 + ], + [ + 109.37403677203578, + -0.023417849992849238 + ], + [ + 109.37417772035396, + -0.0234688937029321 + ], + [ + 109.3741827798009, + -0.02357418570939267 + ], + [ + 109.37442062954415, + -0.023708350841352265 + ], + [ + 109.37455895525952, + -0.02363702566028095 + ], + [ + 109.37463486516253, + -0.023645517842894256 + ], + [ + 109.37479005794377, + -0.02373213081657419 + ], + [ + 109.37482548217841, + -0.023769492906358932 + ], + [ + 109.37480861257157, + -0.023830629985190126 + ], + [ + 109.37492332033652, + -0.023890070372333386 + ], + [ + 109.37495031114229, + -0.02384082121575439 + ], + [ + 109.37497392763125, + -0.02383742498062966 + ], + [ + 109.37501130292053, + -0.0238277998969099 + ], + [ + 109.37507575037313, + -0.02386383139697635 + ], + [ + 109.3751965961863, + -0.023908754465333717 + ], + [ + 109.37518426440694, + -0.023924499793842584 + ], + [ + 109.37512743268037, + -0.02399706305780226 + ], + [ + 109.37528431235476, + -0.02408537435774482 + ], + [ + 109.37533604891928, + -0.02400936000470138 + ], + [ + 109.3753933255925, + -0.024041382447655947 + ], + [ + 109.37571808000766, + -0.024174779133922575 + ], + [ + 109.37565711238524, + -0.024302755915320873 + ], + [ + 109.37570771879638, + -0.02432653214715633 + ], + [ + 109.37573808405054, + -0.024222938744910574 + ], + [ + 109.37580899427243, + -0.024212123239160617 + ], + [ + 109.37584941898886, + -0.024205957502342133 + ], + [ + 109.37587809648232, + -0.024175389192508043 + ], + [ + 109.37602316809874, + -0.024253510843189004 + ], + [ + 109.37605521901159, + -0.02425351123042974 + ], + [ + 109.37613450246582, + -0.024285779106752173 + ], + [ + 109.37614968482234, + -0.024256908889490177 + ], + [ + 109.37598437057606, + -0.02417369221374085 + ], + [ + 109.37602654385616, + -0.024087081524106494 + ], + [ + 109.37631668722622, + -0.024234833540009777 + ], + [ + 109.37630993938124, + -0.024258609084721668 + ], + [ + 109.37633018192427, + -0.024270497142511994 + ], + [ + 109.3762947560084, + -0.02437239225433625 + ], + [ + 109.37631331166004, + -0.024384280292715294 + ], + [ + 109.37630021668242, + -0.0244138986634619 + ], + [ + 109.37628761110803, + -0.024436581442650537 + ], + [ + 109.37626230775281, + -0.024436581134671256 + ], + [ + 109.37627580261034, + -0.024458658665727436 + ], + [ + 109.37630447941966, + -0.024485831159185616 + ], + [ + 109.37635969645902, + -0.02450930293562519 + ], + [ + 109.37570682427238, + -0.025298009748087116 + ], + [ + 109.37555721234862, + -0.025894997366551275 + ], + [ + 109.37554146203829, + -0.027245859466677184 + ], + [ + 109.37608683095894, + -0.03127627066595922 + ], + [ + 109.37603100097476, + -0.031292563477298016 + ], + [ + 109.37601115314479, + -0.031438855766620324 + ], + [ + 109.3746781323205, + -0.032802725295651626 + ], + [ + 109.37324964390676, + -0.03424079298360153 + ], + [ + 109.37099353668515, + -0.03618221595337264 + ], + [ + 109.37250173347746, + -0.03774465400129412 + ], + [ + 109.37253665196815, + -0.037749805218706516 + ], + [ + 109.37483412287624, + -0.040154911756452374 + ], + [ + 109.37671963547282, + -0.042266321008518036 + ], + [ + 109.3778891495028, + -0.04367245053361178 + ], + [ + 109.37960856531406, + -0.04576952991995732 + ], + [ + 109.38074144697917, + -0.04715124500861748 + ], + [ + 109.38322543069353, + -0.05092877252349227 + ], + [ + 109.38482115645535, + -0.05338460982500063 + ], + [ + 109.38489690875245, + -0.053611866716971615 + ], + [ + 109.37947968708988, + -0.0578063631310829 + ], + [ + 109.3794795305165, + -0.05780647935329723 + ], + [ + 109.3783088794856, + -0.058675665792127914 + ], + [ + 109.37668742912675, + -0.059851904357629604 + ], + [ + 109.37645019186353, + -0.06002400189977049 + ], + [ + 109.3753080090549, + -0.06085256841306111 + ], + [ + 109.37485887350392, + -0.061186364187442166 + ], + [ + 109.37457525052568, + -0.06141915576466081 + ], + [ + 109.37369333219321, + -0.06214301526608477 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "Id": 0, + "Plan_Area": 113026328.1, + "Ket": "Pontianak Selatan", + "Penduduk": 94314 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 109.35425171375785, + -0.04207834350964987 + ], + [ + 109.35334707526704, + -0.043105218849610556 + ], + [ + 109.35175946042841, + -0.044535422834847266 + ], + [ + 109.35193342151885, + -0.04487510170995203 + ], + [ + 109.35087495701242, + -0.04606645834771445 + ], + [ + 109.34954295376907, + -0.04751183325128081 + ], + [ + 109.34858140443534, + -0.04896407822417487 + ], + [ + 109.34727622990728, + -0.05100467478763772 + ], + [ + 109.34548918426678, + -0.053022664678963764 + ], + [ + 109.34177229675167, + -0.057301053193673665 + ], + [ + 109.33822989052756, + -0.06131030726062607 + ], + [ + 109.33501859789894, + -0.06513216064808534 + ], + [ + 109.33243550823754, + -0.06818769289691332 + ], + [ + 109.33040539270077, + -0.07078409119737616 + ], + [ + 109.32774600026455, + -0.07481502560151726 + ], + [ + 109.32463498212687, + -0.07940820627817206 + ], + [ + 109.32393840924082, + -0.08043223156481946 + ], + [ + 109.32389276289446, + -0.08052549670981375 + ], + [ + 109.3221106182342, + -0.07884362761056447 + ], + [ + 109.31702731337046, + -0.0744255305517153 + ], + [ + 109.31687796744671, + -0.07428110893073998 + ], + [ + 109.31684825367195, + -0.07426990288058401 + ], + [ + 109.31504516209638, + -0.07270276618168048 + ], + [ + 109.3128921325343, + -0.0705725784417261 + ], + [ + 109.31094134562541, + -0.06858347944660835 + ], + [ + 109.31011931907972, + -0.06774530889071882 + ], + [ + 109.30680107635439, + -0.06436189820850707 + ], + [ + 109.3048320665492, + -0.06235421838426612 + ], + [ + 109.30473708443193, + -0.06233721052814042 + ], + [ + 109.30470076601497, + -0.06230462738825889 + ], + [ + 109.30592483648198, + -0.06115790978185177 + ], + [ + 109.30699226064493, + -0.06003601996990354 + ], + [ + 109.30803151893961, + -0.05899239587119645 + ], + [ + 109.30886309776761, + -0.05821339794593776 + ], + [ + 109.30922115564408, + -0.05779283901742656 + ], + [ + 109.31014057450321, + -0.05686925418155446 + ], + [ + 109.31095422427445, + -0.05591006277695976 + ], + [ + 109.31203935583139, + -0.0549893416096532 + ], + [ + 109.31413037706432, + -0.05284824873871211 + ], + [ + 109.31642282578291, + -0.050634668632088826 + ], + [ + 109.31720023906404, + -0.04984814625261359 + ], + [ + 109.31792659285628, + -0.04907813170237839 + ], + [ + 109.3189193802165, + -0.048139538454601324 + ], + [ + 109.31959341850713, + -0.04750229392434255 + ], + [ + 109.32091635935765, + -0.04611141557186 + ], + [ + 109.32191463922695, + -0.04515239613305311 + ], + [ + 109.3227098229548, + -0.04438848475261101 + ], + [ + 109.32372239973736, + -0.043415728877689824 + ], + [ + 109.32448646470382, + -0.04261931863073522 + ], + [ + 109.32503242609339, + -0.04205024456135078 + ], + [ + 109.32678273002863, + -0.04053795579041163 + ], + [ + 109.329812240833, + -0.03792040735123325 + ], + [ + 109.33260000690117, + -0.03551172396131128 + ], + [ + 109.33398214420839, + -0.03414347596314283 + ], + [ + 109.33665977712866, + -0.03156383186112857 + ], + [ + 109.33789455090782, + -0.03038760898972202 + ], + [ + 109.33810328408603, + -0.03033352538936671 + ], + [ + 109.33866974466297, + -0.029896030424540483 + ], + [ + 109.33989534931862, + -0.02900890989233597 + ], + [ + 109.34073186993628, + -0.028449083707839516 + ], + [ + 109.34210494955182, + -0.027631289913580114 + ], + [ + 109.34356263152422, + -0.02669498758526942 + ], + [ + 109.34477671544886, + -0.02609238682029518 + ], + [ + 109.3447772889293, + -0.026093985688603088 + ], + [ + 109.3448252710265, + -0.026078890927666052 + ], + [ + 109.34482499924553, + -0.02606842153728127 + ], + [ + 109.34486255383933, + -0.026049781607866525 + ], + [ + 109.34491225410952, + -0.026141171484718558 + ], + [ + 109.34497657227377, + -0.02628073387719494 + ], + [ + 109.34491823293445, + -0.026309474353295764 + ], + [ + 109.34490248884289, + -0.02631173845261811 + ], + [ + 109.34486200518585, + -0.026230222768342717 + ], + [ + 109.34480802510078, + -0.026264186691571746 + ], + [ + 109.34489124170891, + -0.026415896542756792 + ], + [ + 109.34498795237518, + -0.02663100725980172 + ], + [ + 109.34511966740254, + -0.026915906740525842 + ], + [ + 109.3451156682584, + -0.026964211962285694 + ], + [ + 109.34517564586167, + -0.026948111023972612 + ], + [ + 109.34523162366847, + -0.02702862058847765 + ], + [ + 109.3451236638267, + -0.027068873504370587 + ], + [ + 109.34524761347394, + -0.027334554245575453 + ], + [ + 109.34542354288948, + -0.02764451559184486 + ], + [ + 109.34626897794506, + -0.029344829448260588 + ], + [ + 109.34622180452567, + -0.029379127678858995 + ], + [ + 109.34614102474866, + -0.029437859976001925 + ], + [ + 109.34629029647094, + -0.029802835622159907 + ], + [ + 109.34621921158605, + -0.02983861625367011 + ], + [ + 109.34623342684996, + -0.029945961588370557 + ], + [ + 109.34637559200198, + -0.030182123027989515 + ], + [ + 109.34642534964392, + -0.03027515623571814 + ], + [ + 109.34648221778069, + -0.03023221905583069 + ], + [ + 109.34655685452849, + -0.030352983479598288 + ], + [ + 109.3467700998999, + -0.030857508961326833 + ], + [ + 109.34674877417459, + -0.03088434491452481 + ], + [ + 109.34688205161456, + -0.03125468778605199 + ], + [ + 109.3469513322026, + -0.031292218368397934 + ], + [ + 109.34695135745199, + -0.03134593226924811 + ], + [ + 109.34705264888944, + -0.031587460498472955 + ], + [ + 109.3471539435405, + -0.031625032930165164 + ], + [ + 109.34723391083558, + -0.03179141923200582 + ], + [ + 109.34728189005897, + -0.031963172287824956 + ], + [ + 109.34731920816104, + -0.03203831451753001 + ], + [ + 109.34735652772447, + -0.032022213346628484 + ], + [ + 109.34745781901591, + -0.03226910891903399 + ], + [ + 109.3475537809566, + -0.03239255745881312 + ], + [ + 109.3477030528705, + -0.032719962768074505 + ], + [ + 109.34774813052691, + -0.032707586812145105 + ], + [ + 109.34777592599028, + -0.03277103086236109 + ], + [ + 109.34783970722566, + -0.03290578086995675 + ], + [ + 109.3479025822383, + -0.03303418711319799 + ], + [ + 109.34795828535361, + -0.03313276523707584 + ], + [ + 109.34798522285034, + -0.03317346837813125 + ], + [ + 109.34808312910782, + -0.033295562123554265 + ], + [ + 109.34816667597993, + -0.03339684336810289 + ], + [ + 109.34819618800779, + -0.0335042620046357 + ], + [ + 109.34824016987467, + -0.03360087345708448 + ], + [ + 109.3481841901465, + -0.03364515240958023 + ], + [ + 109.34832813216781, + -0.033878630746162194 + ], + [ + 109.34840810058117, + -0.033971217386388926 + ], + [ + 109.34844408734818, + -0.03395511621977578 + ], + [ + 109.3485200571949, + -0.034051728246088105 + ], + [ + 109.34847607301559, + -0.03409198195289902 + ], + [ + 109.34859602456129, + -0.034293256364595716 + ], + [ + 109.3485520400297, + -0.03435363730112218 + ], + [ + 109.348659995429, + -0.034591140548717945 + ], + [ + 109.34862000952613, + -0.034643470657091606 + ], + [ + 109.34865999277865, + -0.03474410753200294 + ], + [ + 109.34859201723371, + -0.03480448803749234 + ], + [ + 109.34863999740682, + -0.034909150502556774 + ], + [ + 109.3488199313076, + -0.034828644739195565 + ], + [ + 109.3487439615558, + -0.03472800722144302 + ], + [ + 109.34882393218527, + -0.0346917796027504 + ], + [ + 109.34892789025342, + -0.034860850223438794 + ], + [ + 109.34887990775859, + -0.03488902750791279 + ], + [ + 109.34888390548633, + -0.03493330749980244 + ], + [ + 109.34897586986926, + -0.03499771628810244 + ], + [ + 109.34891988867743, + -0.03512250416704281 + ], + [ + 109.34887590482056, + -0.035142630621497147 + ], + [ + 109.34888215158182, + -0.03519357780109486 + ], + [ + 109.34883266958384, + -0.03522527731642202 + ], + [ + 109.34891813437011, + -0.03540415966254968 + ], + [ + 109.34895187191749, + -0.035392838696352555 + ], + [ + 109.3489068905877, + -0.0352909437518574 + ], + [ + 109.3490845756288, + -0.03519584572354251 + ], + [ + 109.34911606278563, + -0.03525471845972415 + ], + [ + 109.34903734154537, + -0.035297739026511274 + ], + [ + 109.34906657942186, + -0.03536340466572438 + ], + [ + 109.34899910416419, + -0.03539510385729094 + ], + [ + 109.34903059127373, + -0.03545624090842232 + ], + [ + 109.34912055761922, + -0.03545171389501244 + ], + [ + 109.34915879244011, + -0.0354992651853477 + ], + [ + 109.34923526405149, + -0.035483416358144414 + ], + [ + 109.34928699329248, + -0.03556040398456842 + ], + [ + 109.34937695960953, + -0.035558141289823375 + ], + [ + 109.3494354361616, + -0.03564418630455796 + ], + [ + 109.34937920663118, + -0.03567815001057707 + ], + [ + 109.34937920602539, + -0.03571211473140124 + ], + [ + 109.34939944795336, + -0.03573928687436903 + ], + [ + 109.34942868699471, + -0.035739287403473576 + ], + [ + 109.34945117824172, + -0.035757402328914736 + ], + [ + 109.34946692169379, + -0.03579363165109679 + ], + [ + 109.34946692068067, + -0.03585023952165522 + ], + [ + 109.34946916935095, + -0.03587741134039346 + ], + [ + 109.3494916604754, + -0.035902319212302754 + ], + [ + 109.34950965361057, + -0.03590911248397003 + ], + [ + 109.34952314822861, + -0.03592722724838707 + ], + [ + 109.34952089866472, + -0.03594987035633719 + ], + [ + 109.34953889127107, + -0.03598609972233075 + ], + [ + 109.34968058348043, + -0.036246498536571164 + ], + [ + 109.34985709522988, + -0.036546775139202785 + ], + [ + 109.35011299101598, + -0.03700299752701005 + ], + [ + 109.35031557556127, + -0.03734113914616466 + ], + [ + 109.3503560388122, + -0.03731669861917723 + ], + [ + 109.35039554652818, + -0.03729283525172374 + ], + [ + 109.35058746898291, + -0.0375987731383611 + ], + [ + 109.35056596785829, + -0.037646872989632624 + ], + [ + 109.35053948491984, + -0.03770611757905876 + ], + [ + 109.35050216443962, + -0.037765156810364874 + ], + [ + 109.35055014506617, + -0.037840299475118036 + ], + [ + 109.35067276270216, + -0.03800668713193602 + ], + [ + 109.35080927195249, + -0.037989070668093324 + ], + [ + 109.35088068543124, + -0.03797985478683049 + ], + [ + 109.35094465966952, + -0.038076466853924976 + ], + [ + 109.35096565098604, + -0.03812024404470885 + ], + [ + 109.35097464693196, + -0.0381564732829768 + ], + [ + 109.35099263968529, + -0.038183645429102055 + ], + [ + 109.35103762251792, + -0.03820176083089957 + ], + [ + 109.35106461207981, + -0.03821987588566233 + ], + [ + 109.35105561469483, + -0.0382591238668479 + ], + [ + 109.35110359559953, + -0.038319506573934986 + ], + [ + 109.35108482417398, + -0.03833390442248441 + ], + [ + 109.35121003535708, + -0.03847123400050238 + ], + [ + 109.3513582541071, + -0.03867923399854586 + ], + [ + 109.35150376973384, + -0.03887728088051426 + ], + [ + 109.35165467598556, + -0.039066296496739095 + ], + [ + 109.35181995723912, + -0.039282452757087256 + ], + [ + 109.35200411348485, + -0.03948864025750211 + ], + [ + 109.35218825411265, + -0.03968128087990572 + ], + [ + 109.35236342598019, + -0.03988112462231661 + ], + [ + 109.3525367853514, + -0.040113562124018995 + ], + [ + 109.35271105098032, + -0.04031976525282673 + ], + [ + 109.35289430098642, + -0.040512390252949726 + ], + [ + 109.35308473847796, + -0.04072762462449755 + ], + [ + 109.35327337910327, + -0.04094015587685209 + ], + [ + 109.35347100411673, + -0.04113731213606574 + ], + [ + 109.35366322286178, + -0.04133989024822667 + ], + [ + 109.35382851973594, + -0.04156959337507654 + ], + [ + 109.35398483223223, + -0.04177851525769786 + ], + [ + 109.35415728535484, + -0.04197295274622931 + ], + [ + 109.35425171375785, + -0.04207834350964987 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "Id": 0, + "Plan_Area": 113026328.1, + "Ket": "Pontianak Utara", + "Penduduk": 151735 + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [ + 109.29875831658174, + 0.003910766031328909 + ], + [ + 109.2985550978325, + 0.003814328546525718 + ], + [ + 109.29826155094918, + 0.0038464847839492647 + ], + [ + 109.29803573844467, + 0.003975078537449749 + ], + [ + 109.29753897281945, + 0.003975078541259786 + ], + [ + 109.29713251969933, + 0.004039375417452581 + ], + [ + 109.29688412907375, + 0.004328703547509942 + ], + [ + 109.29672606657333, + 0.004425156657614642 + ], + [ + 109.29663575407761, + 0.004553750406613091 + ], + [ + 109.29668091033275, + 0.0046823441642509485 + ], + [ + 109.29681639469328, + 0.0048752347948509715 + ], + [ + 109.29699703531742, + 0.005003844156955543 + ], + [ + 109.29744864470878, + 0.0051324379179639975 + ], + [ + 109.29771961344711, + 0.0050038441652494586 + ], + [ + 109.29803573844838, + 0.004875250422110328 + ], + [ + 109.2983970353287, + 0.0046823441692222375 + ], + [ + 109.29871316033513, + 0.004553750413511107 + ], + [ + 109.29878089470802, + 0.004264422288841691 + ], + [ + 109.29875831658174, + 0.003910766031328909 + ] + ] + ], + [ + [ + [ + 109.31163578110953, + 0.03960991437806738 + ], + [ + 109.31618184981018, + 0.0380326088085311 + ], + [ + 109.31700383485254, + 0.03774741260338989 + ], + [ + 109.31840476190277, + 0.03726832214311322 + ], + [ + 109.32000373043512, + 0.03670656778541136 + ], + [ + 109.3214900858912, + 0.03619086139032862 + ], + [ + 109.32411466902072, + 0.03528023512848499 + ], + [ + 109.32417392721798, + 0.03525967486738239 + ], + [ + 109.33200929661922, + 0.032541112387531296 + ], + [ + 109.33205998010244, + 0.03252624743904441 + ], + [ + 109.33206351053556, + 0.03252230230179938 + ], + [ + 109.33354650230532, + 0.03200776297068947 + ], + [ + 109.33552522077207, + 0.031474369697340146 + ], + [ + 109.33694178625706, + 0.031094462921202954 + ], + [ + 109.33791823515195, + 0.03083259043448938 + ], + [ + 109.35162086063058, + 0.02715770206170378 + ], + [ + 109.35361619946161, + 0.026533411138184543 + ], + [ + 109.37081451031887, + 0.020775376617603713 + ], + [ + 109.37100818839035, + 0.020714427633885767 + ], + [ + 109.37089003533126, + 0.01929707856859266 + ], + [ + 109.37053162908526, + 0.018669219174479994 + ], + [ + 109.37074417595957, + 0.01815468795093814 + ], + [ + 109.370751877201, + 0.01721894663341345 + ], + [ + 109.37017503530708, + 0.016069703547153903 + ], + [ + 109.37043199079581, + 0.015115066152241632 + ], + [ + 109.37060145721108, + 0.014120125425659016 + ], + [ + 109.37021692914271, + 0.013088060010999015 + ], + [ + 109.36986498845827, + 0.012410281668936677 + ], + [ + 109.36990870720904, + 0.011507031686198646 + ], + [ + 109.36966094157616, + 0.010245141042669378 + ], + [ + 109.36984834780728, + 0.009276172303101975 + ], + [ + 109.37051050408482, + 0.009090437929947797 + ], + [ + 109.37077358668483, + 0.007643837797856844 + ], + [ + 109.37124806658763, + 0.006882922314019161 + ], + [ + 109.37144942831051, + 0.0057723757525926025 + ], + [ + 109.37175396439444, + 0.0040927972981457065 + ], + [ + 109.37207070718024, + 0.0030206566893495085 + ], + [ + 109.3722974194573, + 0.0019767908234275245 + ], + [ + 109.37265738188052, + 0.0010571118625172004 + ], + [ + 109.37307051253114, + 0.0002832870030314429 + ], + [ + 109.3731583321806, + -0.0003316870821545239 + ], + [ + 109.37335031655675, + -0.0006566089690434833 + ], + [ + 109.37422616031161, + -0.0014584839405611862 + ], + [ + 109.37513422170801, + -0.002090468243796129 + ], + [ + 109.37574428533425, + -0.00225257771632418 + ], + [ + 109.37611080095589, + -0.002583358948696445 + ], + [ + 109.37608820719555, + -0.0027913277056123217 + ], + [ + 109.37579000408698, + -0.002911499593498449 + ], + [ + 109.3758064572012, + -0.0031947808488847285 + ], + [ + 109.375999910323, + -0.003486530837620033 + ], + [ + 109.37596414468757, + -0.003773265221677468 + ], + [ + 109.37577381657712, + -0.004099983947207114 + ], + [ + 109.37613606658422, + -0.0047642183392838456 + ], + [ + 109.37641734782711, + -0.005331765203261899 + ], + [ + 109.3765106603148, + -0.005798983978125225 + ], + [ + 109.3769493947142, + -0.0062504527121922055 + ], + [ + 109.3778288790618, + -0.008934577725035354 + ], + [ + 109.37796521235072, + -0.010700618640950196 + ], + [ + 109.37794776865229, + -0.010692891575805791 + ], + [ + 109.37789978611589, + -0.010662700025218554 + ], + [ + 109.37787579500558, + -0.010617412955547035 + ], + [ + 109.3778548028598, + -0.010563068514308906 + ], + [ + 109.37783316375624, + -0.010513812290934952 + ], + [ + 109.37780682010468, + -0.010575144779685118 + ], + [ + 109.37776183634725, + -0.010572125413633001 + ], + [ + 109.37764787794353, + -0.010478531810409574 + ], + [ + 109.37752492280403, + -0.010381919042303334 + ], + [ + 109.37729034080078, + -0.01038208555945678 + ], + [ + 109.37717304975511, + -0.010392819632192983 + ], + [ + 109.37702376977117, + -0.010500165640728148 + ], + [ + 109.37681051239501, + -0.010704123387853464 + ], + [ + 109.37674120412608, + -0.010698755680146221 + ], + [ + 109.37671987860944, + -0.010677286213647922 + ], + [ + 109.37670388480826, + -0.010596776055738829 + ], + [ + 109.3767038854749, + -0.010467959939213985 + ], + [ + 109.37665057217019, + -0.010317674195952035 + ], + [ + 109.37650662536629, + -0.01008151059922676 + ], + [ + 109.37641066109937, + -0.009866816621159317 + ], + [ + 109.37632535890864, + -0.009802408155814643 + ], + [ + 109.37619207398866, + -0.009754101473660855 + ], + [ + 109.37610144039805, + -0.009689692994327793 + ], + [ + 109.37605878938267, + -0.009641386760287439 + ], + [ + 109.37606945254849, + -0.009566244100533085 + ], + [ + 109.37605878996388, + -0.009517938021551415 + ], + [ + 109.37606412161985, + -0.009464264682024718 + ], + [ + 109.3761440928793, + -0.009426693702235324 + ], + [ + 109.37625605304538, + -0.009287143466130247 + ], + [ + 109.37629870473306, + -0.009190531595455924 + ], + [ + 109.37636268244802, + -0.009002675083619694 + ], + [ + 109.37628804228275, + -0.009110021490507549 + ], + [ + 109.37620273925401, + -0.009228102514148058 + ], + [ + 109.37609077929217, + -0.009324714057412165 + ], + [ + 109.37598415083657, + -0.009399856269829627 + ], + [ + 109.37593083656002, + -0.009448162045275526 + ], + [ + 109.3758935163735, + -0.009523304573923112 + ], + [ + 109.375802882262, + -0.009571610165906774 + ], + [ + 109.37576556199684, + -0.009662854694547699 + ], + [ + 109.37580288116288, + -0.009802405604524144 + ], + [ + 109.37587218890742, + -0.009909752662334147 + ], + [ + 109.37599481109667, + -0.00993658994918734 + ], + [ + 109.37616008459175, + -0.00995269277745946 + ], + [ + 109.37622613432018, + -0.010047739696064689 + ], + [ + 109.37629011059329, + -0.010168505100648848 + ], + [ + 109.37635008826588, + -0.010297321498281511 + ], + [ + 109.37645005152251, + -0.010418087104901776 + ], + [ + 109.3764700438213, + -0.010510673781663534 + ], + [ + 109.37647004321501, + -0.010627413373638463 + ], + [ + 109.37651802579141, + -0.010643515640704137 + ], + [ + 109.37655801140582, + -0.010631439342586877 + ], + [ + 109.37660599417349, + -0.010611312079425114 + ], + [ + 109.37660999249744, + -0.010655592638557655 + ], + [ + 109.37664198051675, + -0.010736102878601265 + ], + [ + 109.37668596439195, + -0.010780383653461017 + ], + [ + 109.37673794539951, + -0.010820638969610908 + ], + [ + 109.37676993382263, + -0.010824664645743137 + ], + [ + 109.37682591370863, + -0.01080453742801297 + ], + [ + 109.37686589918059, + -0.010820639659005652 + ], + [ + 109.37696186437176, + -0.010848818705090689 + ], + [ + 109.37700584868078, + -0.0108125894044664 + ], + [ + 109.37710581290466, + -0.010752207375847114 + ], + [ + 109.37722177161066, + -0.010643519370153644 + ], + [ + 109.37736971831146, + -0.010623392629111305 + ], + [ + 109.37747368047467, + -0.010683775756555111 + ], + [ + 109.3775216630323, + -0.010707929043442123 + ], + [ + 109.37764161940895, + -0.010772337771046955 + ], + [ + 109.37778556705054, + -0.010852848658891185 + ], + [ + 109.37783354944142, + -0.010909206000707867 + ], + [ + 109.37785754044185, + -0.010973614226216675 + ], + [ + 109.37782555169743, + -0.011025945628110037 + ], + [ + 109.37773758323347, + -0.011062174696839107 + ], + [ + 109.37760563057657, + -0.011110480035840479 + ], + [ + 109.37748167466502, + -0.011227219001197837 + ], + [ + 109.37741769676943, + -0.011404340866920737 + ], + [ + 109.37738970614588, + -0.011533156868555465 + ], + [ + 109.37734972005137, + -0.011625743252553812 + ], + [ + 109.37723775893078, + -0.011891425915619681 + ], + [ + 109.3772097679692, + -0.012072573459381737 + ], + [ + 109.37719777155597, + -0.012197364030908738 + ], + [ + 109.37725375059591, + -0.012322155017881479 + ], + [ + 109.37734571671405, + -0.012434869719944822 + ], + [ + 109.3775176545885, + -0.012446947299865091 + ], + [ + 109.37766560155048, + -0.01239059113843397 + ], + [ + 109.37782554409733, + -0.012350337065700887 + ], + [ + 109.3780454649301, + -0.012326185379209261 + ], + [ + 109.37819341175761, + -0.01229800774008233 + ], + [ + 109.37826138708205, + -0.012326186703324821 + ], + [ + 109.37830537066472, + -0.012418773630593608 + ], + [ + 109.3783173654771, + -0.01255966644525324 + ], + [ + 109.37828137767602, + -0.01268445693131251 + ], + [ + 109.37811268661513, + -0.012982217548086693 + ], + [ + 109.37796423927338, + -0.013118077422357108 + ], + [ + 109.37786752368604, + -0.013190535898604843 + ], + [ + 109.37773257186427, + -0.013267522809337108 + ], + [ + 109.37760661669853, + -0.01336036019711897 + ], + [ + 109.37751214994873, + -0.013487162984775274 + ], + [ + 109.37748066072497, + -0.013575472293673459 + ], + [ + 109.37745142056446, + -0.01368189638823149 + ], + [ + 109.37744467249611, + -0.013756619781172047 + ], + [ + 109.3774626654657, + -0.01383587203725 + ], + [ + 109.37754363524161, + -0.01398305513322124 + ], + [ + 109.37759086784057, + -0.014035135438267026 + ], + [ + 109.37766734004694, + -0.01406457248194538 + ], + [ + 109.37774831074265, + -0.014078159130523706 + ], + [ + 109.37778422079562, + -0.014044339925572087 + ], + [ + 109.37778897263554, + -0.014039864759454167 + ], + [ + 109.37786966032691, + -0.01402768708345877 + ], + [ + 109.3779612156783, + -0.01384583963351223 + ], + [ + 109.37819140347167, + -0.013709073595878453 + ], + [ + 109.37822289216552, + -0.013702280768122781 + ], + [ + 109.3782701246195, + -0.013779268907258977 + ], + [ + 109.3782903673226, + -0.013779269046017823 + ], + [ + 109.3782431349139, + -0.013695487863529095 + ], + [ + 109.37826903048487, + -0.01363979364374237 + ], + [ + 109.37828702548644, + -0.013601091363901386 + ], + [ + 109.37833842303756, + -0.01356716385566656 + ], + [ + 109.37835486435685, + -0.013564494271913259 + ], + [ + 109.37838483474685, + -0.013559627967341613 + ], + [ + 109.3784298187301, + -0.013530191750107735 + ], + [ + 109.37850179283731, + -0.013523399191026677 + ], + [ + 109.3785602717948, + -0.013518870888589994 + ], + [ + 109.37859400972846, + -0.013505285027891976 + ], + [ + 109.3786299968971, + -0.013484906137869943 + ], + [ + 109.37871321695992, + -0.01347811365184894 + ], + [ + 109.3787829416625, + -0.013505286296903996 + ], + [ + 109.3788526662318, + -0.013552838077796281 + ], + [ + 109.37887065941896, + -0.01360265386034128 + ], + [ + 109.37889090158251, + -0.013684170534729019 + ], + [ + 109.37888865198114, + -0.013745307922435744 + ], + [ + 109.3788729073459, + -0.013790594779668734 + ], + [ + 109.37887515621352, + -0.013838146108227818 + ], + [ + 109.3788976482202, + -0.013822295825094038 + ], + [ + 109.37892014005816, + -0.013831353372895068 + ], + [ + 109.37889314940942, + -0.013885697545592776 + ], + [ + 109.37887740483282, + -0.013921927008850851 + ], + [ + 109.37887740436767, + -0.013989857456227172 + ], + [ + 109.37880093103792, + -0.014116660421202161 + ], + [ + 109.3786839719235, + -0.01429554309446221 + ], + [ + 109.37858500633271, + -0.01447216152841498 + ], + [ + 109.37846579848093, + -0.01458537805946452 + ], + [ + 109.37840507013944, + -0.014617078486914459 + ], + [ + 109.37836908297959, + -0.014635193006656862 + ], + [ + 109.3782678693965, + -0.014644249660030905 + ], + [ + 109.37820489198504, + -0.014660099633606273 + ], + [ + 109.37816605185894, + -0.014713114014880933 + ], + [ + 109.37809274747794, + -0.014746835621384461 + ], + [ + 109.37806078850484, + -0.014764537561433956 + ], + [ + 109.37802045781251, + -0.014752936526996583 + ], + [ + 109.37797322486146, + -0.014750671833053145 + ], + [ + 109.37796647706492, + -0.014782372644632735 + ], + [ + 109.37792599146772, + -0.01480954451291973 + ], + [ + 109.37780837529424, + -0.014873187264338603 + ], + [ + 109.37777514924754, + -0.014884141454856852 + ], + [ + 109.37774482667673, + -0.01494210182351541 + ], + [ + 109.37770940183117, + -0.014959084162097241 + ], + [ + 109.37766722948287, + -0.014967575148950831 + ], + [ + 109.3776199963112, + -0.014996445219212038 + ], + [ + 109.37751581112958, + -0.015096452582672356 + ], + [ + 109.37747492262713, + -0.015135701456479415 + ], + [ + 109.37743612371118, + -0.015191743743122134 + ], + [ + 109.37741925451981, + -0.015229105334528687 + ], + [ + 109.37741588038119, + -0.01527665658756115 + ], + [ + 109.37742600123057, + -0.015342888802924856 + ], + [ + 109.37744961728482, + -0.01539893156257549 + ], + [ + 109.37751371876712, + -0.015449879854864028 + ], + [ + 109.37759131538114, + -0.015500828254458093 + ], + [ + 109.37767059901802, + -0.015534794069401498 + ], + [ + 109.37783928801247, + -0.015555174497179948 + ], + [ + 109.37787808650604, + -0.015556873057800595 + ], + [ + 109.37816991865793, + -0.015572159661956344 + ], + [ + 109.37824751565856, + -0.015575556784398594 + ], + [ + 109.37825932372009, + -0.01559933252521755 + ], + [ + 109.37826101024729, + -0.015646883836876062 + ], + [ + 109.37832173799181, + -0.015694435609642097 + ], + [ + 109.378368970732, + -0.015725004671955915 + ], + [ + 109.3784330722491, + -0.01577425473687019 + ], + [ + 109.37852416440987, + -0.01577595371252593 + ], + [ + 109.37855284153716, + -0.015781048720227606 + ], + [ + 109.37857814481458, + -0.015794635006347164 + ], + [ + 109.37862369050872, + -0.015845583193259433 + ], + [ + 109.37866080167512, + -0.015905022620782885 + ], + [ + 109.37868947861838, + -0.015933893285112483 + ], + [ + 109.37871047503792, + -0.01597109671404011 + ], + [ + 109.37873165034031, + -0.016008617106045248 + ], + [ + 109.37861187971512, + -0.01616995094373289 + ], + [ + 109.37856223954205, + -0.01614258331619506 + ], + [ + 109.37854103055253, + -0.016130890373206207 + ], + [ + 109.3784705820132, + -0.016195669300119092 + ], + [ + 109.37840795253588, + -0.01625325843003557 + ], + [ + 109.37820316631752, + -0.016419574767829785 + ], + [ + 109.37805107884874, + -0.01654309200091385 + ], + [ + 109.37795211332795, + -0.016691028537019655 + ], + [ + 109.37784414645482, + -0.017385427412363985 + ], + [ + 109.37782915142424, + -0.01743675248283914 + ], + [ + 109.37782615099927, + -0.017611861962967282 + ], + [ + 109.37782614897581, + -0.017844334928024713 + ], + [ + 109.37780515585814, + -0.017922832105627076 + ], + [ + 109.37776616914782, + -0.018010386508385594 + ], + [ + 109.37769419425382, + -0.01810699799827785 + ], + [ + 109.37764621118737, + -0.018149265373911233 + ], + [ + 109.37754424703004, + -0.018254933967685953 + ], + [ + 109.3775359048212, + -0.018316518301156694 + ], + [ + 109.37743754098908, + -0.018402003686203722 + ], + [ + 109.37740029769714, + -0.018396831712274568 + ], + [ + 109.37739429983434, + -0.018399850786161503 + ], + [ + 109.37740687458104, + -0.01842865504263462 + ], + [ + 109.37738830109622, + -0.018499481980254143 + ], + [ + 109.3773193247233, + -0.018638361263221503 + ], + [ + 109.3772370258539, + -0.01880685717487028 + ], + [ + 109.37705241599649, + -0.019184821000664935 + ], + [ + 109.37705526248565, + -0.01924357876765226 + ], + [ + 109.3770584126607, + -0.019308605316690933 + ], + [ + 109.37692551310403, + -0.01952925746646637 + ], + [ + 109.37687247595177, + -0.019710147550956517 + ], + [ + 109.37685551014788, + -0.020035859399764117 + ], + [ + 109.37686183428859, + -0.020207808235347395 + ], + [ + 109.37686815628707, + -0.020591190378032864 + ], + [ + 109.3768744816761, + -0.020635769754528105 + ], + [ + 109.37687448023796, + -0.020777149857236946 + ], + [ + 109.37685493785388, + -0.02102700608035497 + ], + [ + 109.37683694226044, + -0.021228532681210856 + ], + [ + 109.37681669760244, + -0.02141647317889184 + ], + [ + 109.37679870367174, + -0.021457231213268226 + ], + [ + 109.37674247264003, + -0.02158403397952329 + ], + [ + 109.37672920983695, + -0.02163120978177955 + ], + [ + 109.37670873355002, + -0.02170404394441004 + ], + [ + 109.3766142670939, + -0.021758387220113782 + ], + [ + 109.37651288865362, + -0.021705760281342155 + ], + [ + 109.37647031989859, + -0.021683662257739073 + ], + [ + 109.3764388315177, + -0.021661018462713417 + ], + [ + 109.37640059557529, + -0.02163837459567367 + ], + [ + 109.3763511135532, + -0.021629316680836785 + ], + [ + 109.3762993822489, + -0.02162931612354107 + ], + [ + 109.37623640489032, + -0.021640637171408805 + ], + [ + 109.3761689294487, + -0.021624786027728035 + ], + [ + 109.37612169683553, + -0.021595349032124254 + ], + [ + 109.37607446422305, + -0.02156591203789076 + ], + [ + 109.37600249050868, + -0.021541003469575853 + ], + [ + 109.37595300861582, + -0.021520623834047202 + ], + [ + 109.37593951366311, + -0.021504773274835763 + ], + [ + 109.37591477282477, + -0.02148439390561792 + ], + [ + 109.37586753992896, + -0.02148212905519853 + ], + [ + 109.3758360515315, + -0.02146174961482958 + ], + [ + 109.37580456330137, + -0.021425519761598524 + ], + [ + 109.37577982260686, + -0.021391554326182283 + ], + [ + 109.3757483342574, + -0.02136664619867544 + ], + [ + 109.37572584251151, + -0.02135532423576948 + ], + [ + 109.37570784932869, + -0.0213258875635606 + ], + [ + 109.37569885289132, + -0.021296450987343805 + ], + [ + 109.37568085966055, + -0.021271543005317292 + ], + [ + 109.3756381254976, + -0.02123531303889523 + ], + [ + 109.3756223815947, + -0.021196819014476627 + ], + [ + 109.37552116882425, + -0.02113794498942981 + ], + [ + 109.37562238422176, + -0.02094321242191968 + ], + [ + 109.37556165577125, + -0.02098396998931096 + ], + [ + 109.37551442271972, + -0.02099755556216262 + ], + [ + 109.37547393699371, + -0.021033784649071324 + ], + [ + 109.375451444757, + -0.02107001392345642 + ], + [ + 109.37544244770726, + -0.0210994503058325 + ], + [ + 109.37539071619149, + -0.021122093205039986 + ], + [ + 109.37532998823855, + -0.02111529953306322 + ], + [ + 109.37525126687038, + -0.021101712639685705 + ], + [ + 109.3752085324791, + -0.021088126125548258 + ], + [ + 109.37516354892622, + -0.021072275244355277 + ], + [ + 109.3751140676115, + -0.020997551372548412 + ], + [ + 109.37507133322244, + -0.02098396486153748 + ], + [ + 109.37503534634455, + -0.020974907109481827 + ], + [ + 109.37500385776855, + -0.02097264243637334 + ], + [ + 109.37497686777431, + -0.02094999871572158 + ], + [ + 109.3749588745925, + -0.020920562058046193 + ], + [ + 109.37495662589568, + -0.020873010814263484 + ], + [ + 109.37485541245323, + -0.020882067135924443 + ], + [ + 109.3748284223439, + -0.020870745136554104 + ], + [ + 109.37478118996069, + -0.020820929085337538 + ], + [ + 109.37464623995146, + -0.020712239197665586 + ], + [ + 109.37455177495632, + -0.02063525054757003 + ], + [ + 109.37448205047085, + -0.02061034205397827 + ], + [ + 109.37443931604689, + -0.020601284241801444 + ], + [ + 109.37438308653708, + -0.02058996194827388 + ], + [ + 109.37435834582365, + -0.020558260890169593 + ], + [ + 109.37431336217153, + -0.020553731742788288 + ], + [ + 109.3742810278033, + -0.020624259799844058 + ], + [ + 109.3741293835049, + -0.02055194634261717 + ], + [ + 109.37411543478157, + -0.020467684684664828 + ], + [ + 109.37406693673906, + -0.02046759573864962 + ], + [ + 109.37403182855633, + -0.02044944525767302 + ], + [ + 109.37401949343464, + -0.02042460812209528 + ], + [ + 109.37401949381827, + -0.020386397337931562 + ], + [ + 109.37400810748446, + -0.020370157638945535 + ], + [ + 109.37396256159403, + -0.020360604480407423 + ], + [ + 109.37391891332, + -0.020364425115337186 + ], + [ + 109.3738838049475, + -0.020365380028255067 + ], + [ + 109.37385818540842, + -0.020357737611741387 + ], + [ + 109.37383731043394, + -0.0203309898532768 + ], + [ + 109.37382402667588, + -0.020282270973426116 + ], + [ + 109.3738306691414, + -0.020247881338093767 + ], + [ + 109.37383066944582, + -0.020217312713590764 + ], + [ + 109.37381358989236, + -0.020198207151210523 + ], + [ + 109.37373759874887, + -0.02036511897137608 + ], + [ + 109.37373008709503, + -0.020381618047539976 + ], + [ + 109.37364658650621, + -0.020344361690870748 + ], + [ + 109.37365396900896, + -0.020325239108705897 + ], + [ + 109.37368359355068, + -0.020250680796878067 + ], + [ + 109.37362961301105, + -0.020262756743946116 + ], + [ + 109.37360877561107, + -0.020303688085877142 + ], + [ + 109.37350665519365, + -0.020504285334303842 + ], + [ + 109.37347366704051, + -0.020516361488322903 + ], + [ + 109.37345267419931, + -0.02056164811596322 + ], + [ + 109.37322175924753, + -0.02043786172476147 + ], + [ + 109.37324575090418, + -0.020401632499150814 + ], + [ + 109.37322775770767, + -0.020374460213490903 + ], + [ + 109.37327105519539, + -0.020304360401689203 + ], + [ + 109.37318839771314, + -0.020302661307755775 + ], + [ + 109.37308887178195, + -0.020258505635923175 + ], + [ + 109.3730821243839, + -0.020243221261169002 + ], + [ + 109.37310405417672, + -0.02021774763822251 + ], + [ + 109.37306525598125, + -0.020195669915292185 + ], + [ + 109.37299440672015, + -0.020193970944989502 + ], + [ + 109.37291343633929, + -0.020171892799044914 + ], + [ + 109.37291006296883, + -0.02013113461784612 + ], + [ + 109.37291681090551, + -0.020092074794420586 + ], + [ + 109.37292355879123, + -0.020058109739150808 + ], + [ + 109.37293030665958, + -0.020025842939808566 + ], + [ + 109.37291207558964, + -0.019994781727363155 + ], + [ + 109.37288644832111, + -0.019951119232300037 + ], + [ + 109.37288307472963, + -0.01993243838147033 + ], + [ + 109.37285271110554, + -0.019896774701590098 + ], + [ + 109.3728071656112, + -0.01984922307991022 + ], + [ + 109.37276162008364, + -0.019805067972339694 + ], + [ + 109.37274475157439, + -0.01976770617368622 + ], + [ + 109.37274306508343, + -0.019726948013188392 + ], + [ + 109.37271270094678, + -0.019743930273912788 + ], + [ + 109.37270089250445, + -0.019767705740905202 + ], + [ + 109.37268065000576, + -0.01975242123766662 + ], + [ + 109.37265872071902, + -0.01972694718262205 + ], + [ + 109.37263173095165, + -0.0196827922634704 + ], + [ + 109.37258449832375, + -0.019660714473190843 + ], + [ + 109.37253389174154, + -0.019657317464856324 + ], + [ + 109.37247485093877, + -0.01963184304893208 + ], + [ + 109.37238882017427, + -0.01958259279058203 + ], + [ + 109.37233483988852, + -0.019572402728921117 + ], + [ + 109.37230447582483, + -0.019582591965914057 + ], + [ + 109.3722876065782, + -0.01962165167991537 + ], + [ + 109.37227917225842, + -0.019609763808131547 + ], + [ + 109.37226567747445, + -0.01957749681977239 + ], + [ + 109.37223025382963, + -0.0194756011399508 + ], + [ + 109.37216952583735, + -0.019482393571411235 + ], + [ + 109.37213072660721, + -0.01956900422313216 + ], + [ + 109.37211385783745, + -0.019558814525553133 + ], + [ + 109.37205313024084, + -0.019524848824222302 + ], + [ + 109.37198059469956, + -0.01946371092446632 + ], + [ + 109.37195191800042, + -0.01942465077328941 + ], + [ + 109.37194517064877, + -0.01940427164402359 + ], + [ + 109.37192998900962, + -0.019368608135499347 + ], + [ + 109.37192492873834, + -0.019327849959342093 + ], + [ + 109.37190806009836, + -0.01930407422248293 + ], + [ + 109.37189625210202, + -0.019281996790049393 + ], + [ + 109.37189625253717, + -0.019236143897570224 + ], + [ + 109.37188781789476, + -0.01925822113503791 + ], + [ + 109.37188275663767, + -0.019321056531186657 + ], + [ + 109.37184227116565, + -0.019341435203261096 + ], + [ + 109.37180853306208, + -0.01938049474701569 + ], + [ + 109.37174274430984, + -0.019399174916788147 + ], + [ + 109.37163815752939, + -0.019380493097747108 + ], + [ + 109.37152007587447, + -0.019339733835625306 + ], + [ + 109.37147959066871, + -0.019332940424800635 + ], + [ + 109.37143741839947, + -0.019344827801670163 + ], + [ + 109.3713345188116, + -0.019295577418051848 + ], + [ + 109.37130246808837, + -0.01928368932567488 + ], + [ + 109.37126873038264, + -0.01928199074586636 + ], + [ + 109.37125186179613, + -0.019253120252971417 + ], + [ + 109.37122318489442, + -0.019236137430022518 + ], + [ + 109.37114896203884, + -0.019222550679543798 + ], + [ + 109.37109498186533, + -0.019203869360516917 + ], + [ + 109.37105787047165, + -0.019193679477023343 + ], + [ + 109.37102750668626, + -0.01917669664037479 + ], + [ + 109.37097690014285, + -0.019173299646463162 + ], + [ + 109.37093641486724, + -0.019174997513030874 + ], + [ + 109.37089424289931, + -0.01915631631024461 + ], + [ + 109.37084363693569, + -0.019091782159217915 + ], + [ + 109.37080483920286, + -0.019023851615017573 + ], + [ + 109.37073905125077, + -0.018961015580638414 + ], + [ + 109.37064289923212, + -0.01891176529726435 + ], + [ + 109.3705771107761, + -0.018903273404591255 + ], + [ + 109.37051132221139, + -0.0189066692911701 + ], + [ + 109.37045228106352, + -0.01892365127373878 + ], + [ + 109.37037299738856, + -0.018930443539679265 + ], + [ + 109.37031733022135, + -0.01892534825102269 + ], + [ + 109.37022117839612, + -0.018857417187147348 + ], + [ + 109.37018406711175, + -0.018837037791393993 + ], + [ + 109.37014020778219, + -0.01887100245405544 + ], + [ + 109.37012671240065, + -0.018903269148421522 + ], + [ + 109.37008791404344, + -0.01890326878173021 + ], + [ + 109.37003056014149, + -0.01888288919537033 + ], + [ + 109.36997320592468, + -0.018896474682989297 + ], + [ + 109.36994452853155, + -0.01893383599166947 + ], + [ + 109.36989820271836, + -0.018950856753528065 + ], + [ + 109.36986705711374, + -0.01889807758613014 + ], + [ + 109.36983706835862, + -0.018864867011511053 + ], + [ + 109.36978908602498, + -0.01884675185499556 + ], + [ + 109.36974410257034, + -0.018831655844807827 + ], + [ + 109.36970211788275, + -0.01883165544941188 + ], + [ + 109.3696361417224, + -0.018855807764707577 + ], + [ + 109.36957916239425, + -0.018867883695329473 + ], + [ + 109.36952818118894, + -0.01884674939582535 + ], + [ + 109.36949819221368, + -0.01883769176249809 + ], + [ + 109.36947720020879, + -0.01880146216265111 + ], + [ + 109.36945920736088, + -0.01873806054035652 + ], + [ + 109.36943221790477, + -0.018662582368061384 + ], + [ + 109.36940522780827, + -0.01865654388265162 + ], + [ + 109.36940822704734, + -0.018620314509761655 + ], + [ + 109.36942921983132, + -0.018572008837003568 + ], + [ + 109.36945021253142, + -0.0185327605136332 + ], + [ + 109.36944421543618, + -0.018454263421466787 + ], + [ + 109.36945321256555, + -0.01840897675243416 + ], + [ + 109.36947720460599, + -0.01832142258494622 + ], + [ + 109.36951019300375, + -0.018273494406603093 + ], + [ + 109.3695214392275, + -0.01823726510649195 + ], + [ + 109.36955067874385, + -0.0182168863337729 + ], + [ + 109.36958666575849, + -0.018201036297347234 + ], + [ + 109.36960915785878, + -0.0181670714353732 + ], + [ + 109.36961140730192, + -0.01813763506464795 + ], + [ + 109.36960016176846, + -0.018096876882778515 + ], + [ + 109.36962040462694, + -0.01806970501246106 + ], + [ + 109.36964289664472, + -0.018044797499743873 + ], + [ + 109.36966988732337, + -0.0179859249592545 + ], + [ + 109.36969687761963, + -0.017970074836867314 + ], + [ + 109.36973736285374, + -0.017970075200699345 + ], + [ + 109.36975085785166, + -0.017979132673742078 + ], + [ + 109.3697396122542, + -0.01794516750371272 + ], + [ + 109.36970137644, + -0.017917995105957492 + ], + [ + 109.36968113394333, + -0.017904408897396782 + ], + [ + 109.36966538986589, + -0.017884029715826658 + ], + [ + 109.3696653902251, + -0.01784327163462341 + ], + [ + 109.36969687951562, + -0.017754962738318104 + ], + [ + 109.36972611932673, + -0.017700618888024443 + ], + [ + 109.36973061800208, + -0.01766438952117344 + ], + [ + 109.36972387067996, + -0.017639481744512128 + ], + [ + 109.36975985838073, + -0.017544379868066923 + ], + [ + 109.36979359646831, + -0.017499093404276835 + ], + [ + 109.36982733473076, + -0.01743342789667408 + ], + [ + 109.3698003448071, + -0.017406255605451143 + ], + [ + 109.36981833857531, + -0.017367762015788515 + ], + [ + 109.36987007062035, + -0.01726133857547002 + ], + [ + 109.36990830771546, + -0.017139064647097503 + ], + [ + 109.36996903652728, + -0.017025848258124025 + ], + [ + 109.3701152344601, + -0.016878667509723257 + ], + [ + 109.37012198245218, + -0.016824323445986195 + ], + [ + 109.37011073683327, + -0.01679035827616574 + ], + [ + 109.37005450751893, + -0.01676771442127213 + ], + [ + 109.37001177311727, + -0.01676544972476781 + ], + [ + 109.36988581825273, + -0.016860550868769572 + ], + [ + 109.3697868535672, + -0.01695338789166038 + ], + [ + 109.36972162699537, + -0.016996409758912655 + ], + [ + 109.36969013859421, + -0.01698282346403142 + ], + [ + 109.36969688554379, + -0.01705301799561147 + ], + [ + 109.36967889172499, + -0.017098304599352316 + ], + [ + 109.36952594665429, + -0.01720019848733047 + ], + [ + 109.36962715898065, + -0.017288508532255387 + ], + [ + 109.3695979192015, + -0.017340588048082747 + ], + [ + 109.36941123792775, + -0.01726812762718402 + ], + [ + 109.36937974891485, + -0.017327000130726702 + ], + [ + 109.36934826025013, + -0.017345114557702344 + ], + [ + 109.3692852828244, + -0.017392665096953 + ], + [ + 109.36924929549069, + -0.01744700888080986 + ], + [ + 109.3692313015496, + -0.017505881494934643 + ], + [ + 109.3692942779192, + -0.017580605181403884 + ], + [ + 109.36933926144464, + -0.017587398589403593 + ], + [ + 109.36939774024329, + -0.017571548741100937 + ], + [ + 109.36946296658174, + -0.01755569895106862 + ], + [ + 109.36952144475556, + -0.01761230790658037 + ], + [ + 109.36952594254251, + -0.01767797373910608 + ], + [ + 109.36957767124214, + -0.017954223401298937 + ], + [ + 109.36953493649025, + -0.017992716758413786 + ], + [ + 109.36947195884161, + -0.018062910658016535 + ], + [ + 109.36942022721072, + -0.018119518629812717 + ], + [ + 109.3693797414758, + -0.0181761267008921 + ], + [ + 109.36935724909284, + -0.0182417922837571 + ], + [ + 109.36935724815243, + -0.01834595180851437 + ], + [ + 109.36933250660545, + -0.01840935303053927 + ], + [ + 109.369283024065, + -0.0184750183597697 + ], + [ + 109.36921104883884, + -0.018638049982307732 + ], + [ + 109.36916831435273, + -0.018647106932952393 + ], + [ + 109.36914807142836, + -0.018681071802998273 + ], + [ + 109.36917955932871, + -0.018746737878789613 + ], + [ + 109.36918180783601, + -0.01881919669334156 + ], + [ + 109.369173548005, + -0.018839060971364337 + ], + [ + 109.36901696625627, + -0.018814898069208986 + ], + [ + 109.36866373188661, + -0.018760413703317762 + ], + [ + 109.36822823014926, + -0.01870811194530776 + ], + [ + 109.36821528431788, + -0.018733362959462714 + ], + [ + 109.36817985959183, + -0.018752043400614562 + ], + [ + 109.36814106102057, + -0.018779215069769435 + ], + [ + 109.36810226263886, + -0.01878600771330887 + ], + [ + 109.36803984806437, + -0.018774119362336302 + ], + [ + 109.36803310079851, + -0.018745249014990282 + ], + [ + 109.36802635357934, + -0.01871128391185678 + ], + [ + 109.36798755570187, + -0.018663732493613083 + ], + [ + 109.367952422394, + -0.018607139187907823 + ], + [ + 109.36785510651337, + -0.018554432325168936 + ], + [ + 109.3677648882425, + -0.018541456285700836 + ], + [ + 109.36775308065079, + -0.018476922609266054 + ], + [ + 109.3677446464832, + -0.018449750503246685 + ], + [ + 109.36773283867261, + -0.01840899235236249 + ], + [ + 109.36772271780605, + -0.01836144121059736 + ], + [ + 109.3677159707046, + -0.01831389010023388 + ], + [ + 109.36772271865121, + -0.01826803736533839 + ], + [ + 109.36771934574104, + -0.018172935237626427 + ], + [ + 109.36771259849826, + -0.01814066839350868 + ], + [ + 109.36771428562459, + -0.018113496381220347 + ], + [ + 109.36772440746918, + -0.01805235941063713 + ], + [ + 109.36773621593828, + -0.01802009273423562 + ], + [ + 109.36774802473853, + -0.017950464518886714 + ], + [ + 109.36772440811919, + -0.01797933458609498 + ], + [ + 109.36768213347996, + -0.01799738233968352 + ], + [ + 109.36766536706148, + -0.017996316571413762 + ], + [ + 109.36764006383132, + -0.017994618091732646 + ], + [ + 109.36761813432217, + -0.017998014397412453 + ], + [ + 109.36761307338607, + -0.01803028113289898 + ], + [ + 109.36762319425988, + -0.018077832270450504 + ], + [ + 109.36764174969956, + -0.01810840096836264 + ], + [ + 109.36765331913298, + -0.01813970423267154 + ], + [ + 109.36759402198473, + -0.018170913768759657 + ], + [ + 109.36759070579672, + -0.01821430961830965 + ], + [ + 109.36759995064926, + -0.018228179315134572 + ], + [ + 109.3676704383934, + -0.01824420384652216 + ], + [ + 109.36765692975305, + -0.018318984313810113 + ], + [ + 109.36763499984296, + -0.01836653515908685 + ], + [ + 109.3675860798971, + -0.018403896244727228 + ], + [ + 109.36748992709114, + -0.018456541156142446 + ], + [ + 109.36747137105394, + -0.01849220426650184 + ], + [ + 109.3673735313996, + -0.01854145265204957 + ], + [ + 109.36732123803397, + -0.018541452166452674 + ], + [ + 109.3672470153191, + -0.018527865466557686 + ], + [ + 109.36716435813194, + -0.01852107169443618 + ], + [ + 109.36710363040561, + -0.018515976377485813 + ], + [ + 109.36702603370072, + -0.018527863415758256 + ], + [ + 109.36698048793622, + -0.018521069988604547 + ], + [ + 109.36693662919495, + -0.01849899231787269 + ], + [ + 109.36690626490747, + -0.018543146563446473 + ], + [ + 109.36689664353507, + -0.01858365089983707 + ], + [ + 109.36680835321691, + -0.018578478732120717 + ], + [ + 109.36679774130053, + -0.018634945453656893 + ], + [ + 109.36671976988134, + -0.018631925613002212 + ], + [ + 109.36671138016843, + -0.018572797918431357 + ], + [ + 109.3667047763433, + -0.018526256530222468 + ], + [ + 109.36666878985437, + -0.018490026845080255 + ], + [ + 109.36663580223961, + -0.018456816301468372 + ], + [ + 109.36659381778256, + -0.018438701238314092 + ], + [ + 109.36654583527567, + -0.0184477581321918 + ], + [ + 109.3665338392562, + -0.018493044707197415 + ], + [ + 109.36650984765781, + -0.018535312057898885 + ], + [ + 109.36646903427005, + -0.018565747357180504 + ], + [ + 109.36642887676662, + -0.018595693550510394 + ], + [ + 109.36640435555579, + -0.018564423427023236 + ], + [ + 109.36638389377403, + -0.018538330000170578 + ], + [ + 109.36633291297865, + -0.01848096639690719 + ], + [ + 109.36627293506659, + -0.01846888939341047 + ], + [ + 109.3661889660036, + -0.018450773944976448 + ], + [ + 109.36614098342382, + -0.018468888172048076 + ], + [ + 109.36610199807093, + -0.018429639358541985 + ], + [ + 109.36603302335337, + -0.01842963872140499 + ], + [ + 109.3659280613903, + -0.018477943535403205 + ], + [ + 109.36585608783346, + -0.018471904646053403 + ], + [ + 109.36580810559241, + -0.018453789533973686 + ], + [ + 109.36579011260311, + -0.018408502698527094 + ], + [ + 109.36578711405971, + -0.018369254224400115 + ], + [ + 109.36579311218922, + -0.01833302494440652 + ], + [ + 109.36573913173717, + -0.018360196449021194 + ], + [ + 109.36567615466788, + -0.01837831053591038 + ], + [ + 109.36507337661833, + -0.018290750785729244 + ], + [ + 109.3649654161658, + -0.01829980712544559 + ], + [ + 109.36491143570899, + -0.018329997729357898 + ], + [ + 109.36484545976721, + -0.01834811178178923 + ], + [ + 109.3647674879299, + -0.01839943592936706 + ], + [ + 109.36468651741619, + -0.018426607168431565 + ], + [ + 109.36459055252273, + -0.018441701828606303 + ], + [ + 109.36458829863331, + -0.018582304317596315 + ], + [ + 109.36420009128064, + -0.018582304320317416 + ], + [ + 109.36414600715753, + -0.018574349280038087 + ], + [ + 109.36412572155787, + -0.018631901378560273 + ], + [ + 109.36398777234534, + -0.018622842763133186 + ], + [ + 109.36398777273465, + -0.018580575243609867 + ], + [ + 109.36388281137562, + -0.01857453604806666 + ], + [ + 109.36387081534096, + -0.01862284167001646 + ], + [ + 109.36373886394043, + -0.018613783112160567 + ], + [ + 109.36372298776038, + -0.01854185567116963 + ], + [ + 109.36371487376152, + -0.018505094995562982 + ], + [ + 109.3636818861687, + -0.018474903608393427 + ], + [ + 109.36358892057928, + -0.018450749882815594 + ], + [ + 109.36349295584891, + -0.01845376810175476 + ], + [ + 109.3634329777464, + -0.018471882192302898 + ], + [ + 109.36338199628024, + -0.018496034580422985 + ], + [ + 109.36335500601625, + -0.018517168082897054 + ], + [ + 109.36330702458123, + -0.018417537089144648 + ], + [ + 109.3633160216838, + -0.01837225055953622 + ], + [ + 109.36333101671552, + -0.018311868547008392 + ], + [ + 109.36334301276712, + -0.018260543828708828 + ], + [ + 109.36329203127853, + -0.0182877153286965 + ], + [ + 109.36325304537704, + -0.01831488693756821 + ], + [ + 109.36320506226437, + -0.0183994215021386 + ], + [ + 109.36316607630776, + -0.018432631322182827 + ], + [ + 109.36310909713922, + -0.01844772633085425 + ], + [ + 109.36307910759584, + -0.01851112730341066 + ], + [ + 109.36303280094405, + -0.018555952731530878 + ], + [ + 109.36302821167449, + -0.0185609493020454 + ], + [ + 109.36302213815911, + -0.018561319936415015 + ], + [ + 109.3629556225475, + -0.018565382438695897 + ], + [ + 109.36277010497373, + -0.018576709399338406 + ], + [ + 109.36272898622111, + -0.018540182950021074 + ], + [ + 109.36265926195875, + -0.018533389311080475 + ], + [ + 109.36264233510897, + -0.018584510515222634 + ], + [ + 109.36217585692668, + -0.018612991798566122 + ], + [ + 109.36209954958014, + -0.018626230940715638 + ], + [ + 109.3619805114517, + -0.018596155226601744 + ], + [ + 109.3619805120445, + -0.01853174764272841 + ], + [ + 109.36193652862679, + -0.01849149249412016 + ], + [ + 109.36176059352469, + -0.018487465385744098 + ], + [ + 109.36168462116821, + -0.018527719413840637 + ], + [ + 109.36169661581796, + -0.01862835636096734 + ], + [ + 109.36158065850499, + -0.018636406221746817 + ], + [ + 109.36158465658337, + -0.018684711937477356 + ], + [ + 109.3612847667557, + -0.018733014792028997 + ], + [ + 109.36128076875384, + -0.01867665813829821 + ], + [ + 109.36119280106203, + -0.018692759202002713 + ], + [ + 109.36118480352704, + -0.018745090267825643 + ], + [ + 109.36091690213749, + -0.018773266045957207 + ], + [ + 109.36092090107346, + -0.018728985893325077 + ], + [ + 109.3608289350977, + -0.0187209340839861 + ], + [ + 109.3608129404408, + -0.01878131600783223 + ], + [ + 109.36074496557458, + -0.01877728989438068 + ], + [ + 109.36074096742553, + -0.018737035141812772 + ], + [ + 109.36069298521693, + -0.018728983746920367 + ], + [ + 109.36068898710438, + -0.018684703524117507 + ], + [ + 109.36053704340151, + -0.018664574741052754 + ], + [ + 109.36051305207563, + -0.018684701870953554 + ], + [ + 109.36043308209769, + -0.018632369998264835 + ], + [ + 109.3601731778191, + -0.018664571325180394 + ], + [ + 109.36016517946364, + -0.01880546271124471 + ], + [ + 109.35998524580876, + -0.018821562888850844 + ], + [ + 109.35962937710613, + -0.01884973780345417 + ], + [ + 109.35939746231045, + -0.018910117635020318 + ], + [ + 109.359385466066, + -0.0189825759581672 + ], + [ + 109.3593294867737, + -0.018982575423369605 + ], + [ + 109.35932548870746, + -0.018934269761711312 + ], + [ + 109.35910956848119, + -0.018946344108563374 + ], + [ + 109.35911356737998, + -0.01890608946484216 + ], + [ + 109.35893363352247, + -0.018953722455000186 + ], + [ + 109.35707748451382, + -0.01932727618583158 + ], + [ + 109.35691104608358, + -0.019347653474667013 + ], + [ + 109.3568596408064, + -0.019387585898130336 + ], + [ + 109.35672886338105, + -0.019381616545004324 + ], + [ + 109.35667488355206, + -0.019368030079136605 + ], + [ + 109.35664339563509, + -0.019327271957032724 + ], + [ + 109.3565849169336, + -0.019370293523740815 + ], + [ + 109.3565331858586, + -0.01939972921642353 + ], + [ + 109.35649944840874, + -0.01939746456359679 + ], + [ + 109.35644097008186, + -0.019401992637300747 + ], + [ + 109.35640948162329, + -0.01941784258870024 + ], + [ + 109.35636674723253, + -0.019445014042988557 + ], + [ + 109.35633300989495, + -0.019431427776084975 + ], + [ + 109.35628802666959, + -0.019424634367478513 + ], + [ + 109.35624529228123, + -0.01945180581994243 + ], + [ + 109.35619356116977, + -0.019485770150413754 + ], + [ + 109.35615532550673, + -0.01947218383978241 + ], + [ + 109.35606760787843, + -0.019494826202270683 + ], + [ + 109.35602712298774, + -0.01948803283728962 + ], + [ + 109.35593490778255, + -0.019433688197505888 + ], + [ + 109.35570999107584, + -0.019465386500551813 + ], + [ + 109.35564701415906, + -0.019499350710723107 + ], + [ + 109.35564116360628, + -0.01951954420102592 + ], + [ + 109.35563126948438, + -0.019553694281075003 + ], + [ + 109.35562677084812, + -0.019585394742964106 + ], + [ + 109.35558853473285, + -0.01961935919271871 + ], + [ + 109.35555029888341, + -0.019626151779660102 + ], + [ + 109.35549631913234, + -0.019608036671430264 + ], + [ + 109.35544309402965, + -0.019554054499914028 + ], + [ + 109.35542344026472, + -0.01955747883833027 + ], + [ + 109.35537936278219, + -0.0195921852633289 + ], + [ + 109.35535012361272, + -0.019596713617454027 + ], + [ + 109.3553285128283, + -0.019574086486833252 + ], + [ + 109.35508301884954, + -0.01962581270230119 + ], + [ + 109.35507797421093, + -0.019662376242868974 + ], + [ + 109.35506447905478, + -0.01968049067886809 + ], + [ + 109.35501949581463, + -0.01967822591090871 + ], + [ + 109.35497901072401, + -0.019694075756895687 + ], + [ + 109.35497901047954, + -0.019718983289415005 + ], + [ + 109.35495202032305, + -0.019739361910802628 + ], + [ + 109.35491153545603, + -0.019732568544345117 + ], + [ + 109.3548665523971, + -0.01971218920729051 + ], + [ + 109.354843099107, + -0.01967636440487731 + ], + [ + 109.35479907799908, + -0.01966237347558127 + ], + [ + 109.3547473472545, + -0.01966237296224251 + ], + [ + 109.3546776230543, + -0.019678222515829638 + ], + [ + 109.3546700658779, + -0.01971282294416363 + ], + [ + 109.35464263568355, + -0.019718602555146566 + ], + [ + 109.35456266632498, + -0.019630041662222698 + ], + [ + 109.35456266679655, + -0.019581736154778887 + ], + [ + 109.35459601927015, + -0.01955341147059367 + ], + [ + 109.35459482460077, + -0.019509164125212197 + ], + [ + 109.35455467110411, + -0.019444870471872475 + ], + [ + 109.35454267588209, + -0.01941266668279945 + ], + [ + 109.3545426765038, + -0.01934825934022288 + ], + [ + 109.35451868621104, + -0.01926774992863285 + ], + [ + 109.35452268514662, + -0.01922346991990975 + ], + [ + 109.35454667663697, + -0.019179190104568745 + ], + [ + 109.35455067560906, + -0.019130884636123308 + ], + [ + 109.35455467450326, + -0.01909063008531571 + ], + [ + 109.35460228514904, + -0.019054070543014712 + ], + [ + 109.35460859402941, + -0.01889237790493689 + ], + [ + 109.35455467819652, + -0.018698147838886868 + ], + [ + 109.35448359073264, + -0.018998714757907226 + ], + [ + 109.35445081946598, + -0.019590848240541758 + ], + [ + 109.35443991986031, + -0.019970385862509682 + ], + [ + 109.35443581988551, + -0.02011315135808401 + ], + [ + 109.35410538423977, + -0.02016033411502451 + ], + [ + 109.35401297678686, + -0.020173528932027215 + ], + [ + 109.35401141439992, + -0.02024356991754123 + ], + [ + 109.35397522104792, + -0.020250635369086056 + ], + [ + 109.35297835980673, + -0.020445236652138928 + ], + [ + 109.35297835957296, + -0.020468161344230865 + ], + [ + 109.35297835891257, + -0.020532790314334574 + ], + [ + 109.35241456907765, + -0.020559956283051566 + ], + [ + 109.35237236675253, + -0.020430255393394294 + ], + [ + 109.35232970070894, + -0.020433835522013905 + ], + [ + 109.35213746633012, + -0.020451023016553285 + ], + [ + 109.35211859132843, + -0.02045372614991334 + ], + [ + 109.35200439118807, + -0.020467414646826103 + ], + [ + 109.35196473753346, + -0.020517684344645758 + ], + [ + 109.35169783696573, + -0.02054787247442469 + ], + [ + 109.35169783535034, + -0.020704865146397758 + ], + [ + 109.3512899875528, + -0.020732032679135738 + ], + [ + 109.35116103559449, + -0.020750145861134953 + ], + [ + 109.3509930973829, + -0.020849774027893366 + ], + [ + 109.35086414559986, + -0.020852791756366364 + ], + [ + 109.35085514940579, + -0.020810524421671108 + ], + [ + 109.35069021066089, + -0.020855809009712508 + ], + [ + 109.35068421224462, + -0.020919209800709743 + ], + [ + 109.35053726734351, + -0.020910150981173704 + ], + [ + 109.35045329849555, + -0.020937321884265177 + ], + [ + 109.35025537267165, + -0.020931281611899583 + ], + [ + 109.34997047783246, + -0.02107921386703259 + ], + [ + 109.34976355526966, + -0.02108826891978025 + ], + [ + 109.34973356647629, + -0.02109128768652326 + ], + [ + 109.34955663250567, + -0.02111845757642657 + ], + [ + 109.34954017951615, + -0.02109361119507184 + ], + [ + 109.34952664422445, + -0.021073170957846256 + ], + [ + 109.34949365652716, + -0.02107920877889516 + ], + [ + 109.34945467168232, + -0.02102788389369493 + ], + [ + 109.34938869670296, + -0.02100071141439908 + ], + [ + 109.34939469337465, + -0.02110336041325593 + ], + [ + 109.34916377993349, + -0.021106377031704846 + ], + [ + 109.34917245499177, + -0.021183239323342394 + ], + [ + 109.34917877290466, + -0.021239216975833444 + ], + [ + 109.34877992163142, + -0.021323747076243415 + ], + [ + 109.34851348646235, + -0.021337563087317973 + ], + [ + 109.34800621120914, + -0.021408273075218382 + ], + [ + 109.34794923280695, + -0.021390157952346323 + ], + [ + 109.34791324655203, + -0.021369023973783322 + ], + [ + 109.34788625685303, + -0.021353928261389203 + ], + [ + 109.34788325852706, + -0.0213026038007013 + ], + [ + 109.34787126374022, + -0.02123618382326901 + ], + [ + 109.34784127526368, + -0.021212030828876932 + ], + [ + 109.34778729564177, + -0.0212029729969814 + ], + [ + 109.34776330443218, + -0.021224106325989773 + ], + [ + 109.34772013383558, + -0.02124317690573855 + ], + [ + 109.34772925338193, + -0.021312599464729652 + ], + [ + 109.34766434062533, + -0.02131769685550224 + ], + [ + 109.34764034973809, + -0.021308639345336642 + ], + [ + 109.3476283536663, + -0.021362982721027796 + ], + [ + 109.34753838738325, + -0.02137203899727932 + ], + [ + 109.34752339353494, + -0.021323733499351627 + ], + [ + 109.34751139868708, + -0.021263351700802003 + ], + [ + 109.34752039562619, + -0.02123316096318317 + ], + [ + 109.34752039613869, + -0.021184855627821932 + ], + [ + 109.34750840087138, + -0.021163721914894087 + ], + [ + 109.34745442116242, + -0.02116372133578067 + ], + [ + 109.34737944925106, + -0.021172777781163526 + ], + [ + 109.34734346249279, + -0.02119994914354673 + ], + [ + 109.34732546893727, + -0.021230139781801865 + ], + [ + 109.34730447625233, + -0.02128448305222583 + ], + [ + 109.3472834839202, + -0.02130561640737065 + ], + [ + 109.34728048411273, + -0.021393169784698688 + ], + [ + 109.34693561397668, + -0.02137807063102265 + ], + [ + 109.34693561439603, + -0.021338822557633575 + ], + [ + 109.34671969566402, + -0.021335801138882768 + ], + [ + 109.34671140263399, + -0.021444321812248906 + ], + [ + 109.34670543511231, + -0.02144394487332922 + ], + [ + 109.34652283638641, + -0.02143426151854532 + ], + [ + 109.34652476985947, + -0.021257302899201483 + ], + [ + 109.3465127745671, + -0.0212391882783833 + ], + [ + 109.34644080153011, + -0.021251263830232785 + ], + [ + 109.34641980914314, + -0.021278435339921957 + ], + [ + 109.34640781343357, + -0.02129956878274744 + ], + [ + 109.34635083481928, + -0.021305606330447178 + ], + [ + 109.34627586301362, + -0.021308624601737408 + ], + [ + 109.34620388995272, + -0.021323719230890395 + ], + [ + 109.34614391228001, + -0.02134787123291639 + ], + [ + 109.34609293141447, + -0.02135390884327516 + ], + [ + 109.34602995522953, + -0.0213448509174969 + ], + [ + 109.3459040028978, + -0.021323715986471738 + ], + [ + 109.34556958092276, + -0.021306144315182116 + ], + [ + 109.34542895074155, + -0.021268476115850445 + ], + [ + 109.34523402885775, + -0.02121150737579636 + ], + [ + 109.34501304449228, + -0.021158147989749312 + ], + [ + 109.34481271637227, + -0.021112929254440607 + ], + [ + 109.3446357632441, + -0.021075851126112488 + ], + [ + 109.34446766956162, + -0.021045000585865847 + ], + [ + 109.34446406661047, + -0.02103701524135433 + ], + [ + 109.34450597593032, + -0.02082283446785029 + ], + [ + 109.34450297769115, + -0.020762452859430153 + ], + [ + 109.34445499633418, + -0.02071112801446604 + ], + [ + 109.3443890215189, + -0.02068395561257891 + ], + [ + 109.3443290441597, + -0.020683954982518408 + ], + [ + 109.34428406013782, + -0.02078056502203853 + ], + [ + 109.34428106067189, + -0.02083792748180145 + ], + [ + 109.34430505114132, + -0.020883213913757945 + ], + [ + 109.34429821355205, + -0.02099104017181689 + ], + [ + 109.34429605304767, + -0.02102511050880618 + ], + [ + 109.34386721692188, + -0.020840942179172896 + ], + [ + 109.34389568461452, + -0.020763325075439373 + ], + [ + 109.34393919177647, + -0.020644702872900194 + ], + [ + 109.34342938666786, + -0.020424304890362652 + ], + [ + 109.34341242005729, + -0.02045846592094597 + ], + [ + 109.34339939738352, + -0.020484686122036556 + ], + [ + 109.34342638674752, + -0.020526953483614253 + ], + [ + 109.34341738983699, + -0.02055714416145812 + ], + [ + 109.34321646681505, + -0.020457512525873404 + ], + [ + 109.34322546412483, + -0.020388073851197203 + ], + [ + 109.34276963930225, + -0.020113333195015123 + ], + [ + 109.34251773682621, + -0.019892938086205146 + ], + [ + 109.34256301605072, + -0.01980047635990528 + ], + [ + 109.34249974471722, + -0.019784251176648917 + ], + [ + 109.34248175137652, + -0.019799346374340335 + ], + [ + 109.34224184350026, + -0.01966650464268207 + ], + [ + 109.34214887702086, + -0.019835571925633542 + ], + [ + 109.3420199266283, + -0.019757074674046585 + ], + [ + 109.3420919012512, + -0.01956687366346178 + ], + [ + 109.34198694155953, + -0.019509510194337325 + ], + [ + 109.34201393189937, + -0.019452148036786212 + ], + [ + 109.34207090996905, + -0.019488377501773835 + ], + [ + 109.34210788123119, + -0.01941610974351577 + ], + [ + 109.34214273812182, + -0.019443926243035172 + ], + [ + 109.34216987335253, + -0.019400825300066395 + ], + [ + 109.34197195005453, + -0.019219678849857792 + ], + [ + 109.34186831902373, + -0.019221646316484365 + ], + [ + 109.34181301022434, + -0.019222696370805342 + ], + [ + 109.34165407119201, + -0.019144198880491736 + ], + [ + 109.34148613589646, + -0.0190324914991591 + ], + [ + 109.34124109122531, + -0.018891780717508654 + ], + [ + 109.34122534764415, + -0.01884423015367891 + ], + [ + 109.34116686987794, + -0.018837436677039722 + ], + [ + 109.34113763109178, + -0.01882385056526588 + ], + [ + 109.34109264904367, + -0.01872874931577596 + ], + [ + 109.34116798136303, + -0.01866807847767115 + ], + [ + 109.34119386143547, + -0.01864723528885934 + ], + [ + 109.34116912111153, + -0.018615534780024097 + ], + [ + 109.34112701972877, + -0.018621892140504213 + ], + [ + 109.34107915509492, + -0.018629119758910143 + ], + [ + 109.34092396497977, + -0.018518167342155452 + ], + [ + 109.34087673308255, + -0.01849778815241564 + ], + [ + 109.34082500276631, + -0.018490994750710407 + ], + [ + 109.34069230406837, + -0.018386835483622778 + ], + [ + 109.34081082367639, + -0.01821345442046701 + ], + [ + 109.3407182072281, + -0.018052624599996462 + ], + [ + 109.34061400410694, + -0.017877171479093738 + ], + [ + 109.34056507270813, + -0.01780393630543389 + ], + [ + 109.34059334732704, + -0.017757357870699788 + ], + [ + 109.34055061414637, + -0.017689428347028205 + ], + [ + 109.34048763842506, + -0.01764640599426748 + ], + [ + 109.34041332935388, + -0.01761179166111941 + ], + [ + 109.34033694623632, + -0.017576211206191152 + ], + [ + 109.34022898731551, + -0.01756488871907275 + ], + [ + 109.34011203177641, + -0.017558094760883394 + ], + [ + 109.34002881417058, + -0.01746752185774032 + ], + [ + 109.3400400602379, + -0.017429028789881423 + ], + [ + 109.3400423097403, + -0.017388271337981695 + ], + [ + 109.33997483624947, + -0.017286377064360908 + ], + [ + 109.3399388500725, + -0.01726826231656199 + ], + [ + 109.33988487039552, + -0.017288640576217103 + ], + [ + 109.33987587391056, + -0.017277318978007255 + ], + [ + 109.33989836570247, + -0.017238826010807305 + ], + [ + 109.3398916185981, + -0.017200332786663424 + ], + [ + 109.33984438692745, + -0.01715504629762596 + ], + [ + 109.33980165319753, + -0.017150517316852472 + ], + [ + 109.33979940371981, + -0.017189010460325806 + ], + [ + 109.33977466298018, + -0.017204860369508936 + ], + [ + 109.33970943781534, + -0.017198066887168694 + ], + [ + 109.33967795014841, + -0.017155044843481317 + ], + [ + 109.33965545912174, + -0.01710522996892054 + ], + [ + 109.33963521735596, + -0.01704182929389297 + ], + [ + 109.33957449108578, + -0.016964842449292212 + ], + [ + 109.33952276103058, + -0.016930877451420016 + ], + [ + 109.33942379857817, + -0.01693766950778392 + ], + [ + 109.33936756971568, + -0.016964840661168695 + ], + [ + 109.3392033835823, + -0.016792752212206344 + ], + [ + 109.33922362628836, + -0.0167452020213679 + ], + [ + 109.33919663651002, + -0.016749730397165194 + ], + [ + 109.33912916139616, + -0.01684030193899864 + ], + [ + 109.33893348688513, + -0.016711235005266997 + ], + [ + 109.33900546029497, + -0.01661839920341764 + ], + [ + 109.33905494125374, + -0.01664557125664697 + ], + [ + 109.33912016706036, + -0.016573114116047654 + ], + [ + 109.3391179183674, + -0.01651877082680134 + ], + [ + 109.33908643077059, + -0.016466691595237233 + ], + [ + 109.33888625865562, + -0.01625158116305939 + ], + [ + 109.33888276972412, + -0.0161887027303862 + ], + [ + 109.33884397081451, + -0.016138984438763576 + ], + [ + 109.33878954630484, + -0.01614515814231397 + ], + [ + 109.33867708992399, + -0.016040999305799233 + ], + [ + 109.33853989321115, + -0.0159051400485488 + ], + [ + 109.33849940940951, + -0.01580551042294652 + ], + [ + 109.3384814169559, + -0.015717202492469412 + ], + [ + 109.33842968742552, + -0.015617572784148747 + ], + [ + 109.33835030229386, + -0.015448084951227854 + ], + [ + 109.3382266005666, + -0.015294111447977387 + ], + [ + 109.33815912670326, + -0.015235239074551073 + ], + [ + 109.33806466287376, + -0.015208066720313827 + ], + [ + 109.3380376732362, + -0.015196745002654593 + ], + [ + 109.33797244829579, + -0.015167308576425982 + ], + [ + 109.33791172143704, + -0.015165043805230304 + ], + [ + 109.33787573541709, + -0.015128814703103486 + ], + [ + 109.3378217561982, + -0.015099378368112652 + ], + [ + 109.33777227523267, + -0.015074470672037946 + ], + [ + 109.33774078758597, + -0.015026920101866103 + ], + [ + 109.33768006081985, + -0.015013333828968821 + ], + [ + 109.33765532058581, + -0.014968047614756848 + ], + [ + 109.33769849108532, + -0.014931320196809131 + ], + [ + 109.3376305804862, + -0.014904646992001929 + ], + [ + 109.33758559787721, + -0.01487068213269924 + ], + [ + 109.33752262202263, + -0.01485030294535473 + ], + [ + 109.33743715485811, + -0.014814073482238942 + ], + [ + 109.3374011691097, + -0.014741615580079656 + ], + [ + 109.33736518332589, + -0.014673686282377339 + ], + [ + 109.3373426923144, + -0.01461707859207932 + ], + [ + 109.33719200097391, + -0.014449519216431112 + ], + [ + 109.3371515169807, + -0.014370268395002139 + ], + [ + 109.33711103270791, + -0.014329510687239347 + ], + [ + 109.3370300635926, + -0.014327245794819771 + ], + [ + 109.3369918282358, + -0.01431818831349748 + ], + [ + 109.33694684548497, + -0.014304602182362024 + ], + [ + 109.33683888593261, + -0.014404230607602634 + ], + [ + 109.33676241506362, + -0.014408758645976018 + ], + [ + 109.3366319655572, + -0.014306864184383728 + ], + [ + 109.33664321146298, + -0.014279692665629565 + ], + [ + 109.33647002850441, + -0.014143833411423142 + ], + [ + 109.33664771235024, + -0.013912876086064447 + ], + [ + 109.33671743540577, + -0.013962691184470674 + ], + [ + 109.33673767812975, + -0.013899290923634367 + ], + [ + 109.33671518730846, + -0.01381324735826898 + ], + [ + 109.3366657064337, + -0.013777018207955039 + ], + [ + 109.33663871817838, + -0.013566438112120221 + ], + [ + 109.33658923759374, + -0.013487187269283898 + ], + [ + 109.3365487536513, + -0.013396614992188223 + ], + [ + 109.33652176452672, + -0.013310571410311682 + ], + [ + 109.33649477511189, + -0.013267549528862626 + ], + [ + 109.33647678212807, + -0.01324490640825016 + ], + [ + 109.33649477545906, + -0.013215470631181427 + ], + [ + 109.33651787792007, + -0.013181679702637398 + ], + [ + 109.33658024330305, + -0.01314754220757709 + ], + [ + 109.33664546909318, + -0.013045649141855117 + ], + [ + 109.33646930097447, + -0.013167296473854248 + ], + [ + 109.33623162464819, + -0.013398877123657851 + ], + [ + 109.33593923785536, + -0.013161123701970772 + ], + [ + 109.33590325162587, + -0.01315659486177605 + ], + [ + 109.33585826910208, + -0.013111308576373671 + ], + [ + 109.33580999997024, + -0.01306878787166591 + ], + [ + 109.33575031095586, + -0.013016207294121385 + ], + [ + 109.33573199547105, + -0.013032388172927498 + ], + [ + 109.33564010240826, + -0.01311357141496539 + ], + [ + 109.33552764603724, + -0.013011677216824204 + ], + [ + 109.33558688139986, + -0.012948904804820527 + ], + [ + 109.33561311397976, + -0.012921105829399378 + ], + [ + 109.3354669205922, + -0.012803361331868917 + ], + [ + 109.33543725363035, + -0.012839515523223705 + ], + [ + 109.3353814525731, + -0.012907518509332229 + ], + [ + 109.33519702420833, + -0.012728637720088932 + ], + [ + 109.33508156898817, + -0.01262699513625886 + ], + [ + 109.33511755559186, + -0.012570387910669159 + ], + [ + 109.33457776521462, + -0.01205186027517017 + ], + [ + 109.33461118742096, + -0.012006647084497482 + ], + [ + 109.33464973827421, + -0.011954495914315064 + ], + [ + 109.33455996261907, + -0.01187857519753598 + ], + [ + 109.33442482544957, + -0.011764293550097627 + ], + [ + 109.33442609570214, + -0.011672258403342822 + ], + [ + 109.33442707554923, + -0.011601264142913108 + ], + [ + 109.33441358148916, + -0.011467670510830301 + ], + [ + 109.33439109069401, + -0.0113635126955379 + ], + [ + 109.33434610835803, + -0.011284262024809043 + ], + [ + 109.33428538198064, + -0.011216332754295268 + ], + [ + 109.33421790795562, + -0.011189160800388205 + ], + [ + 109.33410545104779, + -0.011180102968830891 + ], + [ + 109.33407396329166, + -0.011146138332418945 + ], + [ + 109.33411219894823, + -0.01109405971586589 + ], + [ + 109.33407171478545, + -0.011032923464679396 + ], + [ + 109.3340118280435, + -0.011098694011343332 + ], + [ + 109.33393434785418, + -0.011012218340907366 + ], + [ + 109.33389113579642, + -0.010970306434695595 + ], + [ + 109.33389403354491, + -0.010899328946165653 + ], + [ + 109.33367361919204, + -0.010670633757845834 + ], + [ + 109.33363538415188, + -0.010611761837877265 + ], + [ + 109.33355441541899, + -0.010564211172117897 + ], + [ + 109.3335026852588, + -0.010559682299910719 + ], + [ + 109.33351393132352, + -0.010489489170549255 + ], + [ + 109.33330926075791, + -0.010294758598394124 + ], + [ + 109.33328227105272, + -0.01030381564111199 + ], + [ + 109.3330798496198, + -0.010111349418863703 + ], + [ + 109.33306065235297, + -0.01012552214013642 + ], + [ + 109.3330461124239, + -0.010136256499478361 + ], + [ + 109.33294715079744, + -0.01005021274624725 + ], + [ + 109.33299888131027, + -0.009982284136420515 + ], + [ + 109.33296964268939, + -0.009948319549829141 + ], + [ + 109.33296964295015, + -0.009896240745900443 + ], + [ + 109.33296289587476, + -0.009828311837312904 + ], + [ + 109.33291791317555, + -0.009816990132402958 + ], + [ + 109.3329044184276, + -0.009801139994402823 + ], + [ + 109.33290666765483, + -0.009783025639701784 + ], + [ + 109.3330226700517, + -0.009453682985961873 + ], + [ + 109.33306532092973, + -0.00933560435232449 + ], + [ + 109.33308131552636, + -0.009179955047673813 + ], + [ + 109.33312396682864, + -0.0089652664418077 + ], + [ + 109.33316661801364, + -0.00877204670767229 + ], + [ + 109.33320617678476, + -0.008560747832799368 + ], + [ + 109.33324125756705, + -0.008428544932092453 + ], + [ + 109.33325192084821, + -0.008262161142428023 + ], + [ + 109.33335788181229, + -0.007877566675011344 + ], + [ + 109.33340586391654, + -0.007753785335696484 + ], + [ + 109.33343885218538, + -0.007518298644333354 + ], + [ + 109.33347483872902, + -0.00743074598825379 + ], + [ + 109.33345623847433, + -0.007053999588108622 + ], + [ + 109.33343041035138, + -0.0071398120874433405 + ], + [ + 109.33341135784137, + -0.00716858187826331 + ], + [ + 109.33341304407375, + -0.007338404109674653 + ], + [ + 109.33341304374487, + -0.0074267116667845684 + ], + [ + 109.33338942731815, + -0.00755238002223139 + ], + [ + 109.333364124122, + -0.007654273257144275 + ], + [ + 109.33321888228927, + -0.008194766790206436 + ], + [ + 109.33319416034547, + -0.008277468351085235 + ], + [ + 109.33316900409721, + -0.008384187098962428 + ], + [ + 109.3331060316762, + -0.008593389005135512 + ], + [ + 109.33308072876382, + -0.008618862223382746 + ], + [ + 109.33306892054532, + -0.008674903495802768 + ], + [ + 109.33306723333341, + -0.008756418142070947 + ], + [ + 109.33303855550564, + -0.009050210407012893 + ], + [ + 109.33299132260633, + -0.009267582589823086 + ], + [ + 109.33293247687024, + -0.009484222271086109 + ], + [ + 109.33292466034686, + -0.009505577721713846 + ], + [ + 109.3328923322263, + -0.009593296478779281 + ], + [ + 109.3328644728526, + -0.009668358970661602 + ], + [ + 109.33283842597346, + -0.009731655855983717 + ], + [ + 109.33280878534323, + -0.009788640215903742 + ], + [ + 109.33278362909714, + -0.009834765230100542 + ], + [ + 109.33276566034758, + -0.009859171477891383 + ], + [ + 109.33275667597275, + -0.009862796470676439 + ], + [ + 109.33275578535361, + -0.00986279646628501 + ], + [ + 109.33273692598056, + -0.009936046467083228 + ], + [ + 109.33268768557637, + -0.009977437788823481 + ], + [ + 109.33250213103729, + -0.010116691001634148 + ], + [ + 109.33237899137082, + -0.00999951309098914 + ], + [ + 109.33247851603264, + -0.009931584742444413 + ], + [ + 109.33235200273525, + -0.009797424614580216 + ], + [ + 109.33238573997623, + -0.009760063913764067 + ], + [ + 109.3324430931242, + -0.009729496214731608 + ], + [ + 109.33249617352637, + -0.009650479555739201 + ], + [ + 109.33242285146153, + -0.00961231883791823 + ], + [ + 109.33234188215287, + -0.009688738399268875 + ], + [ + 109.33225585311614, + -0.009595335804264749 + ], + [ + 109.3322879036462, + -0.00952740711060549 + ], + [ + 109.3322609141164, + -0.00950363188151119 + ], + [ + 109.33231995438469, + -0.009415324660839254 + ], + [ + 109.33217657247249, + -0.009294750266532927 + ], + [ + 109.33211415896474, + -0.009286258864070532 + ], + [ + 109.33206524032721, + -0.009267578199753783 + ], + [ + 109.33203319030311, + -0.009230217184609103 + ], + [ + 109.33200451396031, + -0.009197950849637263 + ], + [ + 109.33195896905318, + -0.009174175541429982 + ], + [ + 109.33194547448439, + -0.009119832406808713 + ], + [ + 109.3318898084866, + -0.009090962391258157 + ], + [ + 109.33186113212811, + -0.00906209250246784 + ], + [ + 109.33183751651988, + -0.008990767115841212 + ], + [ + 109.33178691132547, + -0.008907554061890066 + ], + [ + 109.33173124523773, + -0.008899062704180727 + ], + [ + 109.33166545819033, + -0.008856606886217139 + ], + [ + 109.33159461057272, + -0.008817547489574681 + ], + [ + 109.33159967131026, + -0.008776790216169466 + ], + [ + 109.33161653998965, + -0.008741127657450672 + ], + [ + 109.33161147960521, + -0.008702068558980154 + ], + [ + 109.33157774278519, + -0.008651121789019018 + ], + [ + 109.33148834007362, + -0.008547529937774937 + ], + [ + 109.33142086629873, + -0.008476204380670998 + ], + [ + 109.33136182663178, + -0.008440541494626402 + ], + [ + 109.33129603939933, + -0.008442239430807056 + ], + [ + 109.33126230247011, + -0.00841676597926205 + ], + [ + 109.33117289937354, + -0.008404878051917775 + ], + [ + 109.33117121231932, + -0.008452428213181939 + ], + [ + 109.33113578841026, + -0.00845752272071638 + ], + [ + 109.33104469872077, + -0.00838619707813799 + ], + [ + 109.33105988052239, + -0.008353930959346062 + ], + [ + 109.33108855696557, + -0.008362422183191814 + ], + [ + 109.33111723345934, + -0.008359025865436366 + ], + [ + 109.3311239809594, + -0.008336949030883662 + ], + [ + 109.33110879945782, + -0.008297889900322219 + ], + [ + 109.33086251995667, + -0.008095800669713623 + ], + [ + 109.33073937929548, + -0.008218071994165637 + ], + [ + 109.330644915997, + -0.008126367724903057 + ], + [ + 109.33068708747557, + -0.00807881774365709 + ], + [ + 109.33070395592685, + -0.008094101792528615 + ], + [ + 109.33076805663293, + -0.00800749284054439 + ], + [ + 109.33071576447847, + -0.00794805492932258 + ], + [ + 109.33068877482224, + -0.007956545918870616 + ], + [ + 109.33066515884842, + -0.007970131581599047 + ], + [ + 109.33063310866623, + -0.007971829670596136 + ], + [ + 109.33060105855968, + -0.007954847341694618 + ], + [ + 109.33056394796773, + -0.007920882794987432 + ], + [ + 109.33054370575944, + -0.007919184493130307 + ], + [ + 109.33049310043943, + -0.007864841257003298 + ], + [ + 109.33047791904737, + -0.00779691240779078 + ], + [ + 109.3304796060854, + -0.007749362262841618 + ], + [ + 109.33048466684444, + -0.007696717471982456 + ], + [ + 109.33047791958464, + -0.007659356611942495 + ], + [ + 109.33045936426763, + -0.007647469001361672 + ], + [ + 109.33045093021447, + -0.0075948241582938555 + ], + [ + 109.33044249605622, + -0.007569350830429803 + ], + [ + 109.33040369846704, + -0.0075727471193056724 + ], + [ + 109.33037670890762, + -0.0075574630380997624 + ], + [ + 109.3303918908387, + -0.007486137872530963 + ], + [ + 109.33037670931978, + -0.007448776982815045 + ], + [ + 109.33036827514081, + -0.00742839831536306 + ], + [ + 109.33036703694354, + -0.007410054558784974 + ], + [ + 109.33032053534228, + -0.00737312460470939 + ], + [ + 109.33030046402857, + -0.007362834335040467 + ], + [ + 109.3302603168764, + -0.007374054880212533 + ], + [ + 109.33019284288183, + -0.0073621670891666335 + ], + [ + 109.33015573220565, + -0.007350279413237454 + ], + [ + 109.33012705561387, + -0.00738424369321168 + ], + [ + 109.33010343967638, + -0.007391036481368866 + ], + [ + 109.3300325920742, + -0.007355373607557042 + ], + [ + 109.32992463405179, + -0.007239894293775685 + ], + [ + 109.3298706546552, + -0.007287444230396573 + ], + [ + 109.32988414941049, + -0.007301030034277589 + ], + [ + 109.32984197799951, + -0.007340088916943569 + ], + [ + 109.32968341466272, + -0.007171964638574682 + ], + [ + 109.32975549374171, + -0.007083436170345403 + ], + [ + 109.32973107756948, + -0.007070918375154847 + ], + [ + 109.32957714394303, + -0.0069308171616225545 + ], + [ + 109.32946581219716, + -0.006822130761665481 + ], + [ + 109.32943376194764, + -0.006849302150325089 + ], + [ + 109.3294135198089, + -0.006830621672747024 + ], + [ + 109.3293780959888, + -0.006820432236285252 + ], + [ + 109.32935616690662, + -0.006828923253268265 + ], + [ + 109.32933513241314, + -0.006852024461075196 + ], + [ + 109.32931905606102, + -0.006869680372031083 + ], + [ + 109.32924989538681, + -0.006817035350907331 + ], + [ + 109.32899518177217, + -0.006599662504200041 + ], + [ + 109.32900684552077, + -0.006583516878135199 + ], + [ + 109.32903566635655, + -0.006543621432408625 + ], + [ + 109.32900361632561, + -0.006507958738089564 + ], + [ + 109.32903735350787, + -0.00645191764183737 + ], + [ + 109.3288467399938, + -0.00629058627456594 + ], + [ + 109.3288096292559, + -0.006304171900923078 + ], + [ + 109.32878095286999, + -0.006285491407773171 + ], + [ + 109.32877251873448, + -0.006249828797985871 + ], + [ + 109.32876577143209, + -0.00621926084854567 + ], + [ + 109.32873709503001, + -0.006205675011651638 + ], + [ + 109.32870841856953, + -0.006210769574915151 + ], + [ + 109.328679742083, + -0.006224355228682381 + ], + [ + 109.32864607514962, + -0.006231286882663797 + ], + [ + 109.32862484784785, + -0.00621056209035164 + ], + [ + 109.32859366034718, + -0.006180108968128835 + ], + [ + 109.32857743687201, + -0.006170066136355051 + ], + [ + 109.32857853139897, + -0.0061309529130105555 + ], + [ + 109.3285363602764, + -0.0060935919841700655 + ], + [ + 109.32852117870928, + -0.0060681186667574894 + ], + [ + 109.328480694374, + -0.0060511363611156005 + ], + [ + 109.32844358367588, + -0.00605453268187407 + ], + [ + 109.32842047309701, + -0.006072900722411023 + ], + [ + 109.32834130662256, + -0.006023894239083018 + ], + [ + 109.3283423728832, + -0.005998491178821124 + ], + [ + 109.32830863599432, + -0.005966224936570432 + ], + [ + 109.328306949288, + -0.005918674832664596 + ], + [ + 109.32832213103835, + -0.005883012304385376 + ], + [ + 109.32833225230532, + -0.005825272928559102 + ], + [ + 109.32833899981237, + -0.005787912156221234 + ], + [ + 109.32842165572723, + -0.00568601932680606 + ], + [ + 109.32845539301567, + -0.00558072991278186 + ], + [ + 109.32838454518655, + -0.005635072682164424 + ], + [ + 109.32833731329289, + -0.005674131556004226 + ], + [ + 109.32830357623818, + -0.005697906507179266 + ], + [ + 109.32823685208541, + -0.005660545521674782 + ], + [ + 109.32820986250255, + -0.005658847225594338 + ], + [ + 109.32813058085601, + -0.005565445024281188 + ], + [ + 109.32803105670101, + -0.005585823351065605 + ], + [ + 109.32798213803545, + -0.0056011071686846645 + ], + [ + 109.32798213770477, + -0.005716585960686736 + ], + [ + 109.32782357401548, + -0.005677526495730502 + ], + [ + 109.32768693949774, + -0.005594313451426756 + ], + [ + 109.3277206766409, + -0.005534875942691546 + ], + [ + 109.32776116098864, + -0.005543367144344109 + ], + [ + 109.32778815064147, + -0.0055178939606884 + ], + [ + 109.32787924067503, + -0.005448267303423183 + ], + [ + 109.32783369588483, + -0.005400717088969836 + ], + [ + 109.32776284801281, + -0.005480533107614701 + ], + [ + 109.32777802961934, + -0.005492420671753138 + ], + [ + 109.32774766627405, + -0.005516195628749958 + ], + [ + 109.32762621339334, + -0.005432982641324637 + ], + [ + 109.32731583400077, + -0.0051544741964843115 + ], + [ + 109.32738836877019, + -0.005044090282490384 + ], + [ + 109.32739849011885, + -0.004942197287440208 + ], + [ + 109.32732932946602, + -0.004887854169316299 + ], + [ + 109.32728715834723, + -0.004850493291213126 + ], + [ + 109.32726860317624, + -0.004784262785628936 + ], + [ + 109.32702401060469, + -0.00460255299874733 + ], + [ + 109.32656418908782, + -0.004268286338618677 + ], + [ + 109.32626030710452, + -0.00403078701615359 + ], + [ + 109.3260563862822, + -0.0038858721748306862 + ], + [ + 109.32606038487648, + -0.0038134149730155048 + ], + [ + 109.32596442230422, + -0.003656424171532691 + ], + [ + 109.32548061019399, + -0.003306213519899243 + ], + [ + 109.32547535586204, + -0.003236023465157063 + ], + [ + 109.32534866149436, + -0.003181425924485342 + ], + [ + 109.32523670500814, + -0.003084816169320944 + ], + [ + 109.32514074231585, + -0.0029962572470910217 + ], + [ + 109.32509675936646, + -0.002988206382269284 + ], + [ + 109.32507276865435, + -0.00299223174394246 + ], + [ + 109.32494791982435, + -0.003120386106864413 + ], + [ + 109.32486884733744, + -0.0032015521306321426 + ], + [ + 109.32461294691487, + -0.002947951663849877 + ], + [ + 109.32462287269603, + -0.0028946161501105937 + ], + [ + 109.32453697658312, + -0.002799011845153886 + ], + [ + 109.3246169457001, + -0.002714478611418564 + ], + [ + 109.32440902649368, + -0.0025776148189399044 + ], + [ + 109.32435704655285, + -0.002650575071076156 + ], + [ + 109.3239761945863, + -0.0024090507780921504 + ], + [ + 109.32334344070397, + -0.001935059741866251 + ], + [ + 109.32338842328464, + -0.0018867550464278262 + ], + [ + 109.3234154128539, + -0.0018354312854448743 + ], + [ + 109.32343040709027, + -0.0017720313267048638 + ], + [ + 109.32343040714898, + -0.0017056123074920243 + ], + [ + 109.32340641650472, + -0.0016573075457928528 + ], + [ + 109.32338630129519, + -0.001605562344579694 + ], + [ + 109.32333444449796, + -0.001599945607035625 + ], + [ + 109.32323848176311, + -0.0015999455280650947 + ], + [ + 109.32315751317697, + -0.0016391930581651358 + ], + [ + 109.32313652131506, + -0.001657307315723199 + ], + [ + 109.32307054694868, + -0.001648250122165074 + ], + [ + 109.3230135691169, + -0.001605983434302952 + ], + [ + 109.32296558777642, + -0.0015818310298195306 + ], + [ + 109.32295359248644, + -0.0015184310627134234 + ], + [ + 109.32293859835512, + -0.0014610691852716272 + ], + [ + 109.32274367412332, + -0.0014218214503177293 + ], + [ + 109.32268369742516, + -0.0014308785419854756 + ], + [ + 109.32264471256113, + -0.0014520118290059006 + ], + [ + 109.32237481758278, + -0.0013010593929322814 + ], + [ + 109.32238081530305, + -0.0012195451900732421 + ], + [ + 109.3223178398322, + -0.0011470880801714655 + ], + [ + 109.32196097870178, + -0.0010021737552404742 + ], + [ + 109.32183758170606, + -0.0009387917238195796 + ], + [ + 109.32166709312004, + -0.0008512214242143897 + ], + [ + 109.32171649313325, + -0.0007415830063253993 + ], + [ + 109.32179904187979, + -0.0005583742340056238 + ], + [ + 109.32193099052277, + -0.0006278122838769784 + ], + [ + 109.32189800337858, + -0.0005523361745583278 + ], + [ + 109.32185601972778, + -0.0004949743288490603 + ], + [ + 109.32178404774592, + -0.00043157439132398304 + ], + [ + 109.3215891236169, + -0.0003530792178489307 + ], + [ + 109.32148416448237, + -0.0002776031161571295 + ], + [ + 109.3214151913374, + -0.0002353365015175805 + ], + [ + 109.32137620652144, + -0.00016589850466631082 + ], + [ + 109.32133722170327, + -0.00010551763980253128 + ], + [ + 109.3212172684099, + -0.00008136529014288557 + ], + [ + 109.32117228592496, + -0.00011155571646041754 + ], + [ + 109.32112015316277, + -0.00018673564664358852 + ], + [ + 109.32095037231062, + -0.0004315742060532019 + ], + [ + 109.32088139919205, + -0.0003681743010045151 + ], + [ + 109.32104123587094, + -0.00011901736617737487 + ], + [ + 109.32106732680234, + -0.0000783462412128485 + ], + [ + 109.32098488257182, + -0.00007066106547703584 + ], + [ + 109.32090538987958, + -0.0000632510226520903 + ], + [ + 109.32084241441277, + -0.00007230814754248895 + ], + [ + 109.32077344128392, + -0.00009947952476713114 + ], + [ + 109.32070746698925, + -0.00011759377397766889 + ], + [ + 109.32060850554116, + -0.00024439352959064795 + ], + [ + 109.32028463179952, + -0.00003607962945662545 + ], + [ + 109.32023065284268, + -0.0001085366210019333 + ], + [ + 109.31996075809631, + 0.00006354872741870308 + ], + [ + 109.31999974288415, + 0.00017525324554708917 + ], + [ + 109.31989178498803, + 0.00022657692986154454 + ], + [ + 109.31983480722162, + 0.0001541199454569085 + ], + [ + 109.31948694293054, + 0.0003594146512930953 + ], + [ + 109.31884519345569, + 0.0005616901587159562 + ], + [ + 109.31876722389829, + 0.0006130138004387185 + ], + [ + 109.31871024614232, + 0.00066131840624591 + ], + [ + 109.3185872941695, + 0.0006975468298641176 + ], + [ + 109.31860228829851, + 0.0007307562623003932 + ], + [ + 109.31850632579301, + 0.0007428323808106884 + ], + [ + 109.31846134338899, + 0.0006975467845468316 + ], + [ + 109.31843735278154, + 0.0006492421589768518 + ], + [ + 109.3184133621677, + 0.0006190517656708387 + ], + [ + 109.31836538093597, + 0.0005707471350862946 + ], + [ + 109.31834139032172, + 0.000540556743905941 + ], + [ + 109.3182514255338, + 0.0003413001926822057 + ], + [ + 109.31800252281687, + 0.0005043281946906286 + ], + [ + 109.31812847348489, + 0.0007609464645053588 + ], + [ + 109.31808349106106, + 0.0007911368269788484 + ], + [ + 109.3181314722592, + 0.0008877460653795757 + ], + [ + 109.3178225929758, + 0.0010417168418390484 + ], + [ + 109.31781128393064, + 0.001123690397328828 + ], + [ + 109.31780759878012, + 0.0011504021868112809 + ], + [ + 109.31775361987874, + 0.001177573492238385 + ], + [ + 109.31768764567863, + 0.0011926686391296945 + ], + [ + 109.31767438768782, + 0.0011724174712507713 + ], + [ + 109.31760067977919, + 0.0010598309460983306 + ], + [ + 109.31631307251178, + 0.0016093505658076024 + ], + [ + 109.31610693101398, + 0.001702381396777835 + ], + [ + 109.31560934826548, + 0.0018168806249608024 + ], + [ + 109.31560223986848, + 0.0018955991381768194 + ], + [ + 109.31541742341994, + 0.001967161241184902 + ], + [ + 109.3153676651673, + 0.0019671611906020457 + ], + [ + 109.31528236530868, + 0.001967161103885106 + ], + [ + 109.31521128206707, + 0.001995785942163883 + ], + [ + 109.315175740425, + 0.0020315670430624076 + ], + [ + 109.31368432585602, + 0.002500968912438016 + ], + [ + 109.31332046890073, + 0.0025693998015538545 + ], + [ + 109.31316852862157, + 0.002621729458880533 + ], + [ + 109.31308856040816, + 0.00237618155906629 + ], + [ + 109.31269271609447, + 0.002500967629175782 + ], + [ + 109.3127606889764, + 0.002762616958602548 + ], + [ + 109.31260175148479, + 0.002819852498161453 + ], + [ + 109.31250953781777, + 0.002810795275589541 + ], + [ + 109.31240607851997, + 0.002847023474938313 + ], + [ + 109.31232735955554, + 0.0028311734564950854 + ], + [ + 109.31228237730456, + 0.002813059216802935 + ], + [ + 109.3115131801722, + 0.0030508065323249967 + ], + [ + 109.3114681980395, + 0.002969292713635651 + ], + [ + 109.31127477420547, + 0.003057598967169297 + ], + [ + 109.31101737563573, + 0.0031450245491031815 + ], + [ + 109.3110706876548, + 0.0033114065720841537 + ], + [ + 109.31062286415494, + 0.0034724205432063748 + ], + [ + 109.31045226472533, + 0.003547560441324146 + ], + [ + 109.31033497761084, + 0.0036065989546650683 + ], + [ + 109.3102123592747, + 0.0036656374507161834 + ], + [ + 109.3099991100307, + 0.0037568787017171683 + ], + [ + 109.30990847930543, + 0.0036924726532239346 + ], + [ + 109.3096525802683, + 0.003794448113722817 + ], + [ + 109.30973051971378, + 0.00396112541818537 + ], + [ + 109.30967256383552, + 0.003970293510845494 + ], + [ + 109.30959393665172, + 0.0038642210148841687 + ], + [ + 109.30952463077622, + 0.0038373851013615767 + ], + [ + 109.30919409469526, + 0.003912524599658081 + ], + [ + 109.30853835390579, + 0.004068170681989765 + ], + [ + 109.30856013729567, + 0.0041183502393600056 + ], + [ + 109.3088528944123, + 0.00479273700870328 + ], + [ + 109.30887955028967, + 0.004889345831371351 + ], + [ + 109.3088475627191, + 0.004980587351970525 + ], + [ + 109.30877825677629, + 0.004996688631458666 + ], + [ + 109.30871428218218, + 0.004964485549601156 + ], + [ + 109.3083939581445, + 0.004183978342707294 + ], + [ + 109.30800689734646, + 0.004253503982649498 + ], + [ + 109.30796791278696, + 0.004256522918780223 + ], + [ + 109.30794595288909, + 0.004219236280934818 + ], + [ + 109.30776699312504, + 0.003915373000901617 + ], + [ + 109.30760505707542, + 0.00403311451565713 + ], + [ + 109.30771769096856, + 0.0042430132464267405 + ], + [ + 109.30774600064534, + 0.0042957697126861085 + ], + [ + 109.30764404102746, + 0.0043108645938164 + ], + [ + 109.3075600743015, + 0.004316902449015525 + ], + [ + 109.307353156246, + 0.004362187303922169 + ], + [ + 109.30714623825244, + 0.0043893580235228645 + ], + [ + 109.30708926079332, + 0.004416529081144137 + ], + [ + 109.30691532971062, + 0.004455775949437291 + ], + [ + 109.30682236651683, + 0.004492003979253777 + ], + [ + 109.30672094930516, + 0.004468328203054169 + ], + [ + 109.30648087908995, + 0.004553781664935093 + ], + [ + 109.30577204512063, + 0.004734910938710529 + ], + [ + 109.30576978388036, + 0.004757675173717978 + ], + [ + 109.30576678493871, + 0.004812017503911405 + ], + [ + 109.30572480157807, + 0.004830131511053707 + ], + [ + 109.305640834964, + 0.004824093263158225 + ], + [ + 109.30560484925954, + 0.004827112191420529 + ], + [ + 109.30557486108918, + 0.004863340337637742 + ], + [ + 109.30550588844578, + 0.0048874923100858305 + ], + [ + 109.30538893487284, + 0.004914663176409046 + ], + [ + 109.30536194563398, + 0.004902587034988992 + ], + [ + 109.3053121369043, + 0.004862085567653752 + ], + [ + 109.3050020821978, + 0.004951703533138335 + ], + [ + 109.30492500465628, + 0.004963107414471536 + ], + [ + 109.30486714265551, + 0.004812015253372499 + ], + [ + 109.30462124024113, + 0.0049176802316059556 + ], + [ + 109.30454627041019, + 0.004784843298854359 + ], + [ + 109.30442032046345, + 0.0048180521685136975 + ], + [ + 109.30449067488637, + 0.005027367954810271 + ], + [ + 109.30437533767999, + 0.005089763547284881 + ], + [ + 109.30431836027715, + 0.005122972577785563 + ], + [ + 109.3042373925313, + 0.0051169343292887925 + ], + [ + 109.30420740456714, + 0.005080706053751559 + ], + [ + 109.30416900320327, + 0.005042045682034899 + ], + [ + 109.30388425407956, + 0.005050047292702 + ], + [ + 109.30387006487555, + 0.005049843302175257 + ], + [ + 109.30385054687193, + 0.005014286764010796 + ], + [ + 109.30382955529684, + 0.004990134583492405 + ], + [ + 109.30378157438953, + 0.004999191505852859 + ], + [ + 109.30372759583433, + 0.005023343489874582 + ], + [ + 109.30367083987777, + 0.005046979152494979 + ], + [ + 109.30347569608684, + 0.005074666089124322 + ], + [ + 109.3034631507656, + 0.005043993319061561 + ], + [ + 109.30343371308582, + 0.004972019465023993 + ], + [ + 109.3030738564348, + 0.005035344166378463 + ], + [ + 109.30281059782732, + 0.005035344162897223 + ], + [ + 109.30273187292002, + 0.005044598098395311 + ], + [ + 109.3027289941088, + 0.00497503665505829 + ], + [ + 109.30249808618582, + 0.004999188165910398 + ], + [ + 109.30237813405337, + 0.005002206867062579 + ], + [ + 109.30232715448777, + 0.004968997587362306 + ], + [ + 109.30229716646625, + 0.004965978496461768 + ], + [ + 109.30223419160644, + 0.004965978333590935 + ], + [ + 109.30221319994067, + 0.004984092358345551 + ], + [ + 109.30212040287249, + 0.005035218583590782 + ], + [ + 109.30159463562023, + 0.004994438913253421 + ], + [ + 109.30135508219773, + 0.0049717035423342566 + ], + [ + 109.30101908220695, + 0.004998062909578481 + ], + [ + 109.30093683221142, + 0.0050045160368893 + ], + [ + 109.3006020139314, + 0.005166781907452538 + ], + [ + 109.30025879618621, + 0.005377415939229459 + ], + [ + 109.30006505751521, + 0.0054485111189407675 + ], + [ + 109.29989312634544, + 0.005452535976451194 + ], + [ + 109.29981315834739, + 0.005460586440783727 + ], + [ + 109.29976517742735, + 0.0055088904532746945 + ], + [ + 109.2997371884538, + 0.005573295904198739 + ], + [ + 109.29965722025223, + 0.005653802582475922 + ], + [ + 109.29956925536091, + 0.005698081121956533 + ], + [ + 109.29947329365274, + 0.005750410323700383 + ], + [ + 109.2994053207327, + 0.0058027396049498 + ], + [ + 109.29937333323478, + 0.005907398476579451 + ], + [ + 109.29933334912234, + 0.005951677146735664 + ], + [ + 109.29924538417671, + 0.00601608238838453 + ], + [ + 109.2991534209237, + 0.0060523102000503695 + ], + [ + 109.29911743519708, + 0.006100614219298552 + ], + [ + 109.29908144935487, + 0.0061851463356425605 + ], + [ + 109.299074441573, + 0.006225500412534756 + ], + [ + 109.29885334782419, + 0.0063393129202218915 + ], + [ + 109.2986946134521, + 0.0064054535386057375 + ], + [ + 109.29854454128774, + 0.00652462695798841 + ], + [ + 109.29795989714773, + 0.006820643578860485 + ], + [ + 109.29752507119011, + 0.0069806493371920905 + ], + [ + 109.29754306374475, + 0.00704102951920675 + ], + [ + 109.2971382255638, + 0.00726745341917267 + ], + [ + 109.29697928907956, + 0.007364060973641102 + ], + [ + 109.2967813682427, + 0.0074697253738973905 + ], + [ + 109.29674838181806, + 0.007388212121769677 + ], + [ + 109.29667940943676, + 0.00741840190011633 + ], + [ + 109.2966104365192, + 0.0075874658764449755 + ], + [ + 109.29623858523475, + 0.007804832673026597 + ], + [ + 109.29624246592563, + 0.007857577987743844 + ], + [ + 109.29624458249182, + 0.007886345799415787 + ], + [ + 109.29623258715812, + 0.007925592798765544 + ], + [ + 109.29620859677385, + 0.007934649710562158 + ], + [ + 109.29617860886482, + 0.00792861157884332 + ], + [ + 109.29614552277837, + 0.00791278960282749 + ], + [ + 109.2960007769671, + 0.007995226057289345 + ], + [ + 109.29587573056241, + 0.007976914374695153 + ], + [ + 109.2956988016545, + 0.00801012266885994 + ], + [ + 109.29564182500526, + 0.007910495337094243 + ], + [ + 109.29516801535233, + 0.008031253460216358 + ], + [ + 109.29508404894642, + 0.008088614144421224 + ], + [ + 109.29501207778453, + 0.008127860863746773 + ], + [ + 109.29494010673885, + 0.008139936564639755 + ], + [ + 109.29486513698063, + 0.008133898242235158 + ], + [ + 109.29480816002307, + 0.008115783990817846 + ], + [ + 109.29469420585981, + 0.008139935517620383 + ], + [ + 109.29458624934821, + 0.008148992061361712 + ], + [ + 109.29452927238643, + 0.008133896813120307 + ], + [ + 109.29449028820827, + 0.008109744639196713 + ], + [ + 109.29424138838122, + 0.008161066593456915 + ], + [ + 109.2942623794396, + 0.008272769706939846 + ], + [ + 109.29428337060921, + 0.008357301816762358 + ], + [ + 109.2942443863478, + 0.008354282645677284 + ], + [ + 109.29423539014576, + 0.008315035598550678 + ], + [ + 109.29422339520427, + 0.00826371253641324 + ], + [ + 109.29413942928385, + 0.008215408166122012 + ], + [ + 109.29407345597849, + 0.008197293880423116 + ], + [ + 109.29403147302048, + 0.008173141698112705 + ], + [ + 109.29391452020225, + 0.008176160197946463 + ], + [ + 109.29384254913371, + 0.008203330889796521 + ], + [ + 109.2937735766898, + 0.008269748590886248 + ], + [ + 109.29372859465285, + 0.008315033393171712 + ], + [ + 109.29370760278779, + 0.008393527295811484 + ], + [ + 109.29366262123304, + 0.008327109104781525 + ], + [ + 109.29358765193837, + 0.008224462792394378 + ], + [ + 109.29349169043927, + 0.008281823368341868 + ], + [ + 109.29350368541458, + 0.008324089412551614 + ], + [ + 109.29348869124757, + 0.008375412337374795 + ], + [ + 109.29347069866925, + 0.008339184265778125 + ], + [ + 109.29345270598672, + 0.008327108189757045 + ], + [ + 109.29340172650828, + 0.008342202963944672 + ], + [ + 109.29336574101474, + 0.008348240805269483 + ], + [ + 109.2933447495574, + 0.00833314571754939 + ], + [ + 109.29331476187592, + 0.008284841600402893 + ], + [ + 109.29325478603519, + 0.00829993633551636 + ], + [ + 109.29328177479606, + 0.008378430429267954 + ], + [ + 109.29327123844939, + 0.008443750414904297 + ], + [ + 109.29318616447175, + 0.008447311954320242 + ], + [ + 109.29311984037719, + 0.00833918273392689 + ], + [ + 109.29270900606392, + 0.008414655894568098 + ], + [ + 109.29266102558915, + 0.00838446570197255 + ], + [ + 109.2926130449845, + 0.008384465491269904 + ], + [ + 109.29258005820168, + 0.008411636328241875 + ], + [ + 109.29256370479996, + 0.008473373796062207 + ], + [ + 109.29255606750439, + 0.008502206160986184 + ], + [ + 109.29250808686255, + 0.008511262940948281 + ], + [ + 109.29248771596295, + 0.008476556424805605 + ], + [ + 109.29243011896672, + 0.00837842669246795 + ], + [ + 109.29234915162841, + 0.008396540322742578 + ], + [ + 109.2923483320723, + 0.00848239851570885 + ], + [ + 109.29230117051706, + 0.008514281015961002 + ], + [ + 109.2922531899082, + 0.008517299799409945 + ], + [ + 109.29222858875701, + 0.008487415013101405 + ], + [ + 109.29220520972355, + 0.008423710666223964 + ], + [ + 109.29215423026447, + 0.00844182442502528 + ], + [ + 109.2921242423944, + 0.008441824292396585 + ], + [ + 109.29203427885284, + 0.008426728908882445 + ], + [ + 109.29193232033894, + 0.008372386513432679 + ], + [ + 109.29189333602088, + 0.008393519320774085 + ], + [ + 109.29189333586335, + 0.008429747283518673 + ], + [ + 109.29191132842492, + 0.008465975326112821 + ], + [ + 109.29187226595108, + 0.00850233275884541 + ], + [ + 109.29175239208601, + 0.008613905458186733 + ], + [ + 109.2917020502271, + 0.008505301758822024 + ], + [ + 109.29164743556011, + 0.008387480246122778 + ], + [ + 109.29090973447232, + 0.008345211068059648 + ], + [ + 109.2908047769585, + 0.00835426759432915 + ], + [ + 109.29069981934582, + 0.008387476079683343 + ], + [ + 109.29055287876777, + 0.008417665381200527 + ], + [ + 109.29049290318355, + 0.00839351315904711 + ], + [ + 109.29045272094855, + 0.008403083209608754 + ], + [ + 109.29048922274792, + 0.008489437936352017 + ], + [ + 109.29002441804455, + 0.008530991543888687 + ], + [ + 109.29112304227971, + 0.011630683792513839 + ], + [ + 109.29844019670283, + 0.03227552844129888 + ], + [ + 109.31160956858122, + 0.03940671734231131 + ], + [ + 109.31164417889936, + 0.03939582571713132 + ], + [ + 109.31163578110953, + 0.03960991437806738 + ] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "Id": 0, + "Plan_Area": 113026328.1, + "Ket": "Pontianak Barat", + "Penduduk": 153809 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 109.33350107458523, + -0.016435714311586334 + ], + [ + 109.33349952873034, + -0.016437205715091656 + ], + [ + 109.33343923412818, + -0.016379201612772235 + ], + [ + 109.33332827566068, + -0.01649694408796973 + ], + [ + 109.3332755215699, + -0.01660490123231449 + ], + [ + 109.33269146246714, + -0.01748296484521548 + ], + [ + 109.3325535534105, + -0.01760303053825808 + ], + [ + 109.33231311873286, + -0.017763320323472637 + ], + [ + 109.33209494652526, + -0.017910252626475772 + ], + [ + 109.33190348928176, + -0.018039374953437933 + ], + [ + 109.3316407921338, + -0.018155139798190154 + ], + [ + 109.33143597740852, + -0.018230832196819504 + ], + [ + 109.3311465652962, + -0.018306524595404182 + ], + [ + 109.3303640394701, + -0.018482954671470923 + ], + [ + 109.33031645344019, + -0.018495477310894494 + ], + [ + 109.33020875874064, + -0.01851801806196926 + ], + [ + 109.32987816105877, + -0.01856560409191833 + ], + [ + 109.32974792560826, + -0.018610685593989935 + ], + [ + 109.32966527618797, + -0.018630721817051367 + ], + [ + 109.32959264487903, + -0.018633226345046577 + ], + [ + 109.32949496829114, + -0.018648253512374622 + ], + [ + 109.32908906655173, + -0.018715140231677464 + ], + [ + 109.32875362803472, + -0.018778488962819345 + ], + [ + 109.3281525413404, + -0.018881174606467762 + ], + [ + 109.32798473797155, + -0.018901210829509043 + ], + [ + 109.32774179876591, + -0.018961319499035297 + ], + [ + 109.32753642747862, + -0.01898886930577992 + ], + [ + 109.32706557623477, + -0.019056491558900178 + ], + [ + 109.32661977026983, + -0.01914415003525847 + ], + [ + 109.32641940803832, + -0.019186727009468798 + ], + [ + 109.32609381941218, + -0.01924683567882133 + ], + [ + 109.32582082587187, + -0.019286908125132982 + ], + [ + 109.32553781421993, + -0.01934451226668101 + ], + [ + 109.32535748821158, + -0.019369557545553456 + ], + [ + 109.32447839892097, + -0.019517324691329607 + ], + [ + 109.3241327740717, + -0.01957993788863837 + ], + [ + 109.32358177793535, + -0.0196876325881019 + ], + [ + 109.32288551918107, + -0.01981035445487719 + ], + [ + 109.32193630310948, + -0.020000698574735517 + ], + [ + 109.32125044153554, + -0.020139046482968645 + ], + [ + 109.32063645313286, + -0.02023862872450207 + ], + [ + 109.31994770796224, + -0.020381386814502866 + ], + [ + 109.31917380884326, + -0.020536667543914342 + ], + [ + 109.31902103264177, + -0.02056922640650852 + ], + [ + 109.31843747764266, + -0.020669407522241973 + ], + [ + 109.31821958371594, + -0.02072951619163034 + ], + [ + 109.31785141811568, + -0.020804652028360536 + ], + [ + 109.31763352418896, + -0.02084221994683384 + ], + [ + 109.31700488768773, + -0.02099499614831767 + ], + [ + 109.31674441678693, + -0.021072636512996346 + ], + [ + 109.31626855648715, + -0.02118283574031233 + ], + [ + 109.31573759657368, + -0.021333107413871337 + ], + [ + 109.31537694455699, + -0.02140824325070585 + ], + [ + 109.31479338955806, + -0.021576046619550694 + ], + [ + 109.31452290054554, + -0.021656191512133716 + ], + [ + 109.31437763792776, + -0.021701273014230634 + ], + [ + 109.31413720324997, + -0.021776408851051653 + ], + [ + 109.31392431837895, + -0.021831508464622538 + ], + [ + 109.31374148784282, + -0.021884103550464083 + ], + [ + 109.31341840374469, + -0.021956734859309962 + ], + [ + 109.31277474007602, + -0.022147078979238687 + ], + [ + 109.31228385260899, + -0.022289837069111716 + ], + [ + 109.31199833642923, + -0.022397531768583615 + ], + [ + 109.31163768441252, + -0.02251524457950044 + ], + [ + 109.31106665205282, + -0.022723120394637225 + ], + [ + 109.31055071930689, + -0.022915969042519614 + ], + [ + 109.30992208280563, + -0.02317643994336487 + ], + [ + 109.30936357308562, + -0.023409361037411973 + ], + [ + 109.30912063387994, + -0.02350954215319899 + ], + [ + 109.3080436868858, + -0.02392028472768914 + ], + [ + 109.30724975154362, + -0.024235855242211498 + ], + [ + 109.30638819394841, + -0.024614038954044494 + ], + [ + 109.30541643712584, + -0.025024781528596416 + ], + [ + 109.30442213955217, + -0.025473092021361835 + ], + [ + 109.3033376789743, + -0.025963979488500145 + ], + [ + 109.30329510200015, + -0.02597900665592159 + ], + [ + 109.30325753408177, + -0.026054142492682834 + ], + [ + 109.30318239824503, + -0.026084196827351826 + ], + [ + 109.30314232579869, + -0.026099223994782327 + ], + [ + 109.30301709940413, + -0.026119260217971705 + ], + [ + 109.30253623004856, + -0.026349676784038312 + ], + [ + 109.3020303154141, + -0.026605138629236295 + ], + [ + 109.30151939172397, + -0.02689065480899174 + ], + [ + 109.30112117178894, + -0.02713108948680134 + ], + [ + 109.30091830502963, + -0.027271343048751902 + ], + [ + 109.30081561938596, + -0.02735148794136626 + ], + [ + 109.30073296996554, + -0.027381542276083855 + ], + [ + 109.30067787035173, + -0.0274241192503304 + ], + [ + 109.30062527526611, + -0.027449164529214032 + ], + [ + 109.30056266206863, + -0.027484227919732457 + ], + [ + 109.30051006698304, + -0.02751428225440533 + ], + [ + 109.30044494925778, + -0.027559363756498045 + ], + [ + 109.30040738133938, + -0.02760194073071504 + ], + [ + 109.30036730889304, + -0.027637004121233318 + ], + [ + 109.30032222739096, + -0.02764952676064821 + ], + [ + 109.3002771458889, + -0.02766455392797556 + ], + [ + 109.3002245508031, + -0.02768959920699932 + ], + [ + 109.30012186515947, + -0.027752212404315347 + ], + [ + 109.30010182893626, + -0.027779762211054874 + ], + [ + 109.30010433346422, + -0.0278173301295482 + ], + [ + 109.3000467293227, + -0.02785990710366917 + ], + [ + 109.30001417046009, + -0.02787994332685497 + ], + [ + 109.2999715934859, + -0.027879943326848382 + ], + [ + 109.2999315210396, + -0.02790999766157279 + ], + [ + 109.29990898028866, + -0.02793754746840397 + ], + [ + 109.29987642142603, + -0.027960088219449827 + ], + [ + 109.29982132181227, + -0.02798012444256285 + ], + [ + 109.29979377200556, + -0.027987638026211806 + ], + [ + 109.29976371767071, + -0.028015187833093886 + ], + [ + 109.29968858183396, + -0.028070287446743544 + ], + [ + 109.29961595052515, + -0.02808030555826308 + ], + [ + 109.29957587807881, + -0.028110359893056595 + ], + [ + 109.29950575129786, + -0.028162954978823356 + ], + [ + 109.29939555207048, + -0.028240595343447295 + ], + [ + 109.29935047056844, + -0.02827565873394354 + ], + [ + 109.29918517172746, + -0.028390867017038142 + ], + [ + 109.2991375856974, + -0.028408398712373443 + ], + [ + 109.29912005400223, + -0.028443462102877203 + ], + [ + 109.29909250419531, + -0.028468507381780705 + ], + [ + 109.29904241363748, + -0.028478525493367154 + ], + [ + 109.29896477327283, + -0.02852110246755609 + ], + [ + 109.29891468271497, + -0.028543643218567647 + ], + [ + 109.29889214196399, + -0.028581211137002308 + ], + [ + 109.2988670966851, + -0.028616274527475914 + ], + [ + 109.2988145015993, + -0.028648833390043146 + ], + [ + 109.29860913031202, + -0.028764041673133033 + ], + [ + 109.29850143561268, + -0.028844186565775892 + ], + [ + 109.29843130883162, + -0.028886763539904502 + ], + [ + 109.2983461548832, + -0.028946872209361686 + ], + [ + 109.2982960643254, + -0.028989449183537616 + ], + [ + 109.2982359556559, + -0.029042044269382113 + ], + [ + 109.29822694119417, + -0.02904897847082371 + ], + [ + 109.29763772027924, + -0.02943225893981783 + ], + [ + 109.29719630062445, + -0.02976905247930244 + ], + [ + 109.29717904488486, + -0.029773366414107074 + ], + [ + 109.29701875509996, + -0.02987104300194137 + ], + [ + 109.29690605134456, + -0.02996120600618947 + ], + [ + 109.29607454808408, + -0.03055978817260521 + ], + [ + 109.2958115726552, + -0.030765159459808504 + ], + [ + 109.29539331649717, + -0.031075720918621815 + ], + [ + 109.29499509656215, + -0.03138628237741637 + ], + [ + 109.2944766592882, + -0.03176446608932984 + ], + [ + 109.29441905514669, + -0.031817061175050544 + ], + [ + 109.29438148722836, + -0.03187216078874242 + ], + [ + 109.29428130611264, + -0.03192726040236893 + ], + [ + 109.2942237019711, + -0.03197484643225554 + ], + [ + 109.29418112499681, + -0.03200490076698789 + ], + [ + 109.29414856613437, + -0.032039964157482705 + ], + [ + 109.29413854802266, + -0.03208504565955725 + ], + [ + 109.29410098010428, + -0.03211509999437774 + ], + [ + 109.29407843935316, + -0.032127622633798356 + ], + [ + 109.29404588049074, + -0.03215517244057824 + ], + [ + 109.2939557174865, + -0.03217520866375818 + ], + [ + 109.29346483001932, + -0.03259346482188859 + ], + [ + 109.29321644186898, + -0.03280559193769602 + ], + [ + 109.29257874346028, + -0.03329214000202692 + ], + [ + 109.29189921853776, + -0.03399176778725929 + ], + [ + 109.29176676010782, + -0.034103695141595794 + ], + [ + 109.29150128015104, + -0.034364166042503926 + ], + [ + 109.29098284287731, + -0.03487759426056016 + ], + [ + 109.29048444182641, + -0.03535345456022512 + ], + [ + 109.29019892564664, + -0.035671529602775 + ], + [ + 109.28968048837272, + -0.03618495782078593 + ], + [ + 109.28930731371673, + -0.03656314153272183 + ], + [ + 109.28837813386836, + -0.037482303269462704 + ], + [ + 109.28812267202305, + -0.03772774700305188 + ], + [ + 109.28736630459946, + -0.03848160989896008 + ], + [ + 109.28720351028647, + -0.038661935907160154 + ], + [ + 109.28634826039102, + -0.03947217387076286 + ], + [ + 109.28608528496227, + -0.03974078448734692 + ], + [ + 109.28607219531767, + -0.03975330002742059 + ], + [ + 109.28614228933485, + -0.039829999967719694 + ], + [ + 109.28614210681985, + -0.03983017860126383 + ], + [ + 109.28605666166604, + -0.03973816486870906 + ], + [ + 109.28512724208747, + -0.040606145838443665 + ], + [ + 109.28459225401619, + -0.03984515586191861 + ], + [ + 109.28398020850554, + -0.04044804890069206 + ], + [ + 109.28364256654531, + -0.040863077730139155 + ], + [ + 109.28335466844723, + -0.041077661140143634 + ], + [ + 109.27931435657267, + -0.03531855349608888 + ], + [ + 109.2761628269257, + -0.027218688336519227 + ], + [ + 109.27991142588166, + -0.022168562113274295 + ], + [ + 109.28209584434909, + -0.02009560977472829 + ], + [ + 109.2856701446203, + -0.018412374595974862 + ], + [ + 109.28535655086988, + -0.016810937082375594 + ], + [ + 109.28592414462466, + -0.013914905846485299 + ], + [ + 109.28567420668837, + -0.012230649506661534 + ], + [ + 109.28652968315649, + -0.009964091899725632 + ], + [ + 109.2868833321167, + -0.00904242144724364 + ], + [ + 109.28693749099111, + -0.008600576300976995 + ], + [ + 109.28699133167875, + -0.007968514823180851 + ], + [ + 109.28705259664852, + -0.006732730351642923 + ], + [ + 109.28705266884782, + -0.00673265469942577 + ], + [ + 109.2870530644507, + -0.006723294634714295 + ], + [ + 109.28706616753426, + -0.006413274744431901 + ], + [ + 109.28706699725494, + -0.006393643922358154 + ], + [ + 109.28705756508239, + -0.0063823869939841835 + ], + [ + 109.28706091408455, + -0.006355647438448171 + ], + [ + 109.2870720897474, + -0.006266416268365792 + ], + [ + 109.28703935495653, + -0.006220985957059631 + ], + [ + 109.28703930888598, + -0.006220698193027571 + ], + [ + 109.2870725196219, + -0.006262983967250727 + ], + [ + 109.28709451960465, + -0.00621018708796425 + ], + [ + 109.2870108259101, + -0.0060427890632982146 + ], + [ + 109.2869545432358, + -0.005691238970462198 + ], + [ + 109.28694142586039, + -0.005574546468270493 + ], + [ + 109.28693872273024, + -0.005550468339645207 + ], + [ + 109.28693612899221, + -0.005527312098256883 + ], + [ + 109.28688772273117, + -0.005236937086018487 + ], + [ + 109.28685816172596, + -0.005089226441492274 + ], + [ + 109.28685398764947, + -0.005063154596888409 + ], + [ + 109.28685296185327, + -0.005063243769488195 + ], + [ + 109.28664666045721, + -0.0040127739867467935 + ], + [ + 109.28661738377264, + -0.0036475582044262103 + ], + [ + 109.28662339155679, + -0.003622817528176109 + ], + [ + 109.28661053526778, + -0.003542515200991235 + ], + [ + 109.28661479981494, + -0.003483639582842361 + ], + [ + 109.28660538622005, + -0.0034802112311831264 + ], + [ + 109.28659147064778, + -0.0030193796909118885 + ], + [ + 109.28659733210391, + -0.0025063277027905758 + ], + [ + 109.28664572273887, + -0.0021200152242219807 + ], + [ + 109.28665493729, + -0.0020341996327890013 + ], + [ + 109.28665984857369, + -0.0019884684275808108 + ], + [ + 109.28669000520172, + -0.0017076656970980973 + ], + [ + 109.28671612897881, + -0.0014644214736614502 + ], + [ + 109.28672942586485, + -0.001314233962600044 + ], + [ + 109.28675358170298, + -0.0010412299721266876 + ], + [ + 109.28680856570422, + -0.0006495562478760398 + ], + [ + 109.28683050398288, + -0.0005056714676946862 + ], + [ + 109.28687814461223, + -0.00012162458300059818 + ], + [ + 109.28689323701744, + -1.4315509590290447e-9 + ], + [ + 109.28692109773029, + 0.00022451603536123425 + ], + [ + 109.28693612898317, + 0.0003457035439701815 + ], + [ + 109.28699828523449, + 0.000848656668077017 + ], + [ + 109.28702851960236, + 0.0010932504140464152 + ], + [ + 109.28705931648463, + 0.001269250415497187 + ], + [ + 109.28705073839508, + 0.0013937035356382953 + ], + [ + 109.28708077836524, + 0.0013824481960254234 + ], + [ + 109.2871044709323, + 0.001234497923533027 + ], + [ + 109.28711946484853, + 0.0012012890504990725 + ], + [ + 109.28715545020351, + 0.0011922321050917043 + ], + [ + 109.28718243922675, + 0.0011741181853468518 + ], + [ + 109.28721542578131, + 0.0012012891110695187 + ], + [ + 109.2872304196068, + 0.0013099727430326332 + ], + [ + 109.28725141106177, + 0.00130997275748054 + ], + [ + 109.28726940379508, + 0.0012224220722348274 + ], + [ + 109.28745232935127, + 0.0012073272403845565 + ], + [ + 109.2874883146602, + 0.0012797830212912577 + ], + [ + 109.28756628293488, + 0.0012767640836726694 + ], + [ + 109.28765024878635, + 0.0012526122185773985 + ], + [ + 109.28770722562693, + 0.0012194033631024825 + ], + [ + 109.28774620977498, + 0.0012073274267669566 + ], + [ + 109.28777319879009, + 0.0012163844149430483 + ], + [ + 109.28780318659135, + 0.0012163844341022204 + ], + [ + 109.28783917195342, + 0.0012163844570927842 + ], + [ + 109.28786016340929, + 0.0012254414420625817 + ], + [ + 109.28788115487114, + 0.001225441455573211 + ], + [ + 109.28801909876874, + 0.0012254415443532316 + ], + [ + 109.28812405607906, + 0.0012405365665549006 + ], + [ + 109.28813005358627, + 0.0013220493258433042 + ], + [ + 109.28816279081343, + 0.0013445206273937171 + ], + [ + 109.28825761345075, + 0.0013252347789186054 + ], + [ + 109.28850678531433, + 0.0012745629067393543 + ], + [ + 109.28881914398185, + 0.0011789272189178881 + ], + [ + 109.28884729246428, + 0.001242271513218518 + ], + [ + 109.28910184736039, + 0.0012232016818997972 + ], + [ + 109.2894638822321, + 0.0010672248748057923 + ], + [ + 109.2894338944448, + 0.0009917500328682403 + ], + [ + 109.28964980685583, + 0.0009041993381395964 + ], + [ + 109.28969178977765, + 0.0009887311736639 + ], + [ + 109.29022992199359, + 0.000776521731677648 + ], + [ + 109.29026429721198, + 0.0007464544523293587 + ], + [ + 109.29015960022669, + 0.00033059069029858844 + ], + [ + 109.29023157103859, + 0.0003426666798196174 + ], + [ + 109.29027955158472, + 0.0003305907110974491 + ], + [ + 109.29031553699589, + 0.00031851473971294313 + ], + [ + 109.29035152240697, + 0.00030945776232279974 + ], + [ + 109.29037851147368, + 0.0002460588821469026 + ], + [ + 109.29040849932404, + 0.00017058402225082495 + ], + [ + 109.29040849932792, + 0.00011926111491026641 + ], + [ + 109.29053144948958, + 0.000043786253967656635 + ], + [ + 109.29059742274906, + 0.0000075583173058581686 + ], + [ + 109.29063940573306, + 0.000019634297147155692 + ], + [ + 109.29066939357917, + 0.00003774826726150619 + ], + [ + 109.29073236805766, + 0.000049824248737839566 + ], + [ + 109.29080134010891, + 0.000034729274676990136 + ], + [ + 109.2908553182374, + 0.000010577313575810389 + ], + [ + 109.29089430244233, + 0.0000045393231728414236 + ], + [ + 109.29090629758191, + 0.00004680525793608333 + ], + [ + 109.2909332866471, + 0.0000649192308934644 + ], + [ + 109.29095127935497, + 0.00011926114885321129 + ], + [ + 109.29102025141, + 0.00016756508146010114 + ], + [ + 109.29107123076297, + 0.00011926115635296212 + ], + [ + 109.29110121861459, + 0.00014341312345916748 + ], + [ + 109.29114020282496, + 0.00013435613920947197 + ], + [ + 109.29117019068097, + 0.00010718517950440723 + ], + [ + 109.29118218582396, + 0.00008605220977135119 + ], + [ + 109.29116419311194, + 0.00006793823452244203 + ], + [ + 109.29116719189777, + 0.00005284325587128488 + ], + [ + 109.29118518461085, + 0.00004076727320563644 + ], + [ + 109.2912151724661, + 0.00003472928221164651 + ], + [ + 109.29124815910707, + 0.0000317102869256939 + ], + [ + 109.29125715546388, + -0.000004140289178198161 + ], + [ + 109.29125715546333, + -0.00005168947446261542 + ], + [ + 109.29127964635526, + -0.000056217968993273005 + ], + [ + 109.29131338269318, + -0.00007433194585808905 + ], + [ + 109.29132237904922, + -0.00009697441618742203 + ], + [ + 109.29131788086582, + -0.00016716607274951448 + ], + [ + 109.29128414452185, + -0.00021924374968249118 + ], + [ + 109.29138985170417, + -0.0003211348802961206 + ], + [ + 109.2914123426028, + -0.0002894354246469721 + ], + [ + 109.29145957349753, + -0.00013093813057968312 + ], + [ + 109.29152029891706, + -0.00004942523438769286 + ], + [ + 109.291601266141, + -0.000019990021228532726 + ], + [ + 109.29170022608578, + -0.00001999002226523143 + ], + [ + 109.29178119331682, + -0.000040368250955727055 + ], + [ + 109.29216470652231, + -0.00022099911596846843 + ], + [ + 109.29277875969487, + -0.0004749958326894779 + ], + [ + 109.29327969845221, + -0.0007053447632492354 + ], + [ + 109.29363155634175, + -0.0007704211078492534 + ], + [ + 109.29412735616889, + -0.0009314346641333777 + ], + [ + 109.2942926227492, + -0.0010870810026222136 + ], + [ + 109.29445255814464, + -0.0012480944773406632 + ], + [ + 109.29466580549546, + -0.0012373603898131378 + ], + [ + 109.29478309150234, + -0.0013017658303668203 + ], + [ + 109.29499100764635, + -0.0013769055721582746 + ], + [ + 109.29504965069509, + -0.0013554371565407336 + ], + [ + 109.295289554003, + -0.0013983742477365153 + ], + [ + 109.29536952173686, + -0.0014681468059801511 + ], + [ + 109.29558545719242, + -0.0014387652079153681 + ], + [ + 109.29655304522956, + -0.0016345283935651667 + ], + [ + 109.29670231844786, + -0.0017311366649470233 + ], + [ + 109.29688891009685, + -0.0017257697140766416 + ], + [ + 109.29732606768086, + -0.0017848084375780761 + ], + [ + 109.29757663367891, + -0.0017955429142652998 + ], + [ + 109.29770458222033, + -0.001870682741034758 + ], + [ + 109.2978538556123, + -0.0018706828868492484 + ], + [ + 109.29788584265742, + -0.001983392488989106 + ], + [ + 109.29792316090105, + -0.0020853678556506296 + ], + [ + 109.29802978472867, + -0.0021175707087170773 + ], + [ + 109.29808842792458, + -0.0020531652976651616 + ], + [ + 109.29816306472829, + -0.0019619242832184633 + ], + [ + 109.29822170784313, + -0.0019780257134076485 + ], + [ + 109.29828035090922, + -0.0020424312563178974 + ], + [ + 109.29829634436922, + -0.0021551408684208555 + ], + [ + 109.29844028654658, + -0.0022141793940806233 + ], + [ + 109.29846694261478, + -0.0021283054400440746 + ], + [ + 109.29857356652522, + -0.002112204185473906 + ], + [ + 109.29858422900058, + -0.0020316973315182758 + ], + [ + 109.29877615202325, + -0.0020316975350228475 + ], + [ + 109.2988774448786, + -0.0018921523871906966 + ], + [ + 109.29896807519061, + -0.0019082538530072397 + ], + [ + 109.29903204957877, + -0.0018706840372939677 + ], + [ + 109.29922397260457, + -0.0019136212339806913 + ], + [ + 109.29939990208955, + -0.0019243556629701657 + ], + [ + 109.29950652611741, + -0.0018384817386125545 + ], + [ + 109.2996717931864, + -0.0018975202987847211 + ], + [ + 109.29981040445179, + -0.001774076496259537 + ], + [ + 109.29992769080647, + -0.0017848108610498633 + ], + [ + 109.30001832116916, + -0.0018009123305591302 + ], + [ + 109.3000716331874, + -0.0017687096093942378 + ], + [ + 109.3003221996167, + -0.0016989371602625938 + ], + [ + 109.30047147321234, + -0.0016989372924864135 + ], + [ + 109.30079134521408, + -0.001731140359167503 + ], + [ + 109.30100459329238, + -0.0016989377646196514 + ], + [ + 109.3011698606511, + -0.001575493883785291 + ], + [ + 109.30121251030603, + -0.0015271897331745152 + ], + [ + 109.30135645278958, + -0.001500354187026353 + ], + [ + 109.30138843995847, + -0.0015593926665524302 + ], + [ + 109.30145241437363, + -0.0015808612481578428 + ], + [ + 109.30149506406008, + -0.0015003542953887419 + ], + [ + 109.30159635693437, + -0.0014788858432865798 + ], + [ + 109.30168698741873, + -0.001441315981695476 + ], + [ + 109.3017349683194, + -0.0013447076201857202 + ], + [ + 109.3018735796322, + -0.0013232391828999197 + ], + [ + 109.30206017178668, + -0.001312505043326024 + ], + [ + 109.30228941359276, + -0.001312505200033377 + ], + [ + 109.30236405046574, + -0.0013125052510498149 + ], + [ + 109.30234718401489, + -0.0011671940842111598 + ], + [ + 109.3027094164274, + -0.0011116100743424004 + ], + [ + 109.30323836841411, + -0.0011890616883157485 + ], + [ + 109.30334499262563, + -0.0010978204134478031 + ], + [ + 109.3034836040604, + -0.0010602505260264132 + ], + [ + 109.30364887154305, + -0.001033414924512232 + ], + [ + 109.30366486514349, + -0.0010817191804281287 + ], + [ + 109.30373950203312, + -0.0011461248880450315 + ], + [ + 109.30379281409608, + -0.0011944291703701207 + ], + [ + 109.30385145743129, + -0.0011407578156883075 + ], + [ + 109.30387278228767, + -0.0011085549932845836 + ], + [ + 109.30396341285753, + -0.0010978207667373526 + ], + [ + 109.30406470586036, + -0.0010709851260500946 + ], + [ + 109.3040860306704, + -0.0011246565356099621 + ], + [ + 109.30424596699815, + -0.0010924537879036754 + ], + [ + 109.30438457853441, + -0.000985111054661152 + ], + [ + 109.30449120276916, + -0.0009582754047823509 + ], + [ + 109.30462448307244, + -0.0009207054823416145 + ], + [ + 109.3046564703214, + -0.0009636426285207122 + ], + [ + 109.30465647030523, + -0.0009958454766928833 + ], + [ + 109.30482173784318, + -0.0010548841222623817 + ], + [ + 109.30491236847165, + -0.0010173141775022343 + ], + [ + 109.30506697363417, + -0.0010226814017994057 + ], + [ + 109.30532287187191, + -0.0010119472513984186 + ], + [ + 109.30529621586469, + -0.000888502945060978 + ], + [ + 109.30528555346594, + -0.0008065777140392928 + ], + [ + 109.3053122095777, + -0.0007113872271874638 + ], + [ + 109.30554120852668, + -0.0008111826232100865 + ], + [ + 109.30548280830075, + -0.000979744473558357 + ], + [ + 109.30572804408675, + -0.0011139232071546714 + ], + [ + 109.30576003140773, + -0.0010548846368269253 + ], + [ + 109.30587198693196, + -0.001033416119240778 + ], + [ + 109.30603192338988, + -0.00103341620513213 + ], + [ + 109.30613854773313, + -0.0009743776619457082 + ], + [ + 109.30625050327839, + -0.0009529091353725874 + ], + [ + 109.30633136414957, + -0.0008846530292084875 + ], + [ + 109.30638894057392, + -0.0008903039717301921 + ], + [ + 109.30650107043425, + -0.0009851121384865872 + ], + [ + 109.306501070389, + -0.001070986482564129 + ], + [ + 109.3067516375972, + -0.0010656194747488447 + ], + [ + 109.30702352969922, + -0.0010817210691331225 + ], + [ + 109.30750333934735, + -0.0011729628767134497 + ], + [ + 109.30767926956457, + -0.0012320016315513524 + ], + [ + 109.30771658806006, + -0.001301774604041068 + ], + [ + 109.30776456902393, + -0.001328610386571455 + ], + [ + 109.3078551998241, + -0.0012481031949048696 + ], + [ + 109.30793516814059, + -0.0012373689457198514 + ], + [ + 109.30802046761835, + -0.0013232434122999815 + ], + [ + 109.308068448597, + -0.0013393448979070889 + ], + [ + 109.3081910667227, + -0.0012856734709808014 + ], + [ + 109.30832434720189, + -0.0013769151368612344 + ], + [ + 109.30832967838121, + -0.0014359538082873977 + ], + [ + 109.3083030221945, + -0.0015379296674268596 + ], + [ + 109.30828169723806, + -0.0016238040739300953 + ], + [ + 109.30827636593348, + -0.0017204127955828506 + ], + [ + 109.3083350093541, + -0.0017418814544144625 + ], + [ + 109.30838832163451, + -0.0016721085296565069 + ], + [ + 109.30843630274765, + -0.0015325626219357866 + ], + [ + 109.30854292729036, + -0.0014037510540318824 + ], + [ + 109.30869220155867, + -0.0013554467892582908 + ], + [ + 109.30893210660199, + -0.0013554469579579798 + ], + [ + 109.3090813808086, + -0.0014359543683109713 + ], + [ + 109.30928929850866, + -0.0014949932201928392 + ], + [ + 109.3094439039839, + -0.0015486648870918216 + ], + [ + 109.3097851023306, + -0.0016452739628281267 + ], + [ + 109.30984374590366, + -0.0015379308965253655 + ], + [ + 109.3099130518261, + -0.0015593995758681545 + ], + [ + 109.309907720516, + -0.0016613755354414135 + ], + [ + 109.310046332393, + -0.001672109967594889 + ], + [ + 109.31009964468733, + -0.0016399070746383937 + ], + [ + 109.31020093798831, + -0.001650641474339305 + ], + [ + 109.31025958144987, + -0.0016935787802898135 + ], + [ + 109.31025958138837, + -0.0017633518209688327 + ], + [ + 109.3103182248783, + -0.0017740861888647238 + ], + [ + 109.31037153722855, + -0.0016882117213228571 + ], + [ + 109.31053680527863, + -0.001698946181389402 + ], + [ + 109.3107233981845, + -0.0017901880361911404 + ], + [ + 109.31083002267638, + -0.0018760626720541595 + ], + [ + 109.31114456514678, + -0.0019297345721563752 + ], + [ + 109.3113631455151, + -0.0019887735511383725 + ], + [ + 109.3114697703047, + -0.0018009230490676632 + ], + [ + 109.31167768821514, + -0.0018975321395602615 + ], + [ + 109.31170967554404, + -0.001956570942063323 + ], + [ + 109.31166169430213, + -0.0021122185587439733 + ], + [ + 109.31167235672915, + -0.0021444215358195065 + ], + [ + 109.31170967531165, + -0.002176624543249198 + ], + [ + 109.31178431250754, + -0.002214194755920692 + ], + [ + 109.31191226216653, + -0.0021229531559254938 + ], + [ + 109.31197623695451, + -0.002117586064547549 + ], + [ + 109.31196557444368, + -0.0021605233465965 + ], + [ + 109.31209885524, + -0.002165890657782687 + ], + [ + 109.31211484900689, + -0.00210148473013807 + ], + [ + 109.31223213615014, + -0.002074649045426218 + ], + [ + 109.31249869769542, + -0.002171258269346269 + ], + [ + 109.31247737257694, + -0.0023322731395495617 + ], + [ + 109.31253601611218, + -0.0023591090268665488 + ], + [ + 109.3126213159136, + -0.0023054374956299234 + ], + [ + 109.3126906218564, + -0.0023859450343237986 + ], + [ + 109.31271194671935, + -0.002444983862363093 + ], + [ + 109.31273860287598, + -0.0024557182237979697 + ], + [ + 109.31281857150685, + -0.002359109371890915 + ], + [ + 109.31325573257845, + -0.0025093905340150737 + ], + [ + 109.31351696298638, + -0.002622101359751903 + ], + [ + 109.31357027531256, + -0.0026489372629493776 + ], + [ + 109.31355961276668, + -0.0027026089097817643 + ], + [ + 109.31365557494014, + -0.0027616478744786955 + ], + [ + 109.31364491238286, + -0.00282068668941775 + ], + [ + 109.31367156849556, + -0.002868991226398476 + ], + [ + 109.31373021209139, + -0.0028743584800613874 + ], + [ + 109.31379418701947, + -0.002815319740467146 + ], + [ + 109.3138315057017, + -0.0027992182941126123 + ], + [ + 109.3141780359366, + -0.002938765160195725 + ], + [ + 109.31414604842769, + -0.002992436789305902 + ], + [ + 109.31455122224757, + -0.0031856554965991424 + ], + [ + 109.314652516022, + -0.0030407420955789266 + ], + [ + 109.31467917220216, + -0.0030514764760111272 + ], + [ + 109.3146791721517, + -0.00308367949147549 + ], + [ + 109.31471649077676, + -0.003115882567156163 + ], + [ + 109.314764472084, + -0.0030192735936402995 + ], + [ + 109.31489242167102, + -0.0031266171902327097 + ], + [ + 109.31488709030981, + -0.0032017575591992207 + ], + [ + 109.31491907774097, + -0.0032071247820695935 + ], + [ + 109.31495106521662, + -0.0031856561550934284 + ], + [ + 109.31497772140874, + -0.003191023369012154 + ], + [ + 109.31499371515554, + -0.003174921885067643 + ], + [ + 109.31503636503456, + -0.003201757806248806 + ], + [ + 109.31504702731893, + -0.0033198355699299038 + ], + [ + 109.31502037101627, + -0.003378874396401539 + ], + [ + 109.31515365186978, + -0.0034701165288084992 + ], + [ + 109.3151696456582, + -0.0034325463632233575 + ], + [ + 109.31534024517852, + -0.003539890087598603 + ], + [ + 109.3155215074653, + -0.0034969530464576772 + ], + [ + 109.31565478881146, + -0.003325203784598647 + ], + [ + 109.31596933182105, + -0.003496953855562519 + ], + [ + 109.31592668152405, + -0.003690171996171406 + ], + [ + 109.31616125593432, + -0.003840453297485936 + ], + [ + 109.3161505933736, + -0.003878023489696759 + ], + [ + 109.31652911118074, + -0.004130281443850235 + ], + [ + 109.31664639882983, + -0.004001469500974454 + ], + [ + 109.31713687289593, + -0.004248360613421119 + ], + [ + 109.31754204703554, + -0.004522087538602061 + ], + [ + 109.31741942764987, + -0.004817281979155896 + ], + [ + 109.31757936490591, + -0.004881688504340389 + ], + [ + 109.31762201467019, + -0.004967563450343936 + ], + [ + 109.31772330814258, + -0.005058805730798237 + ], + [ + 109.31828841939655, + -0.005466712829354811 + ], + [ + 109.31837516255142, + -0.0053554602910697606 + ], + [ + 109.31866511312731, + -0.005569470628071991 + ], + [ + 109.31885703848124, + -0.005451393201074079 + ], + [ + 109.31944347442762, + -0.006047152101801359 + ], + [ + 109.31970470632369, + -0.005880770237653555 + ], + [ + 109.32010454902708, + -0.006310146192110369 + ], + [ + 109.3200565672505, + -0.006460427192256452 + ], + [ + 109.32006189838125, + -0.006497997499131034 + ], + [ + 109.32063234087889, + -0.007018616426012774 + ], + [ + 109.32068565334174, + -0.007045452548206126 + ], + [ + 109.32069098438546, + -0.007104491612499569 + ], + [ + 109.32078694670292, + -0.007184999756246248 + ], + [ + 109.32083492800963, + -0.007184999933827518 + ], + [ + 109.32091489671889, + -0.007222570535453829 + ], + [ + 109.32099486527224, + -0.007303078634064514 + ], + [ + 109.32116013357476, + -0.007480196433332943 + ], + [ + 109.32142669529064, + -0.007780759984631585 + ], + [ + 109.32157597020027, + -0.00786126840791356 + ], + [ + 109.32168792707705, + -0.0077485578992111705 + ], + [ + 109.32181054573384, + -0.00782369903432594 + ], + [ + 109.3217305764634, + -0.007920308110213114 + ], + [ + 109.32199180702123, + -0.008199403029254697 + ], + [ + 109.32195448798029, + -0.008253074765709337 + ], + [ + 109.32205045022266, + -0.008355051776937752 + ], + [ + 109.3220717749184, + -0.008435559714402971 + ], + [ + 109.3224076432637, + -0.008677084751755791 + ], + [ + 109.32253021770333, + -0.008555223813813006 + ], + [ + 109.32261769158315, + -0.008623796470935956 + ], + [ + 109.3227425509715, + -0.008720562102095002 + ], + [ + 109.32279963047175, + -0.008763652674381029 + ], + [ + 109.32271152418895, + -0.008891773768042597 + ], + [ + 109.32293543620538, + -0.009106462476574633 + ], + [ + 109.32304206112765, + -0.009176236477230083 + ], + [ + 109.32311669910227, + -0.009111830518321958 + ], + [ + 109.32320733017032, + -0.009197706028804863 + ], + [ + 109.32316601247736, + -0.009284251816275268 + ], + [ + 109.32317800757158, + -0.009336582003112817 + ], + [ + 109.32321399340474, + -0.00937683612247862 + ], + [ + 109.32325797632443, + -0.009376836334612823 + ], + [ + 109.32325797599738, + -0.009445268045318268 + ], + [ + 109.32330595721066, + -0.009477471437149979 + ], + [ + 109.32340591872563, + -0.009409040208550748 + ], + [ + 109.32345390015496, + -0.0093969642552867 + ], + [ + 109.32342191270784, + -0.009368786335049485 + ], + [ + 109.32347708588438, + -0.00932064778616679 + ], + [ + 109.32350520721754, + -0.009347265230665395 + ], + [ + 109.32361658221639, + -0.00948110897939933 + ], + [ + 109.32370187045336, + -0.0095795202324977 + ], + [ + 109.32367781218895, + -0.009614336700581403 + ], + [ + 109.32366981497724, + -0.009678742990623978 + ], + [ + 109.32372979128326, + -0.009763276599312504 + ], + [ + 109.32380176339282, + -0.009755226168880997 + ], + [ + 109.32396969755096, + -0.009892090482672772 + ], + [ + 109.32412563659165, + -0.009984675396208707 + ], + [ + 109.32415349607781, + -0.009960286738680334 + ], + [ + 109.32422831659875, + -0.01000743709721238 + ], + [ + 109.32443351763983, + -0.009896118238424012 + ], + [ + 109.32452548137562, + -0.01001688062731732 + ], + [ + 109.32438909892838, + -0.010137482127341167 + ], + [ + 109.32422959475933, + -0.010278529893076658 + ], + [ + 109.32427357736533, + -0.010342936475055355 + ], + [ + 109.32448574082967, + -0.010202520238242889 + ], + [ + 109.32470541142408, + -0.010057135531053666 + ], + [ + 109.32484135797255, + -0.010206075960515084 + ], + [ + 109.32463743624962, + -0.010350989204597821 + ], + [ + 109.32469341427745, + -0.010403319672702999 + ], + [ + 109.32473339856755, + -0.010443573864460584 + ], + [ + 109.3246734212783, + -0.010544208484429832 + ], + [ + 109.32486134794958, + -0.010636793658251346 + ], + [ + 109.32491732575717, + -0.010729378122347644 + ], + [ + 109.32507326494556, + -0.010801836155093916 + ], + [ + 109.32511724788536, + -0.01080586179779278 + ], + [ + 109.32527718518217, + -0.010942726249135785 + ], + [ + 109.32536914941112, + -0.010970904559877672 + ], + [ + 109.3254257128914, + -0.010936807162467137 + ], + [ + 109.32547334783577, + -0.010974187091668535 + ], + [ + 109.32558651972391, + -0.011066437100422944 + ], + [ + 109.32568623847186, + -0.011136077721565416 + ], + [ + 109.32575989471499, + -0.011159577718583596 + ], + [ + 109.32579898150435, + -0.01123809290728057 + ], + [ + 109.32609586830552, + -0.010939208624836105 + ], + [ + 109.32616484138813, + -0.010981475720233568 + ], + [ + 109.32619482993914, + -0.010954304433782935 + ], + [ + 109.32640774669616, + -0.011114315331407268 + ], + [ + 109.32637176027173, + -0.011174696143883455 + ], + [ + 109.32650670748797, + -0.011280363706539754 + ], + [ + 109.32655169033666, + -0.011238097250658219 + ], + [ + 109.32680059302682, + -0.01142829893442043 + ], + [ + 109.3268725650883, + -0.01144943271826656 + ], + [ + 109.32700151457134, + -0.011567176498771575 + ], + [ + 109.32706149118447, + -0.011603405478286224 + ], + [ + 109.32713918988726, + -0.011668786142002806 + ], + [ + 109.32726241256415, + -0.011772473601806846 + ], + [ + 109.32734038286131, + -0.011703035867820826 + ], + [ + 109.32755329950278, + -0.011893237467695128 + ], + [ + 109.32753530623198, + -0.011929465990190286 + ], + [ + 109.3276104974185, + -0.011991606859025472 + ], + [ + 109.3277362276516, + -0.012095515135098552 + ], + [ + 109.32782919128564, + -0.012174011093739378 + ], + [ + 109.32789516569196, + -0.012195144878491004 + ], + [ + 109.32795514215306, + -0.01225854537386163 + ], + [ + 109.32797613412711, + -0.012246469292335095 + ], + [ + 109.32823103411367, + -0.012512147625122799 + ], + [ + 109.3286002697244, + -0.012823593345787476 + ], + [ + 109.32865686752183, + -0.012880474997533121 + ], + [ + 109.32842492038567, + -0.013174601082288379 + ], + [ + 109.3285609018043, + -0.013291065746008792 + ], + [ + 109.32849053564527, + -0.01338450460747077 + ], + [ + 109.32860861844959, + -0.013480783501882957 + ], + [ + 109.32862687443743, + -0.013574857307581466 + ], + [ + 109.32865086458473, + -0.013662410052737271 + ], + [ + 109.32873183301407, + -0.013713734547144304 + ], + [ + 109.32887277809564, + -0.01379826907140602 + ], + [ + 109.32894474999702, + -0.013849593513378246 + ], + [ + 109.32899273093311, + -0.013931108337645595 + ], + [ + 109.32902571783757, + -0.013985451563183603 + ], + [ + 109.32923263766187, + -0.014048853208157611 + ], + [ + 109.32936158764485, + -0.014097159024921932 + ], + [ + 109.3294005728654, + -0.014063949695193825 + ], + [ + 109.32947554399108, + -0.014063950235526644 + ], + [ + 109.32951752771521, + -0.014079045816909148 + ], + [ + 109.32950553203345, + -0.014121312510792721 + ], + [ + 109.32959249824187, + -0.014163579922369022 + ], + [ + 109.32984140128433, + -0.014320572654300345 + ], + [ + 109.3298983796579, + -0.014278306283422366 + ], + [ + 109.32996135520122, + -0.014308497308562403 + ], + [ + 109.33010230230522, + -0.01412131682843974 + ], + [ + 109.33030322404805, + -0.014254156788717963 + ], + [ + 109.33033288144645, + -0.014216835750932109 + ], + [ + 109.3303512060243, + -0.014193775998006601 + ], + [ + 109.3306390935999, + -0.014429264575358773 + ], + [ + 109.33060837683475, + -0.01446577644578642 + ], + [ + 109.33039018714567, + -0.014725130332865934 + ], + [ + 109.33041117884656, + -0.014755321062613904 + ], + [ + 109.33054612719211, + -0.014722112451527854 + ], + [ + 109.3307350544558, + -0.01473419010641537 + ], + [ + 109.3308400143962, + -0.014694943146822948 + ], + [ + 109.33091498577875, + -0.014667772189618558 + ], + [ + 109.33098695793615, + -0.014691925194390323 + ], + [ + 109.33108292051668, + -0.014764383312547789 + ], + [ + 109.33111890646228, + -0.014794574166927735 + ], + [ + 109.33113989803422, + -0.014842879258210156 + ], + [ + 109.33117588397971, + -0.014873070114907325 + ], + [ + 109.33125385390812, + -0.01488816600079777 + ], + [ + 109.33129583745976, + -0.014930433139909924 + ], + [ + 109.33134081990514, + -0.014966662186640138 + ], + [ + 109.3314141353962, + -0.0148865272720272 + ], + [ + 109.33143886347008, + -0.014892749603860198 + ], + [ + 109.3314738947228, + -0.014909937100355295 + ], + [ + 109.33152689472718, + -0.014952437096547408 + ], + [ + 109.33159875410212, + -0.015024796477515548 + ], + [ + 109.33168587909594, + -0.015118843360969608 + ], + [ + 109.33178828534203, + -0.015215608972306794 + ], + [ + 109.3318996759737, + -0.015306046481900364 + ], + [ + 109.33191381442312, + -0.015315820238582922 + ], + [ + 109.33203147887052, + -0.015197364633360798 + ], + [ + 109.33345092513063, + -0.016384873322763678 + ], + [ + 109.33350107458523, + -0.016435714311586334 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "Id": 0, + "Plan_Area": 113026328.1, + "Ket": "Pontianak Kota", + "Penduduk": 130798 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 109.34477671544886, + -0.02609238682029518 + ], + [ + 109.34356263152422, + -0.02669498758526942 + ], + [ + 109.34210494955182, + -0.027631289913580114 + ], + [ + 109.34073186993628, + -0.028449083707839516 + ], + [ + 109.33989534931862, + -0.02900890989233597 + ], + [ + 109.33866974466297, + -0.029896030424540483 + ], + [ + 109.33810328408603, + -0.03033352538936671 + ], + [ + 109.33789455090782, + -0.03038760898972202 + ], + [ + 109.33665977712866, + -0.03156383186112857 + ], + [ + 109.33398214420839, + -0.03414347596314283 + ], + [ + 109.33260000690117, + -0.03551172396131128 + ], + [ + 109.329812240833, + -0.03792040735123325 + ], + [ + 109.32678273002863, + -0.04053795579042847 + ], + [ + 109.32503242609339, + -0.04205024456135078 + ], + [ + 109.32448646470382, + -0.04261931863073522 + ], + [ + 109.32372239973736, + -0.043415728877689824 + ], + [ + 109.3227098229548, + -0.04438848475261101 + ], + [ + 109.32191463922695, + -0.04515239613305311 + ], + [ + 109.32091635935765, + -0.04611141557186 + ], + [ + 109.31959341850713, + -0.04750229392434255 + ], + [ + 109.3189193802165, + -0.048139538454601324 + ], + [ + 109.31792659285628, + -0.04907813170237839 + ], + [ + 109.31720023906404, + -0.04984814625261359 + ], + [ + 109.31642282578291, + -0.050634668632088826 + ], + [ + 109.31413037706432, + -0.05284824873871211 + ], + [ + 109.31203935583139, + -0.0549893416096532 + ], + [ + 109.31095422427445, + -0.05591006277695976 + ], + [ + 109.31014057450321, + -0.05686925418155446 + ], + [ + 109.30922115564408, + -0.05779283901742656 + ], + [ + 109.30886309776761, + -0.05821339794593776 + ], + [ + 109.30803151893961, + -0.05899239587119645 + ], + [ + 109.30699226064493, + -0.06003601996990354 + ], + [ + 109.30592483648198, + -0.06115790978185177 + ], + [ + 109.30470076601497, + -0.06230462738825889 + ], + [ + 109.30358075575703, + -0.061299808552741857 + ], + [ + 109.30193009388118, + -0.05967264044108471 + ], + [ + 109.30133568284666, + -0.05695960489856255 + ], + [ + 109.30019381593524, + -0.05496220797215477 + ], + [ + 109.29876899798475, + -0.053427837566184154 + ], + [ + 109.29399956110106, + -0.04829170836292673 + ], + [ + 109.28676733367584, + -0.04050346990507054 + ], + [ + 109.28614210681985, + -0.03983017860126383 + ], + [ + 109.28614228933485, + -0.039829999967719694 + ], + [ + 109.28614964983703, + -0.039838054151606836 + ], + [ + 109.28826868165741, + -0.037842861068459056 + ], + [ + 109.2889299961209, + -0.037183085738776864 + ], + [ + 109.28992141812556, + -0.036156043781946805 + ], + [ + 109.29020044108495, + -0.0358710841638919 + ], + [ + 109.29132246958109, + -0.03471343571542002 + ], + [ + 109.29181521225395, + -0.03420288306633807 + ], + [ + 109.29214172848307, + -0.03397135337673088 + ], + [ + 109.2925276112991, + -0.03361515385415839 + ], + [ + 109.2928838108218, + -0.03332425757730019 + ], + [ + 109.29318064375725, + -0.03297993137213991 + ], + [ + 109.2933053135901, + -0.032861198197907074 + ], + [ + 109.29345373005785, + -0.03274840168251062 + ], + [ + 109.29362589316047, + -0.03260592187342162 + ], + [ + 109.29377430962822, + -0.032546555286220094 + ], + [ + 109.2938871061438, + -0.032469378723081296 + ], + [ + 109.29405333258761, + -0.032261595668280206 + ], + [ + 109.29424330566628, + -0.03213692583527286 + ], + [ + 109.29450451864963, + -0.03193507943917153 + ], + [ + 109.2947538583153, + -0.031715423066870536 + ], + [ + 109.29485478151345, + -0.03163824650368205 + ], + [ + 109.29501507129862, + -0.031507640012129856 + ], + [ + 109.29532377755157, + -0.031299856957228506 + ], + [ + 109.29582839354177, + -0.030919910799815292 + ], + [ + 109.29610741650119, + -0.03063495118175598 + ], + [ + 109.2962498963103, + -0.03053402798372347 + ], + [ + 109.29639831277798, + -0.03046872473788293 + ], + [ + 109.29649329931735, + -0.03042716812680801 + ], + [ + 109.29664171578509, + -0.030326244928864883 + ], + [ + 109.29681387888773, + -0.030189701778456264 + ], + [ + 109.296891055451, + -0.030076905263058222 + ], + [ + 109.29703947191861, + -0.029999728699743378 + ], + [ + 109.29719976170377, + -0.02992848879527126 + ], + [ + 109.29732443153655, + -0.0298275655971235 + ], + [ + 109.29740160809988, + -0.029774135668843855 + ], + [ + 109.29768656771806, + -0.029607909224978243 + ], + [ + 109.29787654079674, + -0.029465429415840124 + ], + [ + 109.29809026051038, + -0.029317012948152674 + ], + [ + 109.29828023358898, + -0.02918046979783592 + ], + [ + 109.29839896676324, + -0.029097356575886846 + ], + [ + 109.2984702066677, + -0.029020180012652528 + ], + [ + 109.29863643311151, + -0.028972686742969635 + ], + [ + 109.2988501528251, + -0.028770840346853867 + ], + [ + 109.29899856929298, + -0.028646170513869074 + ], + [ + 109.29918260571289, + -0.02849775404615545 + ], + [ + 109.29933102218061, + -0.028402767506782497 + ], + [ + 109.29944975535486, + -0.028379020872018632 + ], + [ + 109.29992468805158, + -0.028070314618980918 + ], + [ + 109.3007736302472, + -0.02750039538296088 + ], + [ + 109.30106452652387, + -0.027351978915184323 + ], + [ + 109.3012841828961, + -0.027256992375840505 + ], + [ + 109.30159288914915, + -0.02709076593197692 + ], + [ + 109.30185410213234, + -0.026912666170632937 + ], + [ + 109.30237652809883, + -0.026639579869916177 + ], + [ + 109.30340950671429, + -0.026105280586074787 + ], + [ + 109.30368852967374, + -0.02599842072929834 + ], + [ + 109.3062353562603, + -0.02484077228089171 + ], + [ + 109.30742268800218, + -0.024348029607953604 + ], + [ + 109.3078976206989, + -0.024152119870451887 + ], + [ + 109.30805197382546, + -0.024074943307250043 + ], + [ + 109.3086990696248, + -0.02379592034793677 + ], + [ + 109.3097320482403, + -0.023398164214321428 + ], + [ + 109.31118652962417, + -0.02284605495425876 + ], + [ + 109.31183956208228, + -0.022602651947242043 + ], + [ + 109.31205328179593, + -0.022519538725266887 + ], + [ + 109.3124035446597, + -0.02238299557499625 + ], + [ + 109.31273006088868, + -0.022299882353026067 + ], + [ + 109.31296752723715, + -0.02224645242462198 + ], + [ + 109.3131990569268, + -0.02214552922655911 + ], + [ + 109.31348995320369, + -0.022080225980734978 + ], + [ + 109.3142914021294, + -0.021854632949845222 + ], + [ + 109.3153481273798, + -0.021557800014231954 + ], + [ + 109.31591210995724, + -0.021397510229172 + ], + [ + 109.31677886212891, + -0.02117785385685766 + ], + [ + 109.3171291249928, + -0.02110067729359496 + ], + [ + 109.31773466418116, + -0.02094632416714955 + ], + [ + 109.31824521683023, + -0.020827590992958642 + ], + [ + 109.31963439496845, + -0.02056637800977535 + ], + [ + 109.32080985339289, + -0.02032891166126152 + ], + [ + 109.32112449630453, + -0.020257671756833427 + ], + [ + 109.32218715821361, + -0.02007957199549711 + ], + [ + 109.32460337830855, + -0.019610575957395213 + ], + [ + 109.32576696341553, + -0.019426539537446625 + ], + [ + 109.32628345272339, + -0.019343426315465168 + ], + [ + 109.3268355619834, + -0.01922469314128929 + ], + [ + 109.32859281296152, + -0.01892786020582828 + ], + [ + 109.32970890479895, + -0.01873788712715328 + ], + [ + 109.33051035372482, + -0.01859540731806249 + ], + [ + 109.33088436322333, + -0.018524167413492366 + ], + [ + 109.33108027296088, + -0.018470737485192967 + ], + [ + 109.33125243606354, + -0.01839949758062133 + ], + [ + 109.33154926899901, + -0.018304511041271464 + ], + [ + 109.3320123283783, + -0.018126411279917556 + ], + [ + 109.33240414785325, + -0.017936438201281452 + ], + [ + 109.33273066408226, + -0.01767522521800571 + ], + [ + 109.33295032045444, + -0.017307152378003857 + ], + [ + 109.33335703955245, + -0.016716972859436494 + ], + [ + 109.33336612525251, + -0.016703788831756106 + ], + [ + 109.3334122418994, + -0.016687145688211266 + ], + [ + 109.33357379967187, + -0.016509442243053733 + ], + [ + 109.33357612611913, + -0.016511800771031884 + ], + [ + 109.33359817229996, + -0.016499965426237873 + ], + [ + 109.33422946106636, + -0.01701015816423277 + ], + [ + 109.33507659623616, + -0.01770458456459219 + ], + [ + 109.33545744784372, + -0.01802158982596765 + ], + [ + 109.33578729587748, + -0.018299293812025212 + ], + [ + 109.33623695669901, + -0.01867034241946192 + ], + [ + 109.33662788260209, + -0.018993858014070632 + ], + [ + 109.33698199372701, + -0.019284010300453243 + ], + [ + 109.33738369780845, + -0.01961315950276846 + ], + [ + 109.33783552212492, + -0.0199714268285251 + ], + [ + 109.3377803440222, + -0.020034911212905827 + ], + [ + 109.33777304503681, + -0.020058853384273552 + ], + [ + 109.33735920604552, + -0.019723732618234683 + ], + [ + 109.33723775086416, + -0.019870910945570874 + ], + [ + 109.33783601822357, + -0.020337363106356948 + ], + [ + 109.3379102411385, + -0.02022188449783109 + ], + [ + 109.33794172896451, + -0.020239999235025723 + ], + [ + 109.33797494634473, + -0.020196108531097958 + ], + [ + 109.3380060089359, + -0.020221838989059798 + ], + [ + 109.33797321662854, + -0.020273964084055466 + ], + [ + 109.3383038377735, + -0.020566062457535693 + ], + [ + 109.33835208221741, + -0.02053051522483765 + ], + [ + 109.33838620722558, + -0.02046178085873691 + ], + [ + 109.33817788841054, + -0.020303402123444062 + ], + [ + 109.33821162598491, + -0.020262645038778063 + ], + [ + 109.33822361348254, + -0.02027910897549895 + ], + [ + 109.33862425409838, + -0.020588390231731576 + ], + [ + 109.33856530019744, + -0.02062260199610125 + ], + [ + 109.33845643892116, + -0.02052567673272894 + ], + [ + 109.33844258240886, + -0.020511791419897965 + ], + [ + 109.33832998344502, + -0.020626420532258646 + ], + [ + 109.33845776178075, + -0.020738504832821383 + ], + [ + 109.3384982463903, + -0.02073723159079746 + ], + [ + 109.33852243260385, + -0.020708684841986517 + ], + [ + 109.33860072257558, + -0.02078053745592256 + ], + [ + 109.33861477224654, + -0.020767137820285605 + ], + [ + 109.3386487985158, + -0.020734685841763006 + ], + [ + 109.33867410128468, + -0.020744875469959928 + ], + [ + 109.33872091189636, + -0.020716855221799727 + ], + [ + 109.33901315770584, + -0.020948666306704687 + ], + [ + 109.33900936204587, + -0.020970318661734044 + ], + [ + 109.33900388733987, + -0.020984336865655137 + ], + [ + 109.33905183222879, + -0.021019780861193837 + ], + [ + 109.33900294447659, + -0.021107887163531867 + ], + [ + 109.33918437026601, + -0.021287676749368215 + ], + [ + 109.33919280385018, + -0.02135390770395058 + ], + [ + 109.33944245654993, + -0.021578076446535928 + ], + [ + 109.33951499147061, + -0.021579775471321914 + ], + [ + 109.33967692845235, + -0.02171733369320997 + ], + [ + 109.33960000401616, + -0.021789696738682186 + ], + [ + 109.33958246305528, + -0.02183451035246079 + ], + [ + 109.33995188171349, + -0.022153781308650006 + ], + [ + 109.33997989525034, + -0.02211752200534052 + ], + [ + 109.34003140115718, + -0.02216196878173797 + ], + [ + 109.3402360266023, + -0.021903987730824644 + ], + [ + 109.34031699594439, + -0.021897195720838276 + ], + [ + 109.34050173221132, + -0.021692728325859556 + ], + [ + 109.34051267416511, + -0.021670767448750072 + ], + [ + 109.34049315399385, + -0.021652119915318183 + ], + [ + 109.34052754955461, + -0.021605057176859552 + ], + [ + 109.340631925974, + -0.021670015234227097 + ], + [ + 109.34066063331285, + -0.021709407746605885 + ], + [ + 109.34060488816453, + -0.02176360495628862 + ], + [ + 109.34058464594351, + -0.021754547513510008 + ], + [ + 109.34039796407603, + -0.022000505511840317 + ], + [ + 109.34041775344672, + -0.022011795357146792 + ], + [ + 109.34044856955278, + -0.022029375960474714 + ], + [ + 109.34035579101779, + -0.022141457985522747 + ], + [ + 109.34041145705207, + -0.02217202672261741 + ], + [ + 109.34036085067005, + -0.02222467122775322 + ], + [ + 109.34028863894618, + -0.02217668911924637 + ], + [ + 109.34025373848462, + -0.02222437461777648 + ], + [ + 109.34023487909971, + -0.02226235899343857 + ], + [ + 109.34041453535096, + -0.022294015239200788 + ], + [ + 109.34041722284933, + -0.022296733985635076 + ], + [ + 109.3404764042615, + -0.022211293355431982 + ], + [ + 109.3406158469769, + -0.022285865373601197 + ], + [ + 109.34064159307698, + -0.022260123871124877 + ], + [ + 109.3408005602174, + -0.022101185103240512 + ], + [ + 109.34079043931067, + -0.022078258902706732 + ], + [ + 109.34078917441958, + -0.022055332801873575 + ], + [ + 109.34079550031448, + -0.022040048815073397 + ], + [ + 109.34081194734227, + -0.022027312284636037 + ], + [ + 109.34083598517074, + -0.022020944196573026 + ], + [ + 109.34086761372959, + -0.02202731290871359 + ], + [ + 109.34088373654568, + -0.022032723601021015 + ], + [ + 109.34100424890647, + -0.022073166618737126 + ], + [ + 109.34101310442888, + -0.022117745225095173 + ], + [ + 109.34111811072485, + -0.02218652467841721 + ], + [ + 109.34127119215178, + -0.0222909674936527 + ], + [ + 109.34160253103633, + -0.022671822768044622 + ], + [ + 109.34161052736425, + -0.022728178915732557 + ], + [ + 109.34162552127556, + -0.022763904803056935 + ], + [ + 109.34165251101567, + -0.0227669241901121 + ], + [ + 109.34171248777604, + -0.02281221100303301 + ], + [ + 109.34176946639951, + -0.022794097216371156 + ], + [ + 109.34179645586507, + -0.022821269201500908 + ], + [ + 109.34189541931451, + -0.022739755329671277 + ], + [ + 109.34187742643905, + -0.022712583448278905 + ], + [ + 109.34190141773124, + -0.022679373902135723 + ], + [ + 109.34193740382752, + -0.022703526916467456 + ], + [ + 109.34196439374395, + -0.022691450928349007 + ], + [ + 109.34200337843018, + -0.022739756578314643 + ], + [ + 109.34208734708282, + -0.022700509572710425 + ], + [ + 109.34230926075665, + -0.022902790191162637 + ], + [ + 109.34235350508146, + -0.022855396509083005 + ], + [ + 109.34238898847799, + -0.02288457773371446 + ], + [ + 109.34251653534758, + -0.02300214023455363 + ], + [ + 109.34263582928774, + -0.023113772976633057 + ], + [ + 109.34291802551941, + -0.02332546797662811 + ], + [ + 109.34289366168005, + -0.023366857927247853 + ], + [ + 109.34287004271155, + -0.023406982468707337 + ], + [ + 109.34287004189547, + -0.02347642122434964 + ], + [ + 109.34284605068507, + -0.023500573548412836 + ], + [ + 109.34298699577242, + -0.02363643367514794 + ], + [ + 109.34305297132438, + -0.023594167395219315 + ], + [ + 109.34315793009789, + -0.023723988955205107 + ], + [ + 109.34322239354042, + -0.02369937340353607 + ], + [ + 109.3432149081962, + -0.023754180410841395 + ], + [ + 109.34348180369415, + -0.024056091359822594 + ], + [ + 109.34343082252384, + -0.024092319662950244 + ], + [ + 109.34342182552267, + -0.024125529401767373 + ], + [ + 109.34343382047736, + -0.024167796629555614 + ], + [ + 109.34359875568707, + -0.024370076844259854 + ], + [ + 109.34366545548173, + -0.024304172657742908 + ], + [ + 109.34368272399762, + -0.024367058806423817 + ], + [ + 109.34363174274236, + -0.02440932526026807 + ], + [ + 109.34365273444133, + -0.02443951629595325 + ], + [ + 109.34368572208909, + -0.024430459472804655 + ], + [ + 109.34372470676838, + -0.024478765199004875 + ], + [ + 109.34377868645038, + -0.024472727714908007 + ], + [ + 109.34380267746177, + -0.024466689857675663 + ], + [ + 109.34410555773034, + -0.02489842180878768 + ], + [ + 109.34421951254993, + -0.02507051071889045 + ], + [ + 109.34428773902204, + -0.025025323622104217 + ], + [ + 109.34433347047765, + -0.024995035205528916 + ], + [ + 109.34454638696367, + -0.025245621457296696 + ], + [ + 109.34445172324268, + -0.025308246547117275 + ], + [ + 109.3443364644296, + -0.025384496380569627 + ], + [ + 109.34443842351654, + -0.02557469965554942 + ], + [ + 109.34446241469428, + -0.025556585494460073 + ], + [ + 109.34453138750257, + -0.02564715875605449 + ], + [ + 109.34453138715308, + -0.025674330466003755 + ], + [ + 109.34451639246184, + -0.025701501980213315 + ], + [ + 109.34450739570138, + -0.02571357817810545 + ], + [ + 109.34450439656067, + -0.025734711690864547 + ], + [ + 109.34462734754577, + -0.025936991592494536 + ], + [ + 109.34467832878107, + -0.025900763314677496 + ], + [ + 109.34473830505425, + -0.025985298320994627 + ], + [ + 109.34477671544886, + -0.02609238682029518 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "Id": 0, + "Plan_Area": 113026328.1, + "Ket": "Pontianak Tenggara", + "Penduduk": 50589 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 109.32389276289446, + -0.08052549670981375 + ], + [ + 109.32393840924082, + -0.08043223156481946 + ], + [ + 109.32463498212687, + -0.07940820627817206 + ], + [ + 109.32774600026455, + -0.07481502560151726 + ], + [ + 109.33040539270077, + -0.07078409119737616 + ], + [ + 109.33243550823754, + -0.06818769289691332 + ], + [ + 109.33501859789894, + -0.06513216064808534 + ], + [ + 109.33822989052756, + -0.06131030726062607 + ], + [ + 109.34177229675167, + -0.057301053193673665 + ], + [ + 109.34548918426678, + -0.053022664678963764 + ], + [ + 109.34727622990728, + -0.05100467478763772 + ], + [ + 109.34858140443534, + -0.04896407822417487 + ], + [ + 109.34954295376907, + -0.04751183325128081 + ], + [ + 109.35087495701242, + -0.04606645834771445 + ], + [ + 109.35193342151885, + -0.04487510170995203 + ], + [ + 109.35175946042841, + -0.044535422834847266 + ], + [ + 109.35334707526704, + -0.043105218849610556 + ], + [ + 109.35425171375785, + -0.04207834350964987 + ], + [ + 109.35431448848385, + -0.04214840587070115 + ], + [ + 109.35434681660432, + -0.04218457775050146 + ], + [ + 109.35452378536777, + -0.042380827746403296 + ], + [ + 109.35468187911444, + -0.04256712462387454 + ], + [ + 109.35483548849375, + -0.042752515246202216 + ], + [ + 109.35500526973969, + -0.042963234003939886 + ], + [ + 109.3551876134853, + -0.04315948400864796 + ], + [ + 109.35537445724084, + -0.043342171513130065 + ], + [ + 109.35555320724525, + -0.04353299962899523 + ], + [ + 109.3557373478612, + -0.04374009338507419 + ], + [ + 109.35593678536617, + -0.043927296499979576 + ], + [ + 109.35612542599422, + -0.04410456212951205 + ], + [ + 109.35629698847974, + -0.04428090588788119 + ], + [ + 109.35646856661099, + -0.04446721838889703 + ], + [ + 109.35665809786381, + -0.04464267149998366 + ], + [ + 109.35684944162092, + -0.04481178088107389 + ], + [ + 109.35702370723703, + -0.04499807775636303 + ], + [ + 109.3571907853674, + -0.04519523400136286 + ], + [ + 109.35736773848059, + -0.045362546504240965 + ], + [ + 109.35755009786895, + -0.04550182775933234 + ], + [ + 109.35771269161211, + -0.04563839025944261 + ], + [ + 109.35785012911374, + -0.045766812126829946 + ], + [ + 109.35793816036534, + -0.04584278087586946 + ], + [ + 109.3579660041066, + -0.04586629650353804 + ], + [ + 109.35797048848944, + -0.04587262463159546 + ], + [ + 109.35797678536902, + -0.045878952749835736 + ], + [ + 109.3580046447321, + -0.045912405877382254 + ], + [ + 109.35808547286098, + -0.045996515254539884 + ], + [ + 109.35820853536326, + -0.046107765259906924 + ], + [ + 109.35834058223479, + -0.04621628087817594 + ], + [ + 109.35849150411944, + -0.046332046501001395 + ], + [ + 109.35865139473724, + -0.04646951525790752 + ], + [ + 109.35880859786597, + -0.046616921510605126 + ], + [ + 109.35895770724458, + -0.046758015265081845 + ], + [ + 109.3590897697338, + -0.04689999963101887 + ], + [ + 109.35921911348393, + -0.04703023401526369 + ], + [ + 109.3593565509857, + -0.047159546514658854 + ], + [ + 109.35948320723496, + -0.04727440587834581 + ], + [ + 109.35954608223308, + -0.04732957775593727 + ], + [ + 109.35967184787364, + -0.047436296503040445 + ], + [ + 109.35973651974163, + -0.04748874963936883 + ], + [ + 109.35980030098361, + -0.04754753089157272 + ], + [ + 109.35985778535864, + -0.04760812463552371 + ], + [ + 109.35991528537187, + -0.04767323401643266 + ], + [ + 109.35996648849246, + -0.047741968389913396 + ], + [ + 109.36001589473815, + -0.04780709338382904 + ], + [ + 109.36008505098535, + -0.047870390261888475 + ], + [ + 109.36017667599181, + -0.0479391246377138 + ], + [ + 109.36026741036508, + -0.048018718377986304 + ], + [ + 109.3603500509984, + -0.04810010901007835 + ], + [ + 109.36042820724116, + -0.048176984009334844 + ], + [ + 109.36050366036243, + -0.04825565589168769 + ], + [ + 109.3605809103584, + -0.04833706212599964 + ], + [ + 109.36065995724566, + -0.04841845276519255 + ], + [ + 109.36072912911155, + -0.04849079651376115 + ], + [ + 109.3607767384832, + -0.048548687127765444 + ], + [ + 109.36079739473281, + -0.048580327764626605 + ], + [ + 109.36080278535984, + -0.0485957027588042 + ], + [ + 109.36081716037292, + -0.04860746839028417 + ], + [ + 109.36086476974137, + -0.04864273400118973 + ], + [ + 109.36096178537201, + -0.048735890264149886 + ], + [ + 109.36110820724814, + -0.04886431213165419 + ], + [ + 109.36125103535834, + -0.04901262463854214 + ], + [ + 109.36139386348759, + -0.04916367150462954 + ], + [ + 109.36153848849848, + -0.049316499631445875 + ], + [ + 109.36168400411718, + -0.04946753089318956 + ], + [ + 109.36183222287319, + -0.049633030889205294 + ], + [ + 109.3619786447363, + -0.04981932775495954 + ], + [ + 109.36212145723489, + -0.04999387463823831 + ], + [ + 109.36224722286757, + -0.05014490589163686 + ], + [ + 109.36237298848685, + -0.05028056213393696 + ], + [ + 109.36250592599058, + -0.05041442150940707 + ], + [ + 109.36263437912397, + -0.050554593389834916 + ], + [ + 109.36266670724223, + -0.050589874628320645 + ], + [ + 109.3627601447474, + -0.05069206213525009 + ], + [ + 109.36287691035993, + -0.05081143712891819 + ], + [ + 109.36293530098966, + -0.0508584683780913 + ], + [ + 109.3629847689771, + -0.05096516217791833 + ], + [ + 109.36301275746366, + -0.05101346860686704 + ], + [ + 109.3628088279798, + -0.051190584310824364 + ], + [ + 109.36284881133082, + -0.05126706937747551 + ], + [ + 109.36294877528073, + -0.05123889361964209 + ], + [ + 109.3630887268794, + -0.05111813293434515 + ], + [ + 109.36312871074942, + -0.051174490628634536 + ], + [ + 109.36313670556174, + -0.05126305131280648 + ], + [ + 109.36315669708615, + -0.051307332067174256 + ], + [ + 109.36319668310153, + -0.051279154762270684 + ], + [ + 109.36325266201058, + -0.051299283586276265 + ], + [ + 109.36329264618614, + -0.05134356485890568 + ], + [ + 109.36338387263878, + -0.05128059146668026 + ], + [ + 109.36339253536076, + -0.051293484008386174 + ], + [ + 109.36347067599857, + -0.05141014026103844 + ], + [ + 109.3635035028251, + -0.051461956882718006 + ], + [ + 109.36344491945178, + -0.05150442013294075 + ], + [ + 109.3634569145039, + -0.05152555419708317 + ], + [ + 109.36324998639259, + -0.05168858064372768 + ], + [ + 109.36329196863798, + -0.051779154957166104 + ], + [ + 109.36333095515938, + -0.05174594578793275 + ], + [ + 109.36338193518235, + -0.051794252833936696 + ], + [ + 109.36332495457513, + -0.05185463350252849 + ], + [ + 109.36337293499876, + -0.05193011244128117 + ], + [ + 109.36350488844552, + -0.05185463818668325 + ], + [ + 109.36354687193145, + -0.05189690678944524 + ], + [ + 109.36348689257396, + -0.05195124916648312 + ], + [ + 109.36350188582028, + -0.05199955528200464 + ], + [ + 109.36345390275312, + -0.05202672599882762 + ], + [ + 109.36348988812729, + -0.05208107087844635 + ], + [ + 109.3634718940383, + -0.05210824237757218 + ], + [ + 109.36348988680254, + -0.052132395710388586 + ], + [ + 109.36352287413455, + -0.05215353032836207 + ], + [ + 109.36333693840437, + -0.05231051905171411 + ], + [ + 109.36337647215821, + -0.05235741848967411 + ], + [ + 109.36358884958001, + -0.05216560848729261 + ], + [ + 109.36360384485053, + -0.052135417800594504 + ], + [ + 109.36366682350402, + -0.05206597996403525 + ], + [ + 109.36372080585087, + -0.05198144634764212 + ], + [ + 109.36377778592943, + -0.051942199427738506 + ], + [ + 109.36376279205864, + -0.05191804617141255 + ], + [ + 109.36379072562532, + -0.051891331833366 + ], + [ + 109.36382277150082, + -0.051860684677261466 + ], + [ + 109.36399670171356, + -0.05209014145268094 + ], + [ + 109.36393817831649, + -0.05213724975816286 + ], + [ + 109.3639458634904, + -0.05215173401678236 + ], + [ + 109.36401053537435, + -0.05227471838719551 + ], + [ + 109.36406252765252, + -0.05235287404647941 + ], + [ + 109.36390372639863, + -0.05245545113730205 + ], + [ + 109.36390372443665, + -0.052530928846958355 + ], + [ + 109.3639187181442, + -0.05256112032638504 + ], + [ + 109.36397869548892, + -0.05258527477657811 + ], + [ + 109.36400268628579, + -0.05260037095238314 + ], + [ + 109.36407466048612, + -0.05257621998306889 + ], + [ + 109.36412564176749, + -0.05257622132815119 + ], + [ + 109.36414063641944, + -0.052570183506298426 + ], + [ + 109.36418861660145, + -0.05265471981897021 + ], + [ + 109.36422848094018, + -0.05263271277642474 + ], + [ + 109.36449449278945, + -0.0530955178555815 + ], + [ + 109.36456346438176, + -0.053213264958964425 + ], + [ + 109.3644255130137, + -0.05328873900556894 + ], + [ + 109.3644525013556, + -0.05335516012995001 + ], + [ + 109.36450648186585, + -0.05334308513769064 + ], + [ + 109.36453646982483, + -0.05338233436180131 + ], + [ + 109.36452147484792, + -0.05340044861614107 + ], + [ + 109.36453346964814, + -0.05343064003092263 + ], + [ + 109.36462943555499, + -0.05338837507040717 + ], + [ + 109.36464742823523, + -0.05341554753795306 + ], + [ + 109.3645754525652, + -0.05349404245382014 + ], + [ + 109.36467741035209, + -0.05367519176360451 + ], + [ + 109.36482435805185, + -0.05361481352445358 + ], + [ + 109.36495911349888, + -0.053759702762253433 + ], + [ + 109.36500044162564, + -0.05382934338786463 + ], + [ + 109.3651037384985, + -0.05398670275889562 + ], + [ + 109.36520164475165, + -0.05417210901534461 + ], + [ + 109.36528302744726, + -0.05432778367993095 + ], + [ + 109.36519868067664, + -0.0544224728815654 + ], + [ + 109.36531463535088, + -0.05452713854427252 + ], + [ + 109.3653666158647, + -0.05454324188874734 + ], + [ + 109.36542659192715, + -0.054615702185700006 + ], + [ + 109.36528664060597, + -0.05471230988842696 + ], + [ + 109.36532262578045, + -0.05477269308639613 + ], + [ + 109.36535861171801, + -0.05480489792109718 + ], + [ + 109.36540659238568, + -0.05486930693379749 + ], + [ + 109.3654385797887, + -0.05490151166144003 + ], + [ + 109.36546656898501, + -0.05492163983695868 + ], + [ + 109.36545057397086, + -0.054953843244278044 + ], + [ + 109.36540259078821, + -0.05498202028826975 + ], + [ + 109.3654985512358, + -0.05514304217651774 + ], + [ + 109.3655345372809, + -0.05517122153950435 + ], + [ + 109.3655705231064, + -0.05520745186583451 + ], + [ + 109.36560250973665, + -0.05526783497040557 + ], + [ + 109.36559851032096, + -0.05530003870977628 + ], + [ + 109.36562249976858, + -0.05536444707597378 + ], + [ + 109.36578644169745, + -0.055291992956616715 + ], + [ + 109.36579443810776, + -0.05531614606825555 + ], + [ + 109.3658951677086, + -0.055261545135679685 + ], + [ + 109.3659503833072, + -0.05523161527080817 + ], + [ + 109.3660508937358, + -0.055434004750743275 + ], + [ + 109.36618228558721, + -0.055698577654730075 + ], + [ + 109.36623827910981, + -0.05568829588435585 + ], + [ + 109.36639819855392, + -0.05598841844927554 + ], + [ + 109.36634621746599, + -0.055992442473620105 + ], + [ + 109.3664101905941, + -0.05611723423880407 + ], + [ + 109.36651015099675, + -0.05622592509367195 + ], + [ + 109.36654613659813, + -0.05627020642251357 + ], + [ + 109.36657012625173, + -0.056326563864144585 + ], + [ + 109.36637019525475, + -0.05647550108577025 + ], + [ + 109.36639418546305, + -0.05651173111060318 + ], + [ + 109.3666141082534, + -0.05639499832189014 + ], + [ + 109.36672907676906, + -0.05654657574564042 + ], + [ + 109.3667820406373, + -0.05661640466807459 + ], + [ + 109.36682202657609, + -0.05659627838351911 + ], + [ + 109.36687400576253, + -0.05666068759883511 + ], + [ + 109.36676121775585, + -0.05675207467987724 + ], + [ + 109.36677270724867, + -0.05679570277346339 + ], + [ + 109.36683648849204, + -0.056901515272067896 + ], + [ + 109.36698195326564, + -0.05712362132985517 + ], + [ + 109.36689798249213, + -0.057175950217531406 + ], + [ + 109.36690997651226, + -0.05723230733584371 + ], + [ + 109.36695795839701, + -0.057252436131609324 + ], + [ + 109.36698594782085, + -0.05726451338654648 + ], + [ + 109.36699794275106, + -0.05728866663515844 + ], + [ + 109.3670819133038, + -0.057244388717999986 + ], + [ + 109.36706592085882, + -0.05718400599595737 + ], + [ + 109.36708991242891, + -0.05717193023037265 + ], + [ + 109.36716988023424, + -0.05727659511582879 + ], + [ + 109.3671418898961, + -0.05729672173470122 + ], + [ + 109.36712989485112, + -0.0572765939686891 + ], + [ + 109.36696595193781, + -0.057377226365740386 + ], + [ + 109.36695795349142, + -0.05742553194398241 + ], + [ + 109.3669619507721, + -0.05746981238318298 + ], + [ + 109.36699793681329, + -0.05749799180783355 + ], + [ + 109.36708590350696, + -0.0575382491841164 + ], + [ + 109.36716187539069, + -0.057550327826944 + ], + [ + 109.36724184661693, + -0.05753422819379269 + ], + [ + 109.36728183257448, + -0.05751410192268641 + ], + [ + 109.36739778664621, + -0.057638895294551455 + ], + [ + 109.36743377292264, + -0.05765902375814567 + ], + [ + 109.36751774178244, + -0.05767512812331971 + ], + [ + 109.36756972210257, + -0.05769928253607192 + ], + [ + 109.36760570757772, + -0.057747589399603484 + ], + [ + 109.36761770193166, + -0.05779187008519002 + ], + [ + 109.3676176998615, + -0.057864328821279294 + ], + [ + 109.36761369959511, + -0.05792471098537891 + ], + [ + 109.36758970628463, + -0.05799716902455262 + ], + [ + 109.36760969748059, + -0.05804950091434412 + ], + [ + 109.36763368629022, + -0.05813403680449854 + ], + [ + 109.36764967824944, + -0.05821052149271316 + ], + [ + 109.36766567043766, + -0.05827895521130372 + ], + [ + 109.36769365917029, + -0.058315185397488936 + ], + [ + 109.3677416404844, + -0.05835544165437447 + ], + [ + 109.36778162495159, + -0.058387646708056955 + ], + [ + 109.36779361917651, + -0.05843595288703645 + ], + [ + 109.36777761989723, + -0.058613073788095216 + ], + [ + 109.3678595418368, + -0.05867591492962667 + ], + [ + 109.36598030094076, + -0.06026660899988584 + ], + [ + 109.36283910147407, + -0.06702667196022341 + ], + [ + 109.36283924220501, + -0.06702685980969039 + ], + [ + 109.36283664283378, + -0.06703196309703219 + ], + [ + 109.3628299002327, + -0.06704520059571245 + ], + [ + 109.36238646935098, + -0.06791577439347445 + ], + [ + 109.36167313532995, + -0.06925946260780556 + ], + [ + 109.361291087733, + -0.06991875248622066 + ], + [ + 109.36064959922007, + -0.07089644184335257 + ], + [ + 109.36041797001197, + -0.07124749300969976 + ], + [ + 109.36041574944281, + -0.07124827587437617 + ], + [ + 109.36041450523307, + -0.07125274414937383 + ], + [ + 109.35917826515553, + -0.07312635585072236 + ], + [ + 109.35917228525662, + -0.07312722971944 + ], + [ + 109.35829245193615, + -0.07459025187888303 + ], + [ + 109.35521403926118, + -0.07981326504483512 + ], + [ + 109.35173157142529, + -0.08486883843559084 + ], + [ + 109.34838923710363, + -0.09001839901704267 + ], + [ + 109.34701010911391, + -0.09223632304696226 + ], + [ + 109.34606457199752, + -0.09395909019645002 + ], + [ + 109.34274556452925, + -0.09833009529340522 + ], + [ + 109.33577028215284, + -0.09173477409757182 + ], + [ + 109.32389276289446, + -0.08052549670981375 + ] + ] + ] + } + } + ] +} \ No newline at end of file diff --git a/03/api/db.php b/03/api/db.php new file mode 100644 index 0000000..b687965 --- /dev/null +++ b/03/api/db.php @@ -0,0 +1,15 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); +} catch (PDOException $e) { + http_response_code(500); + echo json_encode(['status' => 'error', 'message' => 'Koneksi database gagal: ' . $e->getMessage()]); + exit; +} \ No newline at end of file diff --git a/03/api/jalan.php b/03/api/jalan.php new file mode 100644 index 0000000..66a6a0f --- /dev/null +++ b/03/api/jalan.php @@ -0,0 +1,98 @@ +query("SELECT * FROM jalan ORDER BY createdat DESC")->fetchAll(); + echo json_encode(['status' => 'success', 'data' => $rows]); + break; + + case 'create': + $input = json_decode(file_get_contents('php://input'), true); + $nama = trim($input['nama'] ?? ''); + $status = $input['status'] ?? 'kabupaten'; + $panjang = isset($input['panjang_meter']) ? (float)$input['panjang_meter'] : null; + $geom = $input['geom'] ?? null; + + if (!$nama) { + echo json_encode(['status' => 'error', 'message' => 'Nama jalan wajib diisi.']); + break; + } + if (!in_array($status, ['nasional','provinsi','kabupaten'])) $status = 'kabupaten'; + + // Validasi dan konversi geometri LineString + $geojson = null; + if (is_array($geom)) { + if (isset($geom['type']) && $geom['type'] === 'LineString' && isset($geom['coordinates'])) { + $geojson = json_encode($geom); + } elseif (isset($geom[0]) && is_array($geom[0])) { + // array of points [[lng,lat],...] + $geojson = json_encode([ + 'type' => 'LineString', + 'coordinates' => $geom + ]); + } + } elseif (is_string($geom)) { + $decoded = json_decode($geom, true); + if ($decoded && isset($decoded['type']) && $decoded['type'] === 'LineString') { + $geojson = $geom; + } else { + echo json_encode(['status'=>'error','message'=>'Format geometri tidak valid.']); + break; + } + } + + if (!$geojson) { + echo json_encode(['status' => 'error', 'message' => 'Geometri tidak valid. Harus berupa GeoJSON LineString atau array koordinat.']); + break; + } + + $stmt = $db->prepare("INSERT INTO jalan (nama, status, panjang_meter, geom) VALUES (?, ?, ?, ?)"); + $stmt->execute([$nama, $status, $panjang, $geojson]); + echo json_encode(['status'=>'success','message'=>'Data jalan berhasil disimpan!','id'=>$db->lastInsertId()]); + break; + + case 'update': + $input = json_decode(file_get_contents('php://input'), true); + $id = (int)($input['id'] ?? 0); + $nama = trim($input['nama'] ?? ''); + $status = $input['status'] ?? 'kabupaten'; + + if (!$id) { echo json_encode(['status'=>'error','message'=>'ID tidak valid.']); break; } + if (!$nama) { echo json_encode(['status'=>'error','message'=>'Nama jalan wajib diisi.']); break; } + if (!in_array($status, ['nasional','provinsi','kabupaten'])) $status = 'kabupaten'; + + $stmt = $db->prepare("UPDATE jalan SET nama=?, status=?, updatedat=NOW() WHERE id=?"); + $stmt->execute([$nama, $status, $id]); + echo json_encode(['status'=>'success','message'=>'Data jalan berhasil diperbarui.']); + break; + + case 'delete': + $input = json_decode(file_get_contents('php://input'), true); + $id = (int)($input['id'] ?? 0); + if (!$id) { echo json_encode(['status'=>'error','message'=>'ID tidak valid.']); break; } + $stmt = $db->prepare("DELETE FROM jalan WHERE id=?"); + $stmt->execute([$id]); + echo json_encode(['status'=>'success','message'=>'Data jalan berhasil dihapus.']); + break; + + default: + echo json_encode(['status'=>'error','message'=>'Aksi tidak dikenali.']); + } +} catch (PDOException $e) { + echo json_encode(['status'=>'error','message'=>'Database error: '.$e->getMessage()]); +} \ No newline at end of file diff --git a/03/api/parsil.php b/03/api/parsil.php new file mode 100644 index 0000000..317b8a1 --- /dev/null +++ b/03/api/parsil.php @@ -0,0 +1,126 @@ +query("SELECT * FROM parsil ORDER BY createdat DESC")->fetchAll(); + echo json_encode(['status' => 'success', 'data' => $rows]); + break; + + case 'create': + $input = json_decode(file_get_contents('php://input'), true); + $nama = trim($input['nama'] ?? ''); + $status = $input['status_kepemilikan'] ?? 'SHM'; + $luas = isset($input['luas_m2']) ? (float)$input['luas_m2'] : null; + $geom = $input['geom'] ?? null; + + if (!$nama) { + echo json_encode(['status' => 'error', 'message' => 'Nama parsil wajib diisi.']); + break; + } + $validStatus = ['SHM','HGB','HGU','HP']; + if (!in_array($status, $validStatus)) $status = 'SHM'; + + // Validasi dan konversi geometri Polygon + $geojson = null; + if (is_array($geom)) { + if (isset($geom['type']) && $geom['type'] === 'Polygon' && isset($geom['coordinates'])) { + $geojson = json_encode($geom); + } elseif (isset($geom[0]) && is_array($geom[0])) { + // array of rings [[lng,lat],...] + $geojson = json_encode([ + 'type' => 'Polygon', + 'coordinates' => $geom + ]); + } + } elseif (is_string($geom)) { + $decoded = json_decode($geom, true); + if ($decoded && isset($decoded['type']) && $decoded['type'] === 'Polygon') { + $geojson = $geom; + } else { + echo json_encode(['status'=>'error','message'=>'Format geometri tidak valid.']); + break; + } + } + + if (!$geojson) { + echo json_encode(['status' => 'error', 'message' => 'Geometri tidak valid. Harus berupa GeoJSON Polygon atau array koordinat.']); + break; + } + + $stmt = $db->prepare("INSERT INTO parsil (nama, status_kepemilikan, luas_m2, geom) VALUES (?, ?, ?, ?)"); + $stmt->execute([$nama, $status, $luas, $geojson]); + echo json_encode(['status'=>'success','message'=>'Data parsil berhasil disimpan!','id'=>$db->lastInsertId()]); + break; + + case 'update': + $input = json_decode(file_get_contents('php://input'), true); + $id = (int)($input['id'] ?? 0); + $nama = trim($input['nama'] ?? ''); + $status = $input['status_kepemilikan'] ?? 'SHM'; + $geom = $input['geom'] ?? null; + $luas = isset($input['luas_m2']) ? (float)$input['luas_m2'] : null; + + if (!$id) { echo json_encode(['status'=>'error','message'=>'ID tidak valid.']); break; } + if (!$nama) { echo json_encode(['status'=>'error','message'=>'Nama parsil wajib diisi.']); break; } + $validStatus = ['SHM','HGB','HGU','HP']; + if (!in_array($status, $validStatus)) $status = 'SHM'; + + // If geometry provided, validate/convert to GeoJSON string + $geojson = null; + if ($geom) { + if (is_array($geom)) { + if (isset($geom['type']) && $geom['type'] === 'Polygon' && isset($geom['coordinates'])) { + $geojson = json_encode($geom); + } elseif (isset($geom[0]) && is_array($geom[0])) { + $geojson = json_encode(['type'=>'Polygon','coordinates'=>$geom]); + } + } elseif (is_string($geom)) { + $decoded = json_decode($geom, true); + if ($decoded && isset($decoded['type']) && $decoded['type'] === 'Polygon') $geojson = $geom; + } + if (!$geojson) { echo json_encode(['status'=>'error','message'=>'Format geometri tidak valid untuk update.']); break; } + } + + // Build update query dynamically + $fields = ['nama = ?', 'status_kepemilikan = ?', 'updatedat = NOW()']; + $params = [$nama, $status]; + if ($luas !== null) { $fields[] = 'luas_m2 = ?'; $params[] = $luas; } + if ($geojson !== null) { $fields[] = 'geom = ?'; $params[] = $geojson; } + $params[] = $id; + + $sql = "UPDATE parsil SET " . implode(', ', $fields) . " WHERE id=?"; + $stmt = $db->prepare($sql); + $stmt->execute($params); + echo json_encode(['status'=>'success','message'=>'Data parsil berhasil diperbarui.']); + break; + + case 'delete': + $input = json_decode(file_get_contents('php://input'), true); + $id = (int)($input['id'] ?? 0); + if (!$id) { echo json_encode(['status'=>'error','message'=>'ID tidak valid.']); break; } + $stmt = $db->prepare("DELETE FROM parsil WHERE id=?"); + $stmt->execute([$id]); + echo json_encode(['status'=>'success','message'=>'Data parsil berhasil dihapus.']); + break; + + default: + echo json_encode(['status'=>'error','message'=>'Aksi tidak dikenali.']); + } +} catch (PDOException $e) { + echo json_encode(['status'=>'error','message'=>'Database error: '.$e->getMessage()]); +} \ No newline at end of file diff --git a/03/api/spbu.php b/03/api/spbu.php new file mode 100644 index 0000000..ea64d62 --- /dev/null +++ b/03/api/spbu.php @@ -0,0 +1,141 @@ +query("SELECT id, namaspbu, nomorwa, statusbuka, + JSON_EXTRACT(koordinat, '$.coordinates[1]') as lat, + JSON_EXTRACT(koordinat, '$.coordinates[0]') as lng, + koordinat + FROM spbu ORDER BY createdat DESC")->fetchAll(); + foreach ($rows as &$r) { + $r['lat'] = (float)$r['lat']; + $r['lng'] = (float)$r['lng']; + } + echo json_encode(['status' => 'success', 'data' => $rows]); + break; + + case 'create': + $input = json_decode(file_get_contents('php://input'), true); + $namaspbu = trim($input['namaspbu'] ?? ''); + $nomorwa = trim($input['nomorwa'] ?? ''); + $statusbuka = $input['statusbuka'] ?? 'limited'; + $koordinat = $input['koordinat'] ?? null; + + if (!$namaspbu) { + echo json_encode(['status'=>'error','message'=>'Nama SPBU wajib diisi.']); + break; + } + if (!in_array($statusbuka, ['full','limited'])) $statusbuka = 'limited'; + + // Validasi dan konversi geometri + $geojson = null; + if (is_array($koordinat)) { + // Cek apakah sudah dalam format GeoJSON Point + if (isset($koordinat['type']) && $koordinat['type'] === 'Point' && isset($koordinat['coordinates'])) { + $geojson = json_encode($koordinat); + } + // Mungkin hanya array [lng, lat] + elseif (isset($koordinat[0]) && isset($koordinat[1])) { + $geojson = json_encode([ + 'type' => 'Point', + 'coordinates' => [(float)$koordinat[0], (float)$koordinat[1]] + ]); + } + // Mungkin objek dengan lat,lng + elseif (isset($koordinat['lat']) && isset($koordinat['lng'])) { + $geojson = json_encode([ + 'type' => 'Point', + 'coordinates' => [(float)$koordinat['lng'], (float)$koordinat['lat']] + ]); + } + } elseif (is_string($koordinat)) { + // Coba parse JSON + $decoded = json_decode($koordinat, true); + if ($decoded && isset($decoded['type']) && $decoded['type'] === 'Point') { + $geojson = $koordinat; + } else { + echo json_encode(['status'=>'error','message'=>'Format koordinat tidak valid.']); + break; + } + } + + if (!$geojson) { + echo json_encode(['status'=>'error','message'=>'Geometri tidak valid. Harus berupa GeoJSON Point atau koordinat [lng, lat].']); + break; + } + + $stmt = $db->prepare("INSERT INTO spbu (namaspbu, nomorwa, statusbuka, koordinat) VALUES (?, ?, ?, ?)"); + $stmt->execute([$namaspbu, $nomorwa, $statusbuka, $geojson]); + echo json_encode(['status'=>'success','message'=>'Data berhasil disimpan!','id'=>$db->lastInsertId()]); + break; + + case 'update': + $input = json_decode(file_get_contents('php://input'), true); + $id = (int)($input['id'] ?? 0); + $namaspbu = trim($input['namaspbu'] ?? ''); + $nomorwa = trim($input['nomorwa'] ?? ''); + $statusbuka = $input['statusbuka'] ?? 'limited'; + $koordinat = $input['koordinat'] ?? null; + + if (!$id) { + echo json_encode(['status'=>'error','message'=>'ID tidak valid.']); + break; + } + + // =============================== + // JIKA ADA KOORDINAT → UPDATE KOORDINAT + // =============================== + if ($koordinat) { + $geojson = json_encode($koordinat); + + $stmt = $db->prepare("UPDATE spbu SET koordinat=?, updatedat=NOW() WHERE id=?"); + $stmt->execute([$geojson, $id]); + + echo json_encode(['status'=>'success','message'=>'Koordinat berhasil diperbarui!']); + break; + } + + // =============================== + // UPDATE DATA BIASA + // =============================== + if (!$namaspbu) { + echo json_encode(['status'=>'error','message'=>'Nama SPBU wajib diisi.']); + break; + } + + $stmt = $db->prepare("UPDATE spbu SET namaspbu=?, nomorwa=?, statusbuka=?, updatedat=NOW() WHERE id=?"); + $stmt->execute([$namaspbu, $nomorwa, $statusbuka, $id]); + + echo json_encode(['status'=>'success','message'=>'Data berhasil diperbarui!']); + break; + + case 'delete': + $input = json_decode(file_get_contents('php://input'), true); + $id = (int)($input['id'] ?? 0); + if (!$id) { echo json_encode(['status'=>'error','message'=>'ID tidak valid.']); break; } + $stmt = $db->prepare("DELETE FROM spbu WHERE id=?"); + $stmt->execute([$id]); + echo json_encode(['status'=>'success','message'=>'Data berhasil dihapus.']); + break; + + default: + echo json_encode(['status'=>'error','message'=>'Aksi tidak dikenali.']); + } +} catch (PDOException $e) { + echo json_encode(['status'=>'error','message'=>'Database error: '.$e->getMessage()]); +} \ No newline at end of file diff --git a/03/css/style.css b/03/css/style.css new file mode 100644 index 0000000..a45942f --- /dev/null +++ b/03/css/style.css @@ -0,0 +1,645 @@ +/* ============================================================ + WEBGIS SPBU — style.css (versi lengkap & diperbaiki) + ============================================================ */ + +/* ── CSS VARIABLES ── */ +:root { + --bg: #f0f4f8; + --sf: #ffffff; + --sf2: #f7f9fc; + --sf3: #eef2f7; + --bd: #dde3ec; + --bdh: #b0bac8; + --tx: #1a2030; + --txs: #4a5568; + --txd: #8a96a8; + + --spbu-24-fill: #22c55e; + --spbu-24-bg: rgba(34,197,94,.10); + --spbu-24-ring: rgba(34,197,94,.25); + --spbu-ltd-fill: #f87171; + --spbu-ltd-bg: rgba(248,113,113,.10); + --spbu-ltd-ring: rgba(248,113,113,.25); + + --jalan-nas: #f97316; + --jalan-nas-bg: rgba(249,115,22,.10); + --jalan-nas-ring: rgba(249,115,22,.28); + --jalan-prov: #3b82f6; + --jalan-prov-bg: rgba(59,130,246,.10); + --jalan-prov-ring: rgba(59,130,246,.28); + --jalan-kab: #10b981; + --jalan-kab-bg: rgba(16,185,129,.10); + --jalan-kab-ring: rgba(16,185,129,.28); + + --shm-stroke: #d97706; --shm-fill: #fef3c7; --shm-bg: rgba(217,119,6,.10); --shm-ring: rgba(217,119,6,.28); + --hgb-stroke: #8b5cf6; --hgb-fill: #ede9fe; --hgb-bg: rgba(139,92,246,.10); --hgb-ring: rgba(139,92,246,.28); + --hgu-stroke: #0891b2; --hgu-fill: #cffafe; --hgu-bg: rgba(8,145,178,.10); --hgu-ring: rgba(8,145,178,.28); + --hp-stroke: #ec4899; --hp-fill: #fce7f3; --hp-bg: rgba(236,72,153,.10); --hp-ring: rgba(236,72,153,.28); + + --green: #16a34a; + --greenb: rgba(22,163,74,.10); + --greenr: rgba(22,163,74,.28); + --red: #dc2626; + --redb: rgba(220,38,38,.10); + --redr: rgba(220,38,38,.28); + --blue: #2563eb; + --blueb: rgba(37,99,235,.10); + --bluer: rgba(37,99,235,.28); + --amber: #d97706; + --amberb: rgba(217,119,6,.10); + --amberr: rgba(217,119,6,.28); + --purple: #7c3aed; + --purpleb: rgba(124,58,237,.10); + --purpler: rgba(124,58,237,.28); + --indigo: #4f46e5; + --indigob: rgba(79,70,229,.10); + --indigor: rgba(79,70,229,.28); + + --sh-sm: 0 1px 3px rgba(0,0,0,.07), 0 1px 2px rgba(0,0,0,.05); + --sh: 0 4px 16px rgba(0,0,0,.09), 0 1px 4px rgba(0,0,0,.05); + --sh-lg: 0 12px 40px rgba(0,0,0,.13), 0 4px 12px rgba(0,0,0,.07); + + --sidebar: 275px; + --topbar: 52px; + --radius: 10px; + --radius-lg: 14px; +} + +/* ── RESET ── */ +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } +html, body { + height: 100%; overflow: hidden; + font-family: 'Plus Jakarta Sans', sans-serif; + font-size: 14px; background: var(--bg); color: var(--tx); + -webkit-font-smoothing: antialiased; +} +#app { display: flex; height: 100vh; } + +/* ── LOADING ── */ +#loading { + position: fixed; inset: 0; z-index: 9999; + background: var(--bg); + display: flex; align-items: center; justify-content: center; +} +.ld-inner { display: flex; flex-direction: column; align-items: center; gap: 14px; } +.ld-inner p { font-size: 13px; color: var(--txd); } +.spin { + width: 32px; height: 32px; + border: 3px solid var(--bd); border-top-color: var(--green); + border-radius: 50%; animation: rot .7s linear infinite; +} +@keyframes rot { to { transform: rotate(360deg); } } + +/* ── SIDEBAR ── */ +#sidebar { + width: var(--sidebar); min-width: var(--sidebar); + background: var(--sf); border-right: 1px solid var(--bd); + display: flex; flex-direction: column; + box-shadow: var(--sh-sm); z-index: 900; +} +.sb-header { padding: 14px 16px; border-bottom: 1px solid var(--bd); } +.sb-brand { display: flex; align-items: center; gap: 10px; } +.sb-icon { + width: 36px; height: 36px; border-radius: 9px; flex-shrink: 0; + background: var(--greenb); border: 1px solid var(--greenr); + display: flex; align-items: center; justify-content: center; + color: var(--green); font-size: 18px; +} +.sb-title { font-size: 14px; font-weight: 800; color: var(--tx); } +.sb-sub { font-size: 10px; color: var(--txd); margin-top: 1px; } + +.sb-scroll { + flex: 1; overflow-y: auto; padding: 12px; + display: flex; flex-direction: column; gap: 14px; +} +.sb-scroll::-webkit-scrollbar { width: 4px; } +.sb-scroll::-webkit-scrollbar-thumb { background: var(--bd); border-radius: 4px; } + +.sb-section { display: flex; flex-direction: column; gap: 6px; } +.sb-label { + font-size: 10px; font-weight: 700; + text-transform: uppercase; letter-spacing: .8px; + color: var(--green); /* ← hijau tema */ +} + +/* ── LEGEND ── */ +.legend-card { + background: var(--sf2); border: 1px solid var(--bd); + border-radius: var(--radius); padding: 10px 12px; + display: flex; flex-direction: column; gap: 7px; +} +.leg-row { display: flex; align-items: center; gap: 9px; font-size: 12px; color: var(--txs); font-weight: 500; } +.leg-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; border: 2px solid rgba(0,0,0,.08); } +.leg-dot.spbu-24 { background: var(--spbu-24-fill); box-shadow: 0 0 0 3px var(--spbu-24-bg); } +.leg-dot.spbu-ltd { background: var(--spbu-ltd-fill); box-shadow: 0 0 0 3px var(--spbu-ltd-bg); } +.leg-line { width: 24px; height: 4px; border-radius: 3px; flex-shrink: 0; } +.leg-line.jalan-nas { background: var(--jalan-nas); } +.leg-line.jalan-prov { background: var(--jalan-prov); } +.leg-line.jalan-kab { background: var(--jalan-kab); } +.leg-poly { width: 18px; height: 13px; border-radius: 3px; flex-shrink: 0; border: 2px solid; } +.leg-poly.parsil-shm { background: var(--shm-fill); border-color: var(--shm-stroke); } +.leg-poly.parsil-hgb { background: var(--hgb-fill); border-color: var(--hgb-stroke); } +.leg-poly.parsil-hgu { background: var(--hgu-fill); border-color: var(--hgu-stroke); } +.leg-poly.parsil-hp { background: var(--hp-fill); border-color: var(--hp-stroke); } + +/* ── STATS ── */ +.stat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; } +.stat-item { + background: var(--sf2); border: 1px solid var(--bd); + border-radius: var(--radius); padding: 10px; text-align: center; +} +.stat-item.green { background: var(--greenb); border-color: var(--greenr); } +.stat-item.blue { background: var(--blueb); border-color: var(--bluer); } +.stat-item.purple { background: var(--purpleb); border-color: var(--purpler); } +.stat-num { font-size: 22px; font-weight: 800; font-family: 'Fira Code', monospace; color: var(--tx); } +.stat-item.green .stat-num { color: var(--green); } +.stat-item.blue .stat-num { color: var(--blue); } +.stat-item.purple .stat-num { color: var(--purple); } +.stat-lbl { font-size: 10px; color: var(--txd); margin-top: 2px; } + +/* ── SPBU LIST ── */ +.spbu-item { + background: var(--sf); border: 1px solid var(--bd); + border-radius: var(--radius); padding: 9px 11px; + cursor: pointer; margin-bottom: 5px; + display: flex; flex-direction: column; gap: 6px; + transition: border-color .18s, background .18s; +} +.spbu-item:hover { border-color: var(--green); background: var(--greenb); } +.si-top { display: flex; align-items: center; justify-content: space-between; gap: 6px; } +.si-name { font-size: 13px; font-weight: 700; display: flex; align-items: center; gap: 7px; } +.si-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; } +.si-dot.dot-green { background: var(--spbu-24-fill); box-shadow: 0 0 0 2px var(--spbu-24-bg); } +.si-dot.dot-red { background: var(--spbu-ltd-fill); box-shadow: 0 0 0 2px var(--spbu-ltd-bg); } +.si-actions { display: flex; gap: 4px; flex-shrink: 0; } +.empty { text-align: center; padding: 24px 0; font-size: 12px; color: var(--txd); } + +/* icon buttons — SERAGAM HIJAU */ +.ib { + width: 30px; height: 30px; border-radius: 8px; + background: var(--greenb); border: 1px solid var(--greenr); + color: var(--green); /* ← hijau tema */ + cursor: pointer; + display: flex; align-items: center; justify-content: center; + transition: all .18s; flex-shrink: 0; font-size: 13px; +} +.ib.del { + background: var(--redb); border-color: var(--redr); color: var(--red); +} +.ib:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(22,163,74,.15); } +.ib.del:hover { box-shadow: 0 4px 12px rgba(220,38,38,.15); } + +/* ── BADGE ── */ +.badge { + display: inline-flex; align-items: center; + font-size: 10px; font-weight: 700; + padding: 2px 8px; border-radius: 20px; border: 1px solid; +} +.badge.bg { background: var(--spbu-24-bg); border-color: var(--spbu-24-ring); color: #15803d; } +.badge.br { background: var(--spbu-ltd-bg); border-color: var(--spbu-ltd-ring); color: #dc2626; } +.badge.bb-orange { background: var(--jalan-nas-bg); border-color: var(--jalan-nas-ring); color: #c2410c; } +.badge.bb-blue { background: var(--jalan-prov-bg); border-color: var(--jalan-prov-ring); color: #1d4ed8; } +.badge.bb-green { background: var(--jalan-kab-bg); border-color: var(--jalan-kab-ring); color: #065f46; } +.badge.bp-shm { background: var(--shm-bg); border-color: var(--shm-ring); color: var(--shm-stroke); } +.badge.bp-hgb { background: var(--hgb-bg); border-color: var(--hgb-ring); color: var(--hgb-stroke); } +.badge.bp-hgu { background: var(--hgu-bg); border-color: var(--hgu-ring); color: var(--hgu-stroke); } +.badge.bp-hp { background: var(--hp-bg); border-color: var(--hp-ring); color: var(--hp-stroke); } + +/* ── GEOM SELECTOR ── */ +.geom-selector { + display: flex; gap: 3px; + background: var(--sf2); border: 1px solid var(--bd); + border-radius: 9px; padding: 3px; +} +.geom-btn { + flex: 1; display: flex; align-items: center; justify-content: center; + gap: 5px; padding: 6px 8px; border-radius: 7px; + border: none; background: transparent; color: var(--txs); + font-family: 'Plus Jakarta Sans', sans-serif; font-size: 11px; font-weight: 600; + cursor: pointer; transition: all .18s; white-space: nowrap; +} +.geom-btn:hover { background: var(--sf); color: var(--tx); } +.geom-btn.active { background: var(--green); color: #fff; box-shadow: 0 2px 8px rgba(22,163,74,.3); } +.geom-hint { + font-size: 10px; color: var(--txd); text-align: left; + margin-top: 4px; min-height: 14px; padding-left: 2px; + transition: color .18s; display: flex; align-items: center; gap: 5px; +} +.geom-hint .gh-dot { + width: 6px; height: 6px; border-radius: 50%; + background: var(--bdh); flex-shrink: 0; transition: background .18s; +} +.geom-hint.on { color: var(--green); font-weight: 600; } +.geom-hint.on .gh-dot { background: var(--green); box-shadow: 0 0 0 2px rgba(22,163,74,.2); } + +/* ── MAIN ── */ +#main { flex: 1; display: flex; flex-direction: column; overflow: hidden; position: relative; } + +/* ── TOPBAR ── */ +#topbar { + height: var(--topbar); min-height: var(--topbar); + background: var(--sf); border-bottom: 1px solid var(--bd); + display: flex; align-items: center; padding: 0 12px; gap: 8px; + box-shadow: var(--sh-sm); overflow: visible; + position: relative; z-index: 850; +} + +/* koordinat pill */ +.coord-pill { + flex: 0 0 auto; font-family: 'Fira Code', monospace; + font-size: 11px; color: var(--txd); + background: var(--sf2); border: 1px solid var(--bd); + border-radius: 7px; padding: 4px 10px; + display: flex; align-items: center; gap: 5px; white-space: nowrap; +} +.coord-pill > span:first-child { color: var(--green); } /* ← ikon hijau */ +.pill-sep { color: var(--bdh); } + +/* ── SEARCH ── */ +#search-wrap { flex: 1; position: relative; max-width: 340px; z-index: 1300; } +.search-box { + display: flex; align-items: center; gap: 7px; + background: var(--sf2); border: 1.5px solid var(--bd); + border-radius: 9px; padding: 0 10px; height: 34px; + transition: border-color .18s, box-shadow .18s; +} +.search-box:focus-within { + border-color: var(--green); + box-shadow: 0 0 0 3px rgba(22,163,74,.08); +} +.search-icon { + font-size: 13px; flex-shrink: 0; + color: var(--green); /* ← hijau tema */ +} +#search-input { + flex: 1; border: none; background: transparent; outline: none; + font-family: 'Plus Jakarta Sans', sans-serif; font-size: 13px; color: var(--tx); +} +#search-input::placeholder { color: var(--txd); } +#search-results { + position: absolute; top: calc(100% + 6px); left: 0; right: 0; + background: var(--sf); border: 1px solid var(--bd); + border-radius: 11px; box-shadow: var(--sh-lg); + overflow: hidden; opacity: 0; pointer-events: none; + transform: translateY(6px); + transition: opacity .18s, transform .18s; + max-height: 320px; overflow-y: auto; +} +#search-results.open { opacity: 1; pointer-events: all; transform: translateY(0); } +.sr-item { + display: flex; align-items: center; gap: 10px; + padding: 9px 12px; cursor: pointer; + transition: background .14s; border-bottom: 1px solid var(--sf3); +} +.sr-item:last-child { border-bottom: none; } +.sr-item:hover { background: var(--sf2); } +.sr-icon { + width: 30px; height: 30px; border-radius: 8px; + background: var(--greenb); border: 1px solid var(--greenr); /* ← hijau tema */ + display: flex; align-items: center; justify-content: center; + font-size: 14px; flex-shrink: 0; +} +.sr-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; } +.sr-label { font-size: 13px; font-weight: 700; color: var(--tx); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.sr-sub { font-size: 10px; color: var(--txd); font-weight: 500; } +.sr-empty { padding: 16px 12px; font-size: 12px; color: var(--txd); text-align: center; } + +/* ── LAYER DROPDOWN ── */ +.layer-wrap { position: absolute; top: 10px; right: 10px; flex-shrink: 0; z-index: 1100; } +.layer-btn { + height: 34px; padding: 0 12px; border-radius: 8px; + background: var(--sf); border: 1px solid var(--bd); + color: var(--txs); font-size: 12px; font-weight: 600; + font-family: 'Plus Jakarta Sans', sans-serif; + cursor: pointer; display: flex; align-items: center; gap: 6px; + transition: all .18s; white-space: nowrap; box-shadow: var(--sh-sm); +} +.layer-btn:hover { border-color: var(--bdh); color: var(--tx); } +.layer-btn svg { color: var(--green); flex-shrink: 0; } /* ← ikon SVG hijau */ +.layer-btn .chev { color: var(--green); font-size: 10px; } /* ← chevron hijau */ + +.layer-menu { + position: absolute; top: calc(100% + 6px); right: 0; + background: var(--sf); border: 1px solid var(--bd); + border-radius: 13px; padding: 8px 6px; min-width: 210px; + box-shadow: var(--sh-lg); z-index: 1200; + opacity: 0; pointer-events: none; + transform: translateY(6px); + transition: opacity .18s, transform .18s; +} +.layer-menu.open { opacity: 1; pointer-events: all; transform: translateY(0); } + +.lm-label { + font-size: 10px; font-weight: 700; text-transform: uppercase; + letter-spacing: .8px; color: var(--green); /* ← hijau tema */ + padding: 4px 10px 8px; +} + +/* Item utama layer */ +.lm-item { + width: 100%; display: flex; align-items: center; gap: 8px; + padding: 7px 10px; border-radius: 8px; cursor: pointer; + transition: background .14s; font-size: 13px; + font-weight: 600; color: var(--tx); user-select: none; +} +.lm-item:hover { background: var(--sf2); } +.lm-item input[type="checkbox"] { display: none; } +.lm-icon { font-size: 14px; } + +/* Custom checkbox */ +.lm-check { + width: 16px; height: 16px; border-radius: 5px; + border: 1.5px solid var(--bdh); background: var(--sf2); flex-shrink: 0; + display: flex; align-items: center; justify-content: center; + transition: all .16s; position: relative; +} +.lm-check.sm { width: 14px; height: 14px; border-radius: 4px; } +.lm-item input:checked ~ .lm-check, +.lm-sub-item input:checked ~ .lm-check { + background: var(--green); border-color: var(--green); +} +.lm-item input:checked ~ .lm-check::after, +.lm-sub-item input:checked ~ .lm-check::after { + content: ''; display: block; + width: 4px; height: 7px; + border: 2px solid #fff; border-top: none; border-left: none; + transform: rotate(45deg) translate(-1px, -1px); +} + +/* Sub-option SPBU */ +.lm-sub { + margin: 2px 0 4px 10px; padding: 4px 0 4px 10px; + border-left: 2px solid var(--greenr); /* ← hijau tema */ + display: flex; flex-direction: column; gap: 2px; + max-height: 100px; overflow: hidden; + transition: max-height .2s ease, opacity .2s ease; opacity: 1; +} +.lm-sub.hidden { max-height: 0; opacity: 0; pointer-events: none; } +.lm-sub-item { + display: flex; align-items: center; gap: 8px; + padding: 5px 8px; border-radius: 7px; cursor: pointer; + font-size: 12px; font-weight: 500; color: var(--txs); + user-select: none; transition: background .14s; +} +.lm-sub-item:hover { background: var(--sf2); } +.lm-sub-item input[type="checkbox"] { display: none; } +.lm-sub-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; } +.lm-sub-dot.dot-green { background: var(--spbu-24-fill); box-shadow: 0 0 0 2px var(--spbu-24-bg); } +.lm-sub-dot.dot-red { background: var(--spbu-ltd-fill); box-shadow: 0 0 0 2px var(--spbu-ltd-bg); } + +/* ── MAP ── */ +#map-wrap { flex: 1; position: relative; overflow: hidden; } +#map { width: 100%; height: 100%; } +.leaflet-container.crosshair-cursor { cursor: crosshair !important; } + +/* ── DRAW TOOLBAR ── */ +#draw-toolbar, +#geom-edit-toolbar { + position: absolute; bottom: 28px; left: 50%; + transform: translateX(-50%) translateY(12px); + z-index: 1000; display: flex; align-items: center; gap: 5px; + background: var(--sf); border: 1px solid var(--bd); + border-radius: 14px; padding: 8px 14px; + box-shadow: var(--sh-lg); pointer-events: all; + opacity: 0; visibility: hidden; + transition: opacity .22s, transform .22s, visibility .22s; +} +#draw-toolbar.visible, +#geom-edit-toolbar.visible { + opacity: 1; visibility: visible; + transform: translateX(-50%) translateY(0); +} +#geom-edit-toolbar { bottom: 28px; } +#draw-toolbar.visible ~ #geom-edit-toolbar.visible { bottom: 90px; } + +.dt-hint { font-size: 12px; font-weight: 600; color: var(--green); padding: 0 4px; white-space: nowrap; } /* ← hijau */ +.dt-counter { + font-family: 'Fira Code', monospace; font-size: 12px; font-weight: 600; + color: var(--green); background: var(--greenb); /* ← hijau */ + border: 1px solid var(--greenr); border-radius: 7px; + padding: 4px 10px; white-space: nowrap; +} +.geom-edit-counter { + color: var(--indigo); background: var(--indigob); + border-color: var(--indigor); min-width: 120px; +} +.dt-btn { + display: flex; align-items: center; gap: 5px; + padding: 7px 13px; border-radius: 9px; + border: 1.5px solid var(--greenr); /* ← hijau */ + background: var(--greenb); color: var(--green); /* ← hijau */ + font-family: 'Plus Jakarta Sans', sans-serif; font-size: 12px; font-weight: 600; + cursor: pointer; transition: all .18s; white-space: nowrap; flex-shrink: 0; +} +.dt-btn:hover { background: var(--sf); border-color: var(--bdh); color: var(--tx); } +.dt-btn:disabled { opacity: .4; cursor: not-allowed; } +.dt-btn.undo:hover { background: var(--amberb); border-color: var(--amberr); color: var(--amber); } +.dt-btn.redo:hover { background: var(--blueb); border-color: var(--bluer); color: var(--blue); } +.dt-btn.cancel { + background: var(--redb); border-color: var(--redr); color: var(--red); +} +.dt-btn.cancel:hover { background: var(--red); color: #fff; border-color: var(--red); } +.dt-btn.finish { + background: var(--green); border-color: var(--green); color: #fff; +} +.dt-btn.finish:hover { opacity: .88; } + +/* ── POPUP / PCARD ── */ +.leaflet-popup-content-wrapper { + background: var(--sf) !important; border: 1px solid var(--bd) !important; + border-radius: 16px !important; box-shadow: var(--sh-lg) !important; + padding: 0 !important; overflow: hidden !important; color: var(--tx) !important; +} +.leaflet-popup-content { margin: 0 !important; width: auto !important; } +.leaflet-popup-tip-container { display: none; } +.leaflet-popup-close-button { + top: 10px !important; right: 12px !important; + width: 26px !important; height: 26px !important; + font-size: 14px !important; line-height: 26px !important; + color: var(--txd) !important; background: var(--sf2) !important; + border-radius: 8px !important; border: 1px solid var(--bd) !important; +} + +/* Popup card */ +.pcard-detail { min-width: 300px; max-width: 360px; font-family: 'Plus Jakarta Sans', sans-serif; } + +/* Header popup */ +.pcd-header { + display: flex; align-items: center; gap: 10px; + padding: 14px 16px 12px; + border-bottom: 1px solid var(--bd); +} +.pcd-header-icon { + width: 38px; height: 38px; border-radius: 10px; flex-shrink: 0; + background: var(--greenb); border: 1px solid var(--greenr); + display: flex; align-items: center; justify-content: center; font-size: 18px; +} +.pcd-header-icon.jalan { background: var(--jalan-prov-bg); border-color: var(--jalan-prov-ring); } +.pcd-header-icon.parsil { background: var(--shm-bg); border-color: var(--shm-ring); } +.pcd-header-text { flex: 1; min-width: 0; } +.pcd-title { + font-size: 15px; font-weight: 800; color: var(--tx); + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} +.pcd-subtitle { font-size: 11px; color: var(--txd); margin-top: 2px; } + +/* Rows popup */ +.pcd-rows { display: flex; flex-direction: column; gap: 6px; padding: 12px 16px; } +.pcd-row { + display: flex; align-items: center; justify-content: space-between; gap: 12px; + background: var(--sf2); padding: 8px 12px; + border-radius: 10px; border: 1px solid var(--bd); +} +.pcd-label { font-size: 11px; font-weight: 600; color: var(--txd); display: flex; align-items: center; gap: 5px; white-space: nowrap; } +.pcd-val { font-size: 13px; font-weight: 700; color: var(--tx); text-align: right; word-break: break-word; max-width: 180px; } + +/* Actions popup */ +.pcd-actions { + display: flex; gap: 6px; padding: 10px 16px 14px; + border-top: 1px solid var(--bd); +} +.pbtn { + flex: 1; display: flex; align-items: center; justify-content: center; + gap: 5px; padding: 8px 10px; border-radius: 10px; + font-size: 12px; font-weight: 700; cursor: pointer; + transition: all .18s; border: 1.5px solid; font-family: 'Plus Jakarta Sans', sans-serif; +} +/* Edit → hijau tema */ +.pbtn-edit { + background: var(--greenb); color: var(--green); border-color: var(--greenr); +} +.pbtn-edit:hover { background: var(--green); color: #fff; border-color: var(--green); transform: translateY(-1px); } +/* Move → indigo */ +.pbtn-move { + background: var(--indigob); color: var(--indigo); border-color: var(--indigor); +} +.pbtn-move:hover { background: var(--indigo); color: #fff; border-color: var(--indigo); transform: translateY(-1px); } +/* Delete → merah */ +.pbtn-del { + background: var(--redb); color: var(--red); border-color: var(--redr); +} +.pbtn-del:hover { background: var(--red); color: #fff; border-color: var(--red); transform: translateY(-1px); } + +/* ── MODAL ── */ +.overlay { + position: fixed; inset: 0; z-index: 2000; + background: rgba(15,20,35,.45); backdrop-filter: blur(6px); + display: flex; align-items: center; justify-content: center; + opacity: 0; pointer-events: none; transition: opacity .22s; +} +.overlay.open { opacity: 1; pointer-events: all; } + +.modal { + background: var(--sf); border: 1px solid var(--bd); + border-radius: var(--radius-lg); width: 440px; max-width: 96vw; + box-shadow: var(--sh-lg); overflow: hidden; + transform: translateY(14px) scale(.98); transition: transform .22s; +} +.overlay.open .modal { transform: translateY(0) scale(1); } + +/* Modal header strip */ +.modal-head { + display: flex; align-items: center; gap: 12px; + padding: 16px 20px 14px; + border-bottom: 1px solid var(--bd); + background: var(--greenb); /* ← hijau strip */ +} +.modal-head-icon { + width: 34px; height: 34px; border-radius: 9px; flex-shrink: 0; + background: var(--green); color: #fff; + display: flex; align-items: center; justify-content: center; font-size: 16px; +} +.modal-head h2 { font-size: 15px; font-weight: 800; color: var(--green); } /* ← hijau */ + +/* Modal body */ +.modal-body { padding: 16px 20px; display: flex; flex-direction: column; gap: 12px; } + +/* Form group */ +.form-group { display: flex; flex-direction: column; gap: 5px; } +.form-group label { + font-size: 11px; font-weight: 700; text-transform: uppercase; + letter-spacing: .5px; color: var(--green); /* ← hijau */ +} +.form-group input, +.form-group select { + width: 100%; padding: 10px 12px; + background: var(--sf2); border: 1.5px solid var(--bd); + border-radius: 10px; color: var(--tx); + font-size: 13px; font-family: 'Plus Jakarta Sans', sans-serif; + outline: none; transition: border-color .18s, box-shadow .18s; + appearance: none; +} +.form-group input:focus, +.form-group select:focus { + border-color: var(--green); + box-shadow: 0 0 0 3px rgba(22,163,74,.08); +} +.form-group input::placeholder { color: var(--txd); } +.form-group input[readonly] { background: var(--sf3); color: var(--txd); cursor: default; } +.form-group select { + cursor: pointer; + background-image: url('data:image/svg+xml;utf8,'); + background-repeat: no-repeat; background-position: right 10px center; background-size: 16px; + padding-right: 32px; +} + +/* Form grid (2 kolom) */ +.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } + +/* Modal footer */ +.modal-foot { + display: flex; gap: 8px; + padding: 12px 20px 16px; + border-top: 1px solid var(--bd); + background: var(--sf2); +} +.btn-ghost { + flex: 1; padding: 10px; border-radius: 9px; + border: 1.5px solid var(--bd); background: transparent; + color: var(--txs); font-size: 13px; font-weight: 600; + font-family: 'Plus Jakarta Sans', sans-serif; + cursor: pointer; transition: all .18s; +} +.btn-ghost:hover { border-color: var(--bdh); color: var(--tx); background: var(--sf); } +.btn-primary { + flex: 1.4; padding: 10px; border-radius: 9px; border: none; color: #fff; + font-size: 13px; font-weight: 700; font-family: 'Plus Jakarta Sans', sans-serif; + cursor: pointer; transition: opacity .18s, transform .12s; + display: flex; align-items: center; justify-content: center; gap: 6px; +} +.btn-primary:hover { opacity: .9; transform: translateY(-1px); } +.btn-primary.green { background: linear-gradient(135deg, #22c55e, #16a34a); } +.btn-primary.blue { background: linear-gradient(135deg, #3b82f6, #2563eb); } +.btn-primary.purple { background: linear-gradient(135deg, #a78bfa, #7c3aed); } +.btn-primary.red { background: linear-gradient(135deg, #f87171, #dc2626); } + +/* ── CONFIRM MODAL ── */ +.confirm-modal { width: 360px; max-width: 94vw; } +.confirm-body { padding: 28px 24px 8px; text-align: center; } +.confirm-icon { font-size: 36px; margin-bottom: 10px; line-height: 1; } +.confirm-title { font-size: 16px; font-weight: 800; color: var(--tx); margin-bottom: 6px; } +.confirm-message { font-size: 13px; color: var(--txs); line-height: 1.5; } + +/* ── TOAST ── */ +#toasts { + position: fixed; bottom: 18px; right: 18px; z-index: 9999; + display: flex; flex-direction: column; gap: 6px; +} +.toast { + background: var(--sf); border: 1px solid var(--bd); + border-radius: 9px; padding: 9px 14px; + font-size: 12px; font-weight: 600; + display: flex; align-items: center; gap: 8px; + min-width: 220px; box-shadow: var(--sh); + animation: tslide .22s ease; +} +.toast.ok { border-color: var(--greenr); color: var(--green); } +.toast.er { border-color: var(--redr); color: var(--red); } +@keyframes tslide { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } + +/* ── RESPONSIVE ── */ +@media (max-width: 640px) { + .modal { width: 92vw; } + .pcard-detail { min-width: 200px; max-width: 260px; } + #search-wrap { max-width: 160px; } +} \ No newline at end of file diff --git a/03/database.sql b/03/database.sql new file mode 100644 index 0000000..4d9321a --- /dev/null +++ b/03/database.sql @@ -0,0 +1,37 @@ +-- Tabel SPBU (Point) +CREATE TABLE `spbu` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `namaspbu` varchar(150) NOT NULL, + `nomorwa` varchar(20) DEFAULT NULL, + `statusbuka` enum('full','limited') DEFAULT 'limited', + `koordinat` JSON DEFAULT NULL COMMENT 'GeoJSON Point', + `lat` decimal(10,8) GENERATED ALWAYS AS (JSON_EXTRACT(`koordinat`, '$.coordinates[1]')) STORED, + `lng` decimal(11,8) GENERATED ALWAYS AS (JSON_EXTRACT(`koordinat`, '$.coordinates[0]')) STORED, + `createdat` timestamp NOT NULL DEFAULT current_timestamp(), + `updatedat` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp(), + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- Tabel Jalan (Polyline) +CREATE TABLE `jalan` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `nama` varchar(150) NOT NULL, + `status` enum('nasional','provinsi','kabupaten') NOT NULL DEFAULT 'kabupaten', + `panjang_meter` decimal(12,2) DEFAULT NULL, + `geom` JSON NOT NULL COMMENT 'GeoJSON LineString', + `createdat` timestamp NOT NULL DEFAULT current_timestamp(), + `updatedat` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp(), + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- Tabel Parsil Tanah (Polygon) +CREATE TABLE `parsil` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `nama` varchar(150) NOT NULL, + `status_kepemilikan` enum('SHM','HGB','HGU','HP') NOT NULL DEFAULT 'SHM', + `luas_m2` decimal(14,2) DEFAULT NULL, + `geom` JSON NOT NULL COMMENT 'GeoJSON Polygon', + `createdat` timestamp NOT NULL DEFAULT current_timestamp(), + `updatedat` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp(), + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; \ No newline at end of file diff --git a/03/index.html b/03/index.html new file mode 100644 index 0000000..aec1d46 --- /dev/null +++ b/03/index.html @@ -0,0 +1,420 @@ + + + + + + WebGIS SPBU, Jalan & Parsil + + + + + + + +
+
+
+

Memuat WebGIS…

+
+
+ +
+ + + + + +
+
+
+ 📍 + | +
+ +
+ +
+
+ + +
+ +
+
Tampilkan Layer
+ + + +
+ + +
+ + + + +
+
+
+ +
+
+ + +
+
ℹ️ Klik peta untuk menggambar
+ 0 titik + + + + +
+ + +
+
📐 Drag titik untuk edit geometri
+ + + +
+
+
+
+ + + + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/03/js/main.js b/03/js/main.js new file mode 100644 index 0000000..17eca16 --- /dev/null +++ b/03/js/main.js @@ -0,0 +1,923 @@ +// =============================== +// KONFIGURASI API +// =============================== +const API = { + spbu: 'api/spbu.php', + jalan: 'api/jalan.php', + parsil: 'api/parsil.php' +}; + +// =============================== +// WARNA TEMA +// =============================== +const COLORS = { + spbu24: { fill: '#22c55e', stroke: '#15803d', glow: 'rgba(34,197,94,.35)' }, + spbuLtd: { fill: '#f87171', stroke: '#dc2626', glow: 'rgba(248,113,113,.35)' }, + jalanNasional: '#f97316', + jalanProvinsi: '#3b82f6', + jalanKabupaten: '#10b981', + SHM: { stroke: '#d97706', fill: '#fef3c7' }, + HGB: { stroke: '#8b5cf6', fill: '#ede9fe' }, + HGU: { stroke: '#0891b2', fill: '#cffafe' }, + HP: { stroke: '#ec4899', fill: '#fce7f3' }, +}; + +// =============================== +// STATE GLOBAL +// =============================== +let map; +let layers = { jalan: null, parsil: null }; +let spbu24Group, spbuLtdGroup; + +let currentGeom = null; +let drawPoints = []; +let tempLayer = null; +let savedDrawPoints = []; + +let allSpbu = [], allJalan = [], allParsil = []; +let isDraggingMarker = false; + +let vertexMarkers = []; +let undoStack = []; +let redoStack = []; + +let editingFeature = null; + +// =============================== +// CONFIRM DIALOG +// =============================== +function showConfirm(message, title = 'Konfirmasi') { + return new Promise(resolve => { + const overlay = document.getElementById('confirm-overlay'); + const titleEl = document.getElementById('confirm-title'); + const msgEl = document.getElementById('confirm-message'); + if (titleEl) titleEl.textContent = title; + if (msgEl) msgEl.textContent = message; + overlay.classList.add('open'); + const btnOk = document.getElementById('confirm-ok'); + const btnCancel = document.getElementById('confirm-cancel'); + function cleanup() { + overlay.classList.remove('open'); + btnOk.removeEventListener('click', onOk); + btnCancel.removeEventListener('click', onCancel); + } + function onOk() { cleanup(); resolve(true); } + function onCancel() { cleanup(); resolve(false); } + btnOk.addEventListener('click', onOk); + btnCancel.addEventListener('click', onCancel); + }); +} + +function showToast(msg, type = 'ok') { + const container = document.getElementById('toasts'); + const toast = document.createElement('div'); + toast.className = `toast ${type}`; + toast.innerHTML = (type === 'ok' ? '✅ ' : '❌ ') + msg; + container.appendChild(toast); + setTimeout(() => toast.remove(), 3200); +} + +// =============================== +// ICON SPBU +// =============================== +function makeSpbuIcon(is24) { + const c = is24 ? COLORS.spbu24 : COLORS.spbuLtd; + const id = is24 ? 'g' : 'r'; + const svg = ` + + + + + + + + + + + `; + return L.divIcon({ html: svg, className: '', iconSize: [32, 40], iconAnchor: [16, 38], popupAnchor: [0, -38] }); +} + +// =============================== +// INIT MAP +// =============================== +function initMap() { + map = L.map('map').setView([-0.0263, 109.3425], 14); + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap', maxZoom: 19 + }).addTo(map); + + map.on('click', onMapClick); + map.on('mousemove', e => { + document.getElementById('cur-lat').innerText = e.latlng.lat.toFixed(5); + document.getElementById('cur-lng').innerText = e.latlng.lng.toFixed(5); + }); + + spbu24Group = L.layerGroup().addTo(map); + spbuLtdGroup = L.layerGroup().addTo(map); +} + +// =============================== +// BUILD POPUP — SPBU +// =============================== +function buildSpbuPopup(s) { + const nama = s.namaspbu || '—'; + const is24 = s.statusbuka === 'full'; + const coord = (s.lat && s.lng) ? `${Number(s.lat).toFixed(6)}, ${Number(s.lng).toFixed(6)}` : '—'; + const wa = s.nomorwa || ''; + const badgeCls = is24 ? 'bg' : 'br'; + const statusLabel = is24 ? 'Buka 24 Jam' : 'Tidak 24 Jam'; + + return ` +
+
+
+
+
${nama}
+
SPBU
+
+
+
+
+ 📍 Koordinat + ${coord} +
+
+ ⏰ Status + ${statusLabel} +
+ ${wa ? `
+ 📱 WhatsApp + ${wa} +
` : ''} +
+
+ + +
+
`; +} + +// =============================== +// BUILD POPUP — JALAN +// =============================== +function buildJalanPopup(p) { + const nama = p.nama || '—'; + const status = p.status || 'kabupaten'; + const panjang = p.panjang_meter ? Number(p.panjang_meter).toFixed(2) + ' m' : '—'; + let statusLabel = 'Kabupaten', badgeClass = 'bb-green'; + if (status === 'nasional') { statusLabel = 'Nasional'; badgeClass = 'bb-orange'; } + if (status === 'provinsi') { statusLabel = 'Provinsi'; badgeClass = 'bb-blue'; } + + return ` +
+
+
🛣️
+
+
${nama}
+
Jalan
+
+
+
+
+ 🛣️ Status + ${statusLabel} +
+
+ 📏 Panjang + ${panjang} +
+
+
+ + + +
+
`; +} + +// =============================== +// BUILD POPUP — PARSIL +// =============================== +function buildParsilPopup(props) { + const nama = props.nama || '—'; + const status = props.status_kepemilikan || 'SHM'; + const luas = props.luas_m2 ? Number(props.luas_m2).toFixed(2) + ' m²' : '—'; + const badgeMap = { SHM: 'bp-shm', HGB: 'bp-hgb', HGU: 'bp-hgu', HP: 'bp-hp' }; + const badgeClass = badgeMap[status] || 'bp-shm'; + + return ` +
+
+
🗺️
+
+
${nama}
+
Parsil Tanah
+
+
+
+
+ 📋 Kepemilikan + ${status} +
+
+ 📐 Luas + ${luas} +
+
+
+ + + +
+
`; +} + +// =============================== +// RENDER SPBU +// =============================== +function renderSpbuLayer() { + spbu24Group.clearLayers(); + spbuLtdGroup.clearLayers(); + + allSpbu.forEach(s => { + const is24 = s.statusbuka === 'full'; + const icon = makeSpbuIcon(is24); + const marker = L.marker([s.lat, s.lng], { icon, draggable: true, title: s.namaspbu }); + marker.bindPopup(buildSpbuPopup(s)); + marker.on('click', () => { if (!currentGeom) marker.openPopup(); }); + marker.on('dragstart', () => { isDraggingMarker = true; marker.setOpacity(0.65); }); + marker.on('dragend', async () => { + isDraggingMarker = false; + marker.setOpacity(1); + const newPos = marker.getLatLng(); + await updateSpbuCoordinates(s.id, newPos.lat, newPos.lng); + s.lat = newPos.lat; s.lng = newPos.lng; + marker.setPopupContent(buildSpbuPopup(s)); + showToast('Koordinat SPBU diperbarui'); + }); + if (is24) spbu24Group.addLayer(marker); + else spbuLtdGroup.addLayer(marker); + }); +} + +// =============================== +// LOAD DATA +// =============================== +async function loadAllData() { + try { + const [spbuRes, jalanRes, parsilRes] = await Promise.all([ + fetch(API.spbu + '?action=list').then(r => r.json()), + fetch(API.jalan + '?action=list').then(r => r.json()), + fetch(API.parsil + '?action=list').then(r => r.json()) + ]); + allSpbu = spbuRes.data || []; + allJalan = jalanRes.data || []; + allParsil = parsilRes.data || []; + renderSpbuLayer(); + renderJalanLayer(); + renderParsilLayer(); + renderSpbuList(); + } catch (e) { + console.error(e); + } finally { + document.getElementById('loading').style.display = 'none'; + } +} + +// =============================== +// RENDER JALAN +// =============================== +function renderJalanLayer() { + if (layers.jalan) map.removeLayer(layers.jalan); + const geojson = allJalan.map(j => ({ + type: 'Feature', geometry: JSON.parse(j.geom), properties: j + })); + layers.jalan = L.geoJSON(geojson, { + style: f => { + const s = f.properties.status; + let color = COLORS.jalanKabupaten; + if (s === 'nasional') color = COLORS.jalanNasional; + else if (s === 'provinsi') color = COLORS.jalanProvinsi; + return { color, weight: 4, opacity: 0.9 }; + }, + onEachFeature: (f, layer) => { + layer.bindPopup(buildJalanPopup(f.properties)); + layer.on('click', e => { if (!currentGeom) { L.DomEvent.stopPropagation(e); layer.openPopup(); } }); + } + }).addTo(map); +} + +// =============================== +// RENDER PARSIL +// =============================== +function renderParsilLayer() { + if (layers.parsil) map.removeLayer(layers.parsil); + const geojson = allParsil.map(p => ({ + type: 'Feature', geometry: JSON.parse(p.geom), properties: p + })); + layers.parsil = L.geoJSON(geojson, { + style: f => { + const c = COLORS[f.properties.status_kepemilikan] || COLORS.SHM; + return { color: c.stroke, fillColor: c.fill, weight: 2.5, fillOpacity: 0.45 }; + }, + onEachFeature: (f, layer) => { + layer.bindPopup(buildParsilPopup(f.properties)); + layer.on('click', e => { if (!currentGeom) { L.DomEvent.stopPropagation(e); layer.openPopup(); } }); + } + }).addTo(map); +} + +// =============================== +// LIST SPBU & STATS +// =============================== +function renderSpbuList() { + const el = document.getElementById('spbu-list'); + if (!el) return; + document.getElementById('cnt-total').innerText = allSpbu.length; + document.getElementById('cnt-24').innerText = allSpbu.filter(s => s.statusbuka === 'full').length; + document.getElementById('cnt-jalan').innerText = allJalan.length; + document.getElementById('cnt-parsil').innerText = allParsil.length; + + if (!allSpbu.length) { + el.innerHTML = '
Belum ada SPBU tercatat.
'; + return; + } + el.innerHTML = allSpbu.map(s => { + const lat = parseFloat(s.lat) || 0; + const lng = parseFloat(s.lng) || 0; + const dot = s.statusbuka === 'full' ? 'dot-green' : 'dot-red'; + return ` +
+
+
+ + ${s.namaspbu || '—'} +
+
+ + +
+
+
`; + }).join(''); +} + +function flyTo(lat, lng) { if (map) map.setView([lat, lng], 16); } + +// =============================== +// EDIT / HAPUS SPBU +// =============================== +function editSpbu(id) { + const s = allSpbu.find(x => x.id == id); + if (!s) return showToast('Data SPBU tidak ditemukan', 'er'); + document.getElementById('e-spbu-id').value = s.id; + document.getElementById('e-spbu-nama').value = s.namaspbu || ''; + document.getElementById('e-spbu-wa').value = s.nomorwa || ''; + document.getElementById('e-spbu-status').value = s.statusbuka || 'limited'; + openModal('m-edit-spbu'); +} + +async function hapusSpbu(id) { + const ok = await showConfirm('Hapus SPBU ini? Tindakan tidak dapat dibatalkan.', 'Hapus SPBU'); + if (!ok) return; + try { + const res = await fetch(API.spbu, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action: 'delete', id }) + }); + const j = await res.json(); + if (j.status === 'success') { loadAllData(); showToast('SPBU berhasil dihapus'); } + else showToast(j.message || 'Gagal hapus', 'er'); + } catch (e) { showToast('Gagal: ' + e.message, 'er'); } +} + +async function updateSpbu() { + const id = parseInt(document.getElementById('e-spbu-id').value || 0); + const namaspbu = document.getElementById('e-spbu-nama').value; + const nomorwa = document.getElementById('e-spbu-wa').value; + const statusbuka = document.getElementById('e-spbu-status').value; + try { + const res = await fetch(API.spbu, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action: 'update', id, namaspbu, nomorwa, statusbuka }) + }); + const j = await res.json(); + if (j.status === 'success') { closeModal('m-edit-spbu'); loadAllData(); showToast('SPBU diperbarui'); } + else showToast(j.message || 'Gagal update', 'er'); + } catch (e) { showToast('Gagal: ' + e.message, 'er'); } +} + +async function updateSpbuCoordinates(id, lat, lng) { + const spbu = allSpbu.find(x => x.id == id); + if (!spbu) return; + await fetch(API.spbu, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + action: 'update', id, + namaspbu: spbu.namaspbu, nomorwa: spbu.nomorwa, statusbuka: spbu.statusbuka, + koordinat: { type: 'Point', coordinates: [lng, lat] } + }) + }); +} + +// =============================== +// EDIT / HAPUS JALAN +// =============================== +function editJalan(id) { + const j = allJalan.find(x => x.id == id); + if (!j) return showToast('Data jalan tidak ditemukan', 'er'); + document.getElementById('e-jalan-id').value = j.id; + document.getElementById('e-j-nama').value = j.nama || ''; + document.getElementById('e-j-status').value = j.status || 'kabupaten'; + document.getElementById('e-j-panjang').value = j.panjang_meter || ''; + openModal('m-edit-jalan'); +} + +async function hapusJalan(id) { + const ok = await showConfirm('Hapus jalan ini? Tindakan tidak dapat dibatalkan.', 'Hapus Jalan'); + if (!ok) return; + try { + const res = await fetch(API.jalan, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action: 'delete', id }) + }); + const j = await res.json(); + if (j.status === 'success') { loadAllData(); showToast('Jalan berhasil dihapus'); } + else showToast(j.message || 'Gagal hapus', 'er'); + } catch (e) { showToast('Gagal: ' + e.message, 'er'); } +} + +async function updateJalan() { + const id = parseInt(document.getElementById('e-jalan-id').value || 0); + const nama = document.getElementById('e-j-nama').value; + const status = document.getElementById('e-j-status').value; + const panjang_meter = parseFloat(document.getElementById('e-j-panjang').value); + try { + const res = await fetch(API.jalan, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action: 'update', id, nama, status, panjang_meter }) + }); + const j = await res.json(); + if (j.status === 'success') { closeModal('m-edit-jalan'); loadAllData(); showToast('Jalan diperbarui'); } + else showToast(j.message || 'Gagal update', 'er'); + } catch (e) { showToast('Gagal: ' + e.message, 'er'); } +} + +// =============================== +// EDIT GEOMETRI JALAN +// =============================== +function startEditGeomJalan(id) { + map.closePopup(); + stopEditGeom(); + const j = allJalan.find(x => x.id == id); + if (!j) return; + const geom = JSON.parse(j.geom); + let coords = geom.coordinates.map(c => [c[1], c[0]]); + const geomLayer = L.polyline(coords, { color: '#f97316', weight: 4, dashArray: '8,5', opacity: 1 }).addTo(map); + const vms = []; + const updatePolyline = () => { + const pts = vms.map(vm => vm.getLatLng()); + geomLayer.setLatLngs(pts); + const turfCoords = pts.map(p => [p.lng, p.lat]); + if (turfCoords.length >= 2) { + const panjang = turf.length(turf.lineString(turfCoords), { units: 'meters' }); + document.getElementById('geom-edit-info').textContent = `Panjang: ${panjang.toFixed(2)} m`; + } + }; + coords.forEach(c => { + const vm = L.marker(c, { draggable: true, icon: vertexIcon() }).addTo(map); + vm.on('drag', updatePolyline); + vms.push(vm); + }); + editingFeature = { type: 'jalan', id, geomLayer, vms }; + showEditGeomToolbar('Drag titik untuk edit geometri jalan'); +} + +// =============================== +// EDIT / HAPUS PARSIL +// =============================== +function editParsil(id) { + const p = allParsil.find(x => x.id == id); + if (!p) return showToast('Data parsil tidak ditemukan', 'er'); + document.getElementById('e-parsil-id').value = p.id; + document.getElementById('e-p-nama').value = p.nama || ''; + document.getElementById('e-p-status').value = p.status_kepemilikan || 'SHM'; + document.getElementById('e-p-luas').value = p.luas_m2 || ''; + openModal('m-edit-parsil'); +} + +async function hapusParsil(id) { + const ok = await showConfirm('Hapus parsil ini? Tindakan tidak dapat dibatalkan.', 'Hapus Parsil'); + if (!ok) return; + try { + const res = await fetch(API.parsil, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action: 'delete', id }) + }); + const j = await res.json(); + if (j.status === 'success') { loadAllData(); showToast('Parsil berhasil dihapus'); } + else showToast(j.message || 'Gagal hapus', 'er'); + } catch (e) { showToast('Gagal: ' + e.message, 'er'); } +} + +async function updateParsil() { + const id = parseInt(document.getElementById('e-parsil-id').value || 0); + const nama = document.getElementById('e-p-nama').value; + const status_kepemilikan = document.getElementById('e-p-status').value; + const luas_m2 = parseFloat(document.getElementById('e-p-luas').value); + try { + const res = await fetch(API.parsil, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action: 'update', id, nama, status_kepemilikan, luas_m2 }) + }); + const j = await res.json(); + if (j.status === 'success') { closeModal('m-edit-parsil'); loadAllData(); showToast('Parsil diperbarui'); } + else showToast(j.message || 'Gagal update', 'er'); + } catch (e) { showToast('Gagal: ' + e.message, 'er'); } +} + +// =============================== +// EDIT GEOMETRI PARSIL +// =============================== +function startEditGeomParsil(id) { + map.closePopup(); + stopEditGeom(); + const p = allParsil.find(x => x.id == id); + if (!p) return; + const geom = JSON.parse(p.geom); + let coords = geom.coordinates[0].slice(0, -1).map(c => [c[1], c[0]]); + const geomLayer = L.polygon(coords, { color: '#8b5cf6', fillColor: '#ede9fe', weight: 2.5, dashArray: '8,5', fillOpacity: 0.3 }).addTo(map); + const vms = []; + const updatePolygon = () => { + const pts = vms.map(vm => vm.getLatLng()); + geomLayer.setLatLngs(pts); + const turfCoords = [...pts.map(p => [p.lng, p.lat])]; + turfCoords.push(turfCoords[0]); + if (turfCoords.length >= 4) { + const luas = turf.area(turf.polygon([turfCoords])); + document.getElementById('geom-edit-info').textContent = `Luas: ${luas.toFixed(2)} m²`; + } + }; + coords.forEach(c => { + const vm = L.marker(c, { draggable: true, icon: vertexIcon() }).addTo(map); + vm.on('drag', updatePolygon); + vms.push(vm); + }); + editingFeature = { type: 'parsil', id, geomLayer, vms }; + showEditGeomToolbar('Drag titik untuk edit geometri parsil'); +} + +function vertexIcon() { + return L.divIcon({ + html: `
`, + className: '', iconSize: [14, 14], iconAnchor: [7, 7] + }); +} + +function showEditGeomToolbar(hint) { + const tb = document.getElementById('geom-edit-toolbar'); + document.getElementById('geom-edit-hint').textContent = hint; + document.getElementById('geom-edit-info').textContent = ''; + tb.classList.add('visible'); +} + +function stopEditGeom() { + if (!editingFeature) return; + if (editingFeature.geomLayer) map.removeLayer(editingFeature.geomLayer); + editingFeature.vms.forEach(vm => { try { map.removeLayer(vm); } catch (e) {} }); + editingFeature = null; + document.getElementById('geom-edit-toolbar').classList.remove('visible'); +} + +async function saveEditGeom() { + if (!editingFeature) return; + const { type, id, vms } = editingFeature; + const pts = vms.map(vm => vm.getLatLng()); + try { + if (type === 'jalan') { + const coords = pts.map(p => [p.lng, p.lat]); + const panjang = turf.length(turf.lineString(coords), { units: 'meters' }); + const j = allJalan.find(x => x.id == id); + const res = await fetch(API.jalan, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action: 'update', id, nama: j.nama, status: j.status, panjang_meter: panjang, geom: { type: 'LineString', coordinates: coords } }) + }); + const data = await res.json(); + if (data.status === 'success') { showToast('Geometri jalan diperbarui'); stopEditGeom(); loadAllData(); } + else showToast(data.message || 'Gagal simpan', 'er'); + } else if (type === 'parsil') { + const coords = pts.map(p => [p.lng, p.lat]); + coords.push(coords[0]); + const luas = turf.area(turf.polygon([coords])); + const p = allParsil.find(x => x.id == id); + const res = await fetch(API.parsil, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action: 'update', id, nama: p.nama, status_kepemilikan: p.status_kepemilikan, luas_m2: luas, geom: { type: 'Polygon', coordinates: [coords] } }) + }); + const data = await res.json(); + if (data.status === 'success') { showToast('Geometri parsil diperbarui'); stopEditGeom(); loadAllData(); } + else showToast(data.message || 'Gagal simpan', 'er'); + } + } catch (e) { showToast('Gagal: ' + e.message, 'er'); } +} + +// =============================== +// DRAWING +// =============================== +function onMapClick(e) { + if (isDraggingMarker) return; + if (!currentGeom) return; + if (currentGeom === 'point') { + document.getElementById('spbu-lat').value = e.latlng.lat; + document.getElementById('spbu-lng').value = e.latlng.lng; + openModal('m-add-spbu'); + return; + } + drawPoints.push({ lat: e.latlng.lat, lng: e.latlng.lng }); + undoStack.push({ lat: e.latlng.lat, lng: e.latlng.lng }); + redoStack = []; + drawTemp(); +} + +function drawTemp() { + if (tempLayer) { map.removeLayer(tempLayer); tempLayer = null; } + vertexMarkers.forEach(m => { try { map.removeLayer(m); } catch (e) {} }); + vertexMarkers = []; + if (!drawPoints.length) { updateCounter(); return; } + const latlngs = drawPoints.map(p => [p.lat, p.lng]); + if (currentGeom === 'polyline' && drawPoints.length >= 2) + tempLayer = L.polyline(latlngs, { color: COLORS.jalanProvinsi, weight: 3, dashArray: '7,5', opacity: .85 }).addTo(map); + if (currentGeom === 'polygon' && drawPoints.length >= 3) + tempLayer = L.polygon(latlngs, { color: COLORS.SHM.stroke, weight: 2.5, dashArray: '7,5', fillColor: COLORS.SHM.fill, fillOpacity: .25 }).addTo(map); + drawPoints.forEach(p => { + const vm = L.circleMarker([p.lat, p.lng], { + radius: 6, color: '#fff', weight: 2.5, + fillColor: currentGeom === 'polygon' ? COLORS.SHM.stroke : COLORS.jalanProvinsi, fillOpacity: 1 + }).addTo(map); + vertexMarkers.push(vm); + }); + updateCounter(); +} + +function undoDraw() { if (!drawPoints.length) return; redoStack.push(drawPoints.pop()); drawTemp(); } +function redoDraw() { if (!redoStack.length) return; drawPoints.push(redoStack.pop()); drawTemp(); } + +function finishDrawing() { + if (currentGeom === 'polyline') { + if (drawPoints.length < 2) { showToast('Minimal 2 titik untuk membuat jalan!', 'er'); return; } + finishJalanDrawing(); + } else if (currentGeom === 'polygon') { + if (drawPoints.length < 3) { showToast('Minimal 3 titik untuk membuat parsil!', 'er'); return; } + finishParsilDrawing(); + } +} + +function finishJalanDrawing() { + savedDrawPoints = drawPoints.map(p => ({ lat: p.lat, lng: p.lng })); + const coords = savedDrawPoints.map(p => [p.lng, p.lat]); + const panjang = turf.length(turf.lineString(coords), { units: 'meters' }); + document.getElementById('j-nama').value = ''; + document.getElementById('j-status').value = 'kabupaten'; + document.getElementById('j-panjang').value = panjang.toFixed(2); + openModal('m-add-jalan'); +} + +function finishParsilDrawing() { + savedDrawPoints = drawPoints.map(p => ({ lat: p.lat, lng: p.lng })); + const coords = savedDrawPoints.map(p => [p.lng, p.lat]); + coords.push(coords[0]); + const luas = turf.area(turf.polygon([coords])); + document.getElementById('p-nama').value = ''; + document.getElementById('p-status').value = 'SHM'; + document.getElementById('p-luas').value = luas.toFixed(2); + openModal('m-add-parsil'); +} + +async function saveSpbu() { + const lat = parseFloat(document.getElementById('spbu-lat').value); + const lng = parseFloat(document.getElementById('spbu-lng').value); + const namaspbu = document.getElementById('spbu-nama').value.trim(); + if (!namaspbu) { showToast('Nama SPBU wajib diisi!', 'er'); return; } + if (isNaN(lat) || isNaN(lng)) { showToast('Koordinat tidak valid!', 'er'); return; } + try { + const res = await fetch(API.spbu, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + action: 'create', namaspbu, + nomorwa: document.getElementById('spbu-wa').value, + statusbuka: document.getElementById('spbu-status').value, + koordinat: { type: 'Point', coordinates: [lng, lat] } + }) + }); + const data = await res.json(); + if (data.status !== 'success') { showToast(data.message || 'Gagal simpan', 'er'); return; } + closeModal('m-add-spbu'); deactivateGeom(); loadAllData(); showToast('SPBU berhasil ditambahkan'); + } catch (e) { showToast('Error: ' + e.message, 'er'); } +} + +async function saveJalan() { + const nama = document.getElementById('j-nama').value.trim(); + if (!nama) { showToast('Nama jalan wajib diisi!', 'er'); return; } + if (savedDrawPoints.length < 2) { showToast('Data koordinat hilang, silakan gambar ulang.', 'er'); return; } + try { + const res = await fetch(API.jalan, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + action: 'create', nama, + status: document.getElementById('j-status').value, + panjang_meter: parseFloat(document.getElementById('j-panjang').value), + geom: { type: 'LineString', coordinates: savedDrawPoints.map(p => [p.lng, p.lat]) } + }) + }); + const data = await res.json(); + if (data.status !== 'success') { showToast(data.message || 'Gagal simpan', 'er'); return; } + closeModal('m-add-jalan'); deactivateGeom(); cancelDraw(); loadAllData(); showToast('Jalan berhasil ditambahkan'); + } catch (e) { showToast('Error: ' + e.message, 'er'); } +} + +async function saveParsil() { + const nama = document.getElementById('p-nama').value.trim(); + if (!nama) { showToast('Nama parsil wajib diisi!', 'er'); return; } + if (savedDrawPoints.length < 3) { showToast('Data koordinat hilang, silakan gambar ulang.', 'er'); return; } + const coords = savedDrawPoints.map(p => [p.lng, p.lat]); + coords.push(coords[0]); + try { + const res = await fetch(API.parsil, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + action: 'create', nama, + status_kepemilikan: document.getElementById('p-status').value, + luas_m2: parseFloat(document.getElementById('p-luas').value), + geom: { type: 'Polygon', coordinates: [coords] } + }) + }); + const data = await res.json(); + if (data.status !== 'success') { showToast(data.message || 'Gagal simpan', 'er'); return; } + closeModal('m-add-parsil'); deactivateGeom(); cancelDraw(); loadAllData(); showToast('Parsil berhasil ditambahkan'); + } catch (e) { showToast('Error: ' + e.message, 'er'); } +} + +function cancelDraw() { + drawPoints = []; undoStack = []; redoStack = []; savedDrawPoints = []; + if (tempLayer) { try { map.removeLayer(tempLayer); } catch (e) {} tempLayer = null; } + vertexMarkers.forEach(m => { try { map.removeLayer(m); } catch (e) {} }); + vertexMarkers = []; + updateCounter(); + document.getElementById('draw-toolbar').classList.remove('visible'); +} + +function deactivateGeom() { + currentGeom = null; + document.querySelectorAll('.geom-btn').forEach(b => b.classList.remove('active')); + document.getElementById('geom-hint').classList.remove('on'); + document.getElementById('map').classList.remove('crosshair-cursor'); + document.getElementById('draw-toolbar').classList.remove('visible'); +} + +function openModal(id) { document.getElementById(id).classList.add('open'); } +function closeModal(id) { document.getElementById(id).classList.remove('open'); } + +function updateCounter() { + const n = drawPoints.length; + document.getElementById('dt-counter').innerText = n + ' titik'; + document.getElementById('btn-undo').disabled = n === 0; + document.getElementById('btn-redo').disabled = redoStack.length === 0; +} + +// =============================== +// SEARCH +// =============================== +function setupSearch() { + const input = document.getElementById('search-input'); + const results = document.getElementById('search-results'); + if (!input || !results) return; + + input.addEventListener('input', () => { + const q = input.value.trim().toLowerCase(); + results.innerHTML = ''; + if (!q) { results.classList.remove('open'); return; } + + const hits = []; + allSpbu.forEach(s => { + if ((s.namaspbu || '').toLowerCase().includes(q)) + hits.push({ label: s.namaspbu, sub: 'SPBU · Point', icon: '⛽', + action: () => { flyTo(s.lat, s.lng); input.value = ''; results.classList.remove('open'); } }); + }); + allJalan.forEach(j => { + if ((j.nama || '').toLowerCase().includes(q)) + hits.push({ label: j.nama, sub: `Jalan ${j.status || ''}`, icon: '🛣️', + action: () => { + try { const geom = JSON.parse(j.geom); const mid = geom.coordinates[Math.floor(geom.coordinates.length/2)]; map.setView([mid[1], mid[0]], 16); } catch (e) {} + input.value = ''; results.classList.remove('open'); + } }); + }); + allParsil.forEach(p => { + if ((p.nama || '').toLowerCase().includes(q)) + hits.push({ label: p.nama, sub: `Parsil · ${p.status_kepemilikan || 'SHM'}`, icon: '🗺️', + action: () => { + try { const geom = JSON.parse(p.geom); const coords = geom.coordinates[0]; const cx = coords.reduce((a,c)=>a+c[0],0)/coords.length; const cy = coords.reduce((a,c)=>a+c[1],0)/coords.length; map.setView([cy,cx],17); } catch (e) {} + input.value = ''; results.classList.remove('open'); + } }); + }); + + if (!hits.length) { + results.innerHTML = '
Tidak ada hasil
'; + } else { + results.innerHTML = hits.slice(0, 8).map((h, i) => + `
+ ${h.icon} +
+
${h.label}
+
${h.sub}
+
+
` + ).join(''); + results.querySelectorAll('.sr-item').forEach((el, i) => { + el.addEventListener('click', () => hits[i].action()); + }); + } + results.classList.add('open'); + }); + document.addEventListener('click', e => { + if (!e.target.closest('#search-wrap')) results.classList.remove('open'); + }); +} + +// =============================== +// INIT +// =============================== +document.addEventListener('DOMContentLoaded', () => { + initMap(); + loadAllData(); + setupSearch(); + + document.getElementById('btn-finish').onclick = finishDrawing; + document.getElementById('btn-undo').onclick = undoDraw; + document.getElementById('btn-redo').onclick = redoDraw; + document.getElementById('btn-cancel-draw').onclick = () => { cancelDraw(); deactivateGeom(); }; + document.getElementById('btn-geom-save').onclick = saveEditGeom; + document.getElementById('btn-geom-cancel').onclick = stopEditGeom; + + // Geom buttons + document.querySelectorAll('.geom-btn').forEach(btn => { + btn.addEventListener('click', () => { + const geom = btn.dataset.geom; + if (currentGeom === geom) { cancelDraw(); deactivateGeom(); return; } + cancelDraw(); + currentGeom = geom; + document.querySelectorAll('.geom-btn').forEach(b => b.classList.remove('active')); + btn.classList.add('active'); + document.getElementById('map').classList.add('crosshair-cursor'); + document.getElementById('draw-toolbar').classList.toggle('visible', geom === 'polyline' || geom === 'polygon'); + const hint = document.getElementById('geom-hint'); + const hintText = document.getElementById('geom-hint-text'); + hint.classList.add('on'); + if (geom === 'point') hintText.innerText = 'Klik peta untuk tambah SPBU'; + if (geom === 'polyline') hintText.innerText = 'Klik peta untuk menambah titik jalan, klik Selesai jika sudah'; + if (geom === 'polygon') hintText.innerText = 'Klik peta untuk menambah titik parsil, klik Selesai jika sudah'; + }); + }); + + // Layer toggle custom + const ldbtn = document.getElementById('ldbtn'); + const layerMenu = document.getElementById('layer-menu'); + if (ldbtn && layerMenu) { + ldbtn.addEventListener('click', e => { + e.stopPropagation(); + layerMenu.classList.toggle('open'); + const chev = ldbtn.querySelector('.chev'); + if (chev) chev.textContent = layerMenu.classList.contains('open') ? '▲' : '▼'; + }); + document.addEventListener('click', () => { + layerMenu.classList.remove('open'); + const chev = ldbtn.querySelector('.chev'); + if (chev) chev.textContent = '▼'; + }); + + const cbPoint = document.getElementById('lm-point'); + const cbSpbu24 = document.getElementById('lm-spbu24'); + const cbSpbuLtd = document.getElementById('lm-spbuLtd'); + const subSpbu = document.getElementById('lm-sub-spbu'); + + function syncSpbuLayers() { + const spbuOn = cbPoint.checked; + subSpbu.classList.toggle('hidden', !spbuOn); + if (spbuOn && cbSpbu24.checked) spbu24Group.addTo(map); else map.removeLayer(spbu24Group); + if (spbuOn && cbSpbuLtd.checked) spbuLtdGroup.addTo(map); else map.removeLayer(spbuLtdGroup); + } + cbPoint.addEventListener('change', syncSpbuLayers); + cbSpbu24.addEventListener('change', syncSpbuLayers); + cbSpbuLtd.addEventListener('change', syncSpbuLayers); + + document.getElementById('lm-jalan').addEventListener('change', function () { + if (this.checked) { if (layers.jalan) layers.jalan.addTo(map); } + else { if (layers.jalan) map.removeLayer(layers.jalan); } + }); + document.getElementById('lm-parsil').addEventListener('change', function () { + if (this.checked) { if (layers.parsil) layers.parsil.addTo(map); } + else { if (layers.parsil) map.removeLayer(layers.parsil); } + }); + } +}); \ No newline at end of file diff --git a/04/add_dokumentasi.php b/04/add_dokumentasi.php new file mode 100644 index 0000000..0979b08 --- /dev/null +++ b/04/add_dokumentasi.php @@ -0,0 +1,8 @@ +exec("ALTER TABLE laporan_warga ADD COLUMN dokumentasi TEXT NULL DEFAULT NULL AFTER catatan_admin"); + echo "Column dokumentasi added"; +} catch (Exception $e) { + echo "Error: " . $e->getMessage(); +} diff --git a/04/api/anggota_keluarga.php b/04/api/anggota_keluarga.php new file mode 100644 index 0000000..8a48c77 --- /dev/null +++ b/04/api/anggota_keluarga.php @@ -0,0 +1,163 @@ + true, 'data' => [], 'total' => 0]); + break; + } + + $wargaId = (int)($_GET['rumah_warga_id'] ?? 0); + if (!$wargaId) { echo json_encode(['success' => false, 'message' => 'rumah_warga_id diperlukan']); break; } + + $res = $conn->query("SELECT * FROM anggota_keluarga WHERE rumah_warga_id=$wargaId ORDER BY tanggal_lahir ASC"); + $data = []; + $today = new DateTime(); + while ($row = $res->fetch_assoc()) { + // Hitung usia + if ($row['tanggal_lahir']) { + $lahir = new DateTime($row['tanggal_lahir']); + $row['usia'] = (int)$today->diff($lahir)->y; + } else { + $row['usia'] = null; + } + $data[] = $row; + } + echo json_encode(['success' => true, 'data' => $data, 'total' => count($data)]); + break; + + case 'POST': + $input = json_decode(file_get_contents('php://input'), true) ?: $_POST; + $wargaId = (int)($input['rumah_warga_id'] ?? 0); + $nama = $conn->real_escape_string(trim($input['nama'] ?? '')); + $nik = $conn->real_escape_string($input['nik'] ?? ''); + $tl = !empty($input['tanggal_lahir']) ? "'" . $conn->real_escape_string($input['tanggal_lahir']) . "'" : 'NULL'; + $jk = $conn->real_escape_string($input['jenis_kelamin'] ?? 'laki_laki'); + $pend = $conn->real_escape_string($input['pendidikan'] ?? 'sd'); + $pek = $conn->real_escape_string($input['pekerjaan'] ?? ''); + $stsBk = $conn->real_escape_string($input['status_bekerja'] ?? 'tidak_bekerja'); + $kes = $conn->real_escape_string($input['kondisi_kesehatan'] ?? 'sehat'); + $bisa = (int)($input['dapat_bekerja'] ?? 1); + + if (!$wargaId || !$nama) { echo json_encode(['success' => false, 'message' => 'ID warga dan nama wajib diisi']); break; } + + $sql = "INSERT INTO anggota_keluarga (rumah_warga_id,nama,nik,tanggal_lahir,jenis_kelamin,pendidikan,pekerjaan,status_bekerja,kondisi_kesehatan,dapat_bekerja) + VALUES ($wargaId,'$nama','$nik',$tl,'$jk','$pend','$pek','$stsBk','$kes',$bisa)"; + + if ($conn->query($sql)) { + // Re-hitung rekomendasi induk + rehitungRekomendasi($conn, $wargaId); + echo json_encode(['success' => true, 'message' => 'Anggota berhasil ditambahkan', 'id' => $conn->insert_id]); + } else { + echo json_encode(['success' => false, 'message' => 'Gagal: ' . $conn->error]); + } + break; + + case 'PUT': + $input = json_decode(file_get_contents('php://input'), true); + $id = (int)($input['id'] ?? 0); + $nama = $conn->real_escape_string(trim($input['nama'] ?? '')); + $nik = $conn->real_escape_string($input['nik'] ?? ''); + $tl = !empty($input['tanggal_lahir']) ? "'" . $conn->real_escape_string($input['tanggal_lahir']) . "'" : 'NULL'; + $jk = $conn->real_escape_string($input['jenis_kelamin'] ?? 'laki_laki'); + $pend = $conn->real_escape_string($input['pendidikan'] ?? 'sd'); + $pek = $conn->real_escape_string($input['pekerjaan'] ?? ''); + $stsBk = $conn->real_escape_string($input['status_bekerja'] ?? 'tidak_bekerja'); + $kes = $conn->real_escape_string($input['kondisi_kesehatan'] ?? 'sehat'); + $bisa = (int)($input['dapat_bekerja'] ?? 1); + + if (!$id) { echo json_encode(['success' => false, 'message' => 'ID tidak valid']); break; } + + $sql = "UPDATE anggota_keluarga SET + nama='$nama', nik='$nik', tanggal_lahir=$tl, jenis_kelamin='$jk', + pendidikan='$pend', pekerjaan='$pek', status_bekerja='$stsBk', + kondisi_kesehatan='$kes', dapat_bekerja=$bisa + WHERE id=$id"; + + if ($conn->query($sql)) { + // Ambil warga_id lalu re-hitung rekomendasi + $r = $conn->query("SELECT rumah_warga_id FROM anggota_keluarga WHERE id=$id"); + if ($row = $r->fetch_assoc()) rehitungRekomendasi($conn, (int)$row['rumah_warga_id']); + echo json_encode(['success' => true, 'message' => 'Anggota berhasil diperbarui']); + } else { + echo json_encode(['success' => false, 'message' => 'Gagal: ' . $conn->error]); + } + break; + + case 'DELETE': + $id = (int)($_GET['id'] ?? 0); + if (!$id) { $input = json_decode(file_get_contents('php://input'), true); $id = (int)($input['id'] ?? 0); } + + // Ambil warga_id sebelum hapus + $r = $conn->query("SELECT rumah_warga_id FROM anggota_keluarga WHERE id=$id"); + $wargaId = ($row = $r->fetch_assoc()) ? (int)$row['rumah_warga_id'] : 0; + + if ($conn->query("DELETE FROM anggota_keluarga WHERE id=$id")) { + if ($wargaId) rehitungRekomendasi($conn, $wargaId); + echo json_encode(['success' => true, 'message' => 'Anggota berhasil dihapus']); + } else { + echo json_encode(['success' => false, 'message' => 'Gagal: ' . $conn->error]); + } + break; + + default: + http_response_code(405); + echo json_encode(['error' => 'Method not allowed']); +} +$conn->close(); + +// ── Helper: re-hitung & simpan rekomendasi ──────────────────────────────── +function rehitungRekomendasi(mysqli $conn, int $wargaId): void +{ + $res = $conn->query("SELECT penghasilan_bulanan, status_pekerjaan, is_rekomendasi_manual FROM rumah_warga WHERE id=$wargaId"); + if (!($row = $res->fetch_assoc())) return; + if (isset($row['is_rekomendasi_manual']) && (int)$row['is_rekomendasi_manual'] === 1) return; + + $penghasilan = (float)$row['penghasilan_bulanan']; + $stsPekerjaan = $row['status_pekerjaan']; + + $today = new DateTime(); + $resA = $conn->query("SELECT tanggal_lahir, status_bekerja, dapat_bekerja, kondisi_kesehatan FROM anggota_keluarga WHERE rumah_warga_id=$wargaId"); + $anggota = []; + while ($a = $resA->fetch_assoc()) $anggota[] = $a; + + $adaProduktif = false; + $adaProduktifBisaKerja = false; + $semuaProduktifTakBisa = true; + + foreach ($anggota as $a) { + $usia = null; + if ($a['tanggal_lahir']) { + $lahir = new DateTime($a['tanggal_lahir']); + $usia = (int)$today->diff($lahir)->y; + } + if ($usia !== null && $usia >= 18 && $usia <= 55) { + $adaProduktif = true; + $bisaKerja = (int)$a['dapat_bekerja'] === 1 && in_array($a['kondisi_kesehatan'], ['sehat', 'sakit_ringan']); + if ($bisaKerja) $semuaProduktifTakBisa = false; + if ($bisaKerja && $a['status_bekerja'] !== 'bekerja') $adaProduktifBisaKerja = true; + } + } + + $perluRutin = !$adaProduktif || ($adaProduktif && $semuaProduktifTakBisa) || $penghasilan < 500000 || $stsPekerjaan === 'tidak_bekerja'; + $perluPelatihan = $adaProduktifBisaKerja; + + $rek = 'tidak_ada'; + if ($perluRutin && $perluPelatihan) $rek = 'keduanya'; + elseif ($perluRutin) $rek = 'bantuan_rutin'; + elseif ($perluPelatihan) $rek = 'bantuan_pelatihan'; + + $conn->query("UPDATE rumah_warga SET rekomendasi_bantuan='$rek' WHERE id=$wargaId"); +} \ No newline at end of file diff --git a/04/api/auth.php b/04/api/auth.php new file mode 100644 index 0000000..afd128d --- /dev/null +++ b/04/api/auth.php @@ -0,0 +1,216 @@ + true, + 'logged_in' => true, + 'user' => [ + 'id' => $user['id'], + 'username' => $user['username'], + 'role' => $user['role'], + 'rumah_ibadah_id' => $user['rumah_ibadah_id'], + 'no_telp' => $user['no_telp'] ?? '' + ] + ]); + } else { + echo json_encode([ + 'success' => true, + 'logged_in' => false, + 'user' => null, + 'message' => 'Guest Mode: Pengguna belum login.' + ]); + } + } else { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Action GET tidak dikenal.']); + } + break; + + case 'POST': + if ($action === 'login') { + // Proses Login + $body = json_decode(file_get_contents('php://input'), true) ?: $_POST; + $username = trim($body['username'] ?? ''); + $password = trim($body['password'] ?? ''); + + if (!$username || !$password) { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Username dan Password wajib diisi.']); + exit; + } + + // Cari user berdasarkan username + $stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?"); + $stmt->execute([$username]); + $user = $stmt->fetch(); + + if ($user && password_verify($password, $user['password'])) { + // Simpan data penting ke session + $_SESSION['user'] = [ + 'id' => (int)$user['id'], + 'username' => $user['username'], + 'role' => $user['role'], + 'rumah_ibadah_id' => $user['rumah_ibadah_id'] ? (int)$user['rumah_ibadah_id'] : null, + 'no_telp' => $user['no_telp'] ?? '' + ]; + + echo json_encode([ + 'success' => true, + 'message' => 'Login berhasil! Selamat datang, ' . $user['username'], + 'user' => $_SESSION['user'] + ]); + } else { + http_response_code(401); + echo json_encode(['success' => false, 'message' => 'Username atau Password salah.']); + } + } elseif ($action === 'register') { + // Proses Registrasi Mandiri (hanya untuk kontributor & koordinator) + $body = json_decode(file_get_contents('php://input'), true) ?: $_POST; + $username = trim($body['username'] ?? ''); + $email = trim($body['email'] ?? ''); + $password = trim($body['password'] ?? ''); + $noTelp = trim($body['no_telp'] ?? ''); + $role = trim($body['role'] ?? ''); + $rumahIbadahId = isset($body['rumah_ibadah_id']) && $body['rumah_ibadah_id'] !== '' ? (int)$body['rumah_ibadah_id'] : null; + + // Validasi field wajib + if (!$username || !$email || !$password || !$noTelp || !$role) { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Semua field wajib diisi (username, email, password, no_telp, role).']); + exit; + } + + // Validasi panjang & kekuatan password + if (strlen($password) < 6) { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Password minimal 6 karakter.']); + exit; + } + + // Validasi format email + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Format email tidak valid.']); + exit; + } + + // Whitelist role — HANYA kontributor dan koordinator yang boleh mendaftar sendiri + $allowedRoles = ['kontributor', 'koordinator']; + if (!in_array($role, $allowedRoles)) { + http_response_code(403); + echo json_encode(['success' => false, 'message' => 'Peran "' . $role . '" tidak diizinkan untuk registrasi mandiri.']); + exit; + } + + // Koordinator wajib memilih rumah ibadah + if ($role === 'koordinator' && !$rumahIbadahId) { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Koordinator Rumah Ibadah wajib memilih rumah ibadah yang dikelola.']); + exit; + } + + // Kontributor tidak boleh terikat ke rumah ibadah + if ($role === 'kontributor') { + $rumahIbadahId = null; + } + + // Cek duplikasi username + $stmtCheck = $pdo->prepare("SELECT id FROM users WHERE username = ? LIMIT 1"); + $stmtCheck->execute([$username]); + if ($stmtCheck->fetch()) { + http_response_code(409); + echo json_encode(['success' => false, 'message' => 'Username "' . $username . '" sudah terdaftar. Gunakan username lain.']); + exit; + } + + // Cek duplikasi email + $stmtCheck2 = $pdo->prepare("SELECT id FROM users WHERE email = ? LIMIT 1"); + $stmtCheck2->execute([$email]); + if ($stmtCheck2->fetch()) { + http_response_code(409); + echo json_encode(['success' => false, 'message' => 'Email "' . $email . '" sudah terdaftar. Gunakan email lain.']); + exit; + } + + // Validasi rumah ibadah ada di database (untuk koordinator) + if ($role === 'koordinator' && $rumahIbadahId) { + $stmtIbadah = $pdo->prepare("SELECT id FROM rumah_ibadah WHERE id = ? LIMIT 1"); + $stmtIbadah->execute([$rumahIbadahId]); + if (!$stmtIbadah->fetch()) { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Rumah ibadah yang dipilih tidak valid atau tidak ditemukan.']); + exit; + } + } + + // Hash password dengan bcrypt + $hashedPassword = password_hash($password, PASSWORD_BCRYPT); + + // Simpan user baru ke database + $stmtInsert = $pdo->prepare("INSERT INTO users (username, email, password, no_telp, role, rumah_ibadah_id) VALUES (?, ?, ?, ?, ?, ?)"); + $stmtInsert->execute([$username, $email, $hashedPassword, $noTelp, $role, $rumahIbadahId]); + + $roleLabel = $role === 'koordinator' ? 'Koordinator Rumah Ibadah' : 'Kontributor'; + echo json_encode([ + 'success' => true, + 'message' => 'Registrasi berhasil! Selamat bergabung sebagai ' . $roleLabel . '. Silakan login dengan akun Anda.', + 'username' => $username + ]); + + } elseif ($action === 'logout') { + // Proses Logout + if (session_status() === PHP_SESSION_NONE) { + session_start(); + } + + // Hapus semua data session dan hancurkan + $_SESSION = []; + if (ini_get("session.use_cookies")) { + $params = session_get_cookie_params(); + setcookie(session_name(), '', time() - 42000, + $params["path"], $params["domain"], + $params["secure"], $params["httponly"] + ); + } + session_destroy(); + + echo json_encode([ + 'success' => true, + 'message' => 'Logout berhasil. Anda sekarang masuk sebagai Guest.' + ]); + } else { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Action POST tidak dikenal.']); + } + break; + + default: + http_response_code(405); + echo json_encode(['error' => 'Method not allowed']); + } +} catch (Exception $e) { + http_response_code(500); + echo json_encode(['success' => false, 'message' => 'Terjadi kesalahan sistem: ' . $e->getMessage()]); +} diff --git a/04/api/kebutuhan.php b/04/api/kebutuhan.php new file mode 100644 index 0000000..01f1bed --- /dev/null +++ b/04/api/kebutuhan.php @@ -0,0 +1,177 @@ +prepare( + "SELECT k.*, ri.nama AS nama_rumah_ibadah, ri.alamat, ri.latitude, ri.longitude, + COALESCE(SUM(CASE WHEN ko.status = 'confirmed' THEN ko.jumlah ELSE 0 END), 0) AS jumlah_terkumpul + FROM kebutuhan k + JOIN rumah_ibadah ri ON k.rumah_ibadah_id = ri.id + LEFT JOIN kontribusi ko ON k.id = ko.kebutuhan_id + WHERE k.id = ? + GROUP BY k.id" + ); + $stmt->execute([$id]); + $row = $stmt->fetch(); + + if ($row) { + $row['sisa_kebutuhan'] = max(0, (float)$row['jumlah_dibutuhkan'] - (float)$row['jumlah_terkumpul']); + echo json_encode(['success' => true, 'data' => $row, 'viewer_role' => $currentUser ? $currentUser['role'] : 'guest']); + } else { + echo json_encode(['success' => false, 'message' => 'Kebutuhan tidak ditemukan']); + } + } else { + $sql = "SELECT k.*, ri.nama AS nama_rumah_ibadah, ri.alamat, ri.latitude, ri.longitude, + COALESCE(SUM(CASE WHEN ko.status = 'confirmed' THEN ko.jumlah ELSE 0 END), 0) AS jumlah_terkumpul + FROM kebutuhan k + JOIN rumah_ibadah ri ON k.rumah_ibadah_id = ri.id + LEFT JOIN kontribusi ko ON k.id = ko.kebutuhan_id + GROUP BY k.id + ORDER BY k.created_at DESC"; + + $rows = $pdo->query($sql)->fetchAll(); + foreach ($rows as &$r) { + $r['sisa_kebutuhan'] = max(0, (float)$r['jumlah_dibutuhkan'] - (float)$r['jumlah_terkumpul']); + } + + echo json_encode(['success' => true, 'data' => $rows, 'viewer_role' => $currentUser ? $currentUser['role'] : 'guest']); + } + break; + + case 'POST': + $currentUser = requireAuth(['admin', 'koordinator']); + $body = json_decode(file_get_contents('php://input'), true) ?: $_POST; + + $rumahIbadahId = (int)($body['rumah_ibadah_id'] ?? 0); + $jenisBantuan = trim($body['jenis_bantuan'] ?? ''); + $kategori = trim($body['kategori'] ?? 'barang'); + $satuan = trim($body['satuan'] ?? 'unit'); + $jumlahReq = (float)($body['jumlah_dibutuhkan'] ?? 0); + + if ($currentUser['role'] === 'koordinator' && $currentUser['rumah_ibadah_id'] != $rumahIbadahId) { + http_response_code(403); + echo json_encode(['success' => false, 'message' => 'Koordinator hanya dapat mengelola kebutuhan rumah ibadah mereka sendiri.']); + exit; + } + + $allowedSatuan = ['unit','orang','jam','shift','nominal']; + if (!in_array($satuan, $allowedSatuan)) $satuan = 'unit'; + + if (!$rumahIbadahId || !$jenisBantuan || $jumlahReq <= 0) { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Parameter input tidak valid atau jumlah harus lebih besar dari 0']); + exit; + } + + try { + $stmt = $pdo->prepare("INSERT INTO kebutuhan (rumah_ibadah_id, jenis_bantuan, kategori, jumlah_dibutuhkan, satuan) VALUES (?, ?, ?, ?, ?)"); + $stmt->execute([$rumahIbadahId, $jenisBantuan, $kategori, $jumlahReq, $satuan]); + } catch (PDOException $e) { + $stmt = $pdo->prepare("INSERT INTO kebutuhan (rumah_ibadah_id, jenis_bantuan, kategori, jumlah_dibutuhkan) VALUES (?, ?, ?, ?)"); + $stmt->execute([$rumahIbadahId, $jenisBantuan, $kategori, $jumlahReq]); + } + + echo json_encode(['success' => true, 'message' => 'Kebutuhan bantuan berhasil ditambahkan', 'id' => $pdo->lastInsertId()]); + break; + + case 'PUT': + $currentUser = requireAuth(['admin', 'koordinator']); + $body = json_decode(file_get_contents('php://input'), true) ?: $_POST; + $id = (int)($body['id'] ?? 0); + + $stmt = $pdo->prepare("SELECT rumah_ibadah_id FROM kebutuhan WHERE id = ?"); + $stmt->execute([$id]); + $kebutuhan = $stmt->fetch(); + + if (!$kebutuhan) { + http_response_code(404); + echo json_encode(['success' => false, 'message' => 'Kebutuhan tidak ditemukan']); + exit; + } + + if ($currentUser['role'] === 'koordinator' && $currentUser['rumah_ibadah_id'] != $kebutuhan['rumah_ibadah_id']) { + http_response_code(403); + echo json_encode(['success' => false, 'message' => 'Koordinator tidak berhak merubah kebutuhan rumah ibadah lain.']); + exit; + } + + $satuanIn = trim($body['satuan'] ?? 'unit'); + $allowedSatuan = ['unit','orang','jam','shift','nominal']; + if (!in_array($satuanIn, $allowedSatuan)) $satuanIn = 'unit'; + + try { + $stmt = $pdo->prepare("UPDATE kebutuhan SET jenis_bantuan = ?, kategori = ?, jumlah_dibutuhkan = ?, satuan = ? WHERE id = ?"); + $stmt->execute([ + trim($body['jenis_bantuan']), + trim($body['kategori']), + (float)$body['jumlah_dibutuhkan'], + $satuanIn, + $id + ]); + } catch (PDOException $e) { + $stmt = $pdo->prepare("UPDATE kebutuhan SET jenis_bantuan = ?, kategori = ?, jumlah_dibutuhkan = ? WHERE id = ?"); + $stmt->execute([ + trim($body['jenis_bantuan']), + trim($body['kategori']), + (float)$body['jumlah_dibutuhkan'], + $id + ]); + } + + echo json_encode(['success' => true, 'message' => 'Kebutuhan berhasil diperbarui']); + break; + + case 'DELETE': + $currentUser = requireAuth(['admin', 'koordinator']); + $id = (int)($_GET['id'] ?? 0); + + $stmt = $pdo->prepare("SELECT rumah_ibadah_id FROM kebutuhan WHERE id = ?"); + $stmt->execute([$id]); + $kebutuhan = $stmt->fetch(); + + if (!$kebutuhan) { + http_response_code(404); + echo json_encode(['success' => false, 'message' => 'Kebutuhan tidak ditemukan']); + exit; + } + + if ($currentUser['role'] === 'koordinator' && $currentUser['rumah_ibadah_id'] != $kebutuhan['rumah_ibadah_id']) { + http_response_code(403); + echo json_encode(['success' => false, 'message' => 'Koordinator tidak berhak menghapus kebutuhan rumah ibadah lain.']); + exit; + } + + $stmt = $pdo->prepare("DELETE FROM kebutuhan WHERE id = ?"); + $stmt->execute([$id]); + + echo json_encode(['success' => true, 'message' => 'Kebutuhan berhasil dihapus']); + break; + + default: + http_response_code(405); + echo json_encode(['error' => 'Method not allowed']); + } +} catch (Exception $e) { + http_response_code(500); + echo json_encode(['success' => false, 'message' => $e->getMessage()]); +} diff --git a/04/api/kemandirian.php b/04/api/kemandirian.php new file mode 100644 index 0000000..62c7e78 --- /dev/null +++ b/04/api/kemandirian.php @@ -0,0 +1,445 @@ +prepare("SELECT nilai, tipe FROM konfigurasi_sistem WHERE kunci = ?"); + $stmt->execute([$kunci]); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (!$row) return $default; + return match($row['tipe']) { + 'integer' => (int)$row['nilai'], + 'decimal' => (float)$row['nilai'], + 'boolean' => filter_var($row['nilai'], FILTER_VALIDATE_BOOLEAN), + default => $row['nilai'], + }; +} + +function hitungUsia(?string $tanggalLahir): ?int { + if (!$tanggalLahir) return null; + $lahir = new DateTime($tanggalLahir); + $today = new DateTime(); + return (int)$today->diff($lahir)->y; +} + +/* ───────────────────────────────────────────── + CORE: Hitung Skor Kemandirian + + Komponen: + A) Skor Penghasilan (bobot configurable, default 40%) + - 0% : penghasilan = 0 + - 50% : penghasilan = 50% threshold + - 100%: penghasilan >= threshold UMR + + B) Skor Status Kerja (default 35%) + - Dihitung dari anggota usia produktif (18-55) + - Poin per anggota: bekerja_tetap=100, tidak_tetap=60, tidak_bekerja=0, pelajar=30 + - Bonus: sudah bekerja tetap setelah ikut pelatihan + + C) Skor Tanggungan (default 25%) + - Tanggungan sedikit (1-2) = skor tinggi + - Tanggungan banyak (> 6) = skor rendah + + Status kemandirian: + - 0-20 : sangat_bergantung + - 21-40 : bergantung + - 41-60 : berkembang + - 61-79 : berpotensi_mandiri + - 80+ : mandiri (siap exit) +───────────────────────────────────────────── */ +function hitungSkorKemandirian( + PDO $pdo, + array $warga, + array $anggotaList, + float $thresholdPenghasilan, + int $bobotPenghasilan, + int $bobotStatusKerja, + int $bobotTanggungan +): array { + $penghasilan = (float)$warga['penghasilan_bulanan']; + $tanggungan = (int)$warga['jumlah_tanggungan']; + $statusKerja = $warga['status_pekerjaan']; + + /* ── A: Skor Penghasilan ── */ + $skorPenghasilan = 0; + if ($thresholdPenghasilan > 0) { + $skorPenghasilan = (int)min(100, round($penghasilan / $thresholdPenghasilan * 100)); + } + + /* ── B: Skor Status Kerja ── */ + $bobotStatusKerjaMap = [ + 'tetap' => 100, + 'tidak_tetap' => 60, + 'pensiun' => 50, + 'tidak_bekerja'=> 0, + ]; + + // Skor kepala keluarga + $poinKK = $bobotStatusKerjaMap[$statusKerja] ?? 0; + $anggotaProd = array_filter($anggotaList, function($a) { + $usia = hitungUsia($a['tanggal_lahir']); + return $usia !== null && $usia >= 18 && $usia <= 55; + }); + + if (count($anggotaProd) > 0) { + $totalPoin = $poinKK; + foreach ($anggotaProd as $a) { + $stsBekMap = [ + 'bekerja' => 100, + 'pensiunan' => 50, + 'pelajar' => 30, + 'tidak_bekerja' => 0, + ]; + $poin = $stsBekMap[$a['status_bekerja']] ?? 0; + // Diskon: tidak bisa bekerja / disabilitas + if (!$a['dapat_bekerja'] || in_array($a['kondisi_kesehatan'], ['sakit_berat','disabilitas'])) { + $poin = (int)($poin * 0.3); + } + $totalPoin += $poin; + } + $jumlahDitimbang = count($anggotaProd) + 1; // +1 untuk KK + $skorStatusKerja = (int)round($totalPoin / ($jumlahDitimbang * 100) * 100); + } else { + // Tidak ada anggota produktif, skor bergantung murni dari KK + $skorStatusKerja = $poinKK; + } + + /* ── C: Skor Tanggungan ── */ + // Makin sedikit tanggungan relatif terhadap yang bekerja = makin mandiri + $anggotaBekerja = count(array_filter($anggotaList, fn($a) => $a['status_bekerja'] === 'bekerja')); + $pencariNafkah = $anggotaBekerja + ($statusKerja !== 'tidak_bekerja' ? 1 : 0); + + if ($tanggungan <= 0) { + $skorTanggungan = 100; + } elseif ($pencariNafkah >= $tanggungan) { + $skorTanggungan = 100; // semua tanggungan punya pemasukan + } else { + $rasio = $pencariNafkah / $tanggungan; + $skorTanggungan = (int)min(100, round($rasio * 100)); + } + + /* ── Skor Total (weighted) ── */ + $totalBobot = $bobotPenghasilan + $bobotStatusKerja + $bobotTanggungan; + if ($totalBobot <= 0) $totalBobot = 100; + + $skorTotal = (int)round( + ($skorPenghasilan * $bobotPenghasilan + + $skorStatusKerja * $bobotStatusKerja + + $skorTanggungan * $bobotTanggungan) / $totalBobot + ); + + /* ── Status ── */ + $status = match(true) { + $skorTotal >= 80 => 'mandiri', + $skorTotal >= 61 => 'berpotensi_mandiri', + $skorTotal >= 41 => 'berkembang', + $skorTotal >= 21 => 'bergantung', + default => 'sangat_bergantung', + }; + + /* ── Estimasi waktu menuju mandiri ── */ + $estimasiKenaikan = 5; // asumsi naik 5 poin/bulan dengan pelatihan + $bulanMenujuMandiri = $skorTotal >= 80 ? 0 : (int)ceil((80 - $skorTotal) / $estimasiKenaikan); + + /* ── Rekomendasi aksi ── */ + $rekomendasi = []; + if ($skorPenghasilan < 50) $rekomendasi[] = 'Tingkatkan penghasilan melalui pelatihan vokasional'; + if ($skorStatusKerja < 50) $rekomendasi[] = 'Dukung anggota produktif untuk mendapat pekerjaan tetap'; + if ($skorTanggungan < 50) $rekomendasi[] = 'Pertimbangkan program KB/pemberdayaan tanggungan'; + if (count($rekomendasi) === 0) $rekomendasi[] = 'Pertahankan perkembangan yang ada, pantau secara berkala'; + + return [ + 'skor_total' => $skorTotal, + 'skor_penghasilan' => $skorPenghasilan, + 'skor_status_kerja' => $skorStatusKerja, + 'skor_tanggungan' => $skorTanggungan, + 'status' => $status, + 'estimasi_bulan_mandiri' => $bulanMenujuMandiri, + 'rekomendasi' => $rekomendasi, + 'detail' => [ + 'penghasilan' => $penghasilan, + 'threshold' => $thresholdPenghasilan, + 'jumlah_tanggungan' => $tanggungan, + 'pencari_nafkah' => $pencariNafkah, + 'anggota_produktif' => count($anggotaProd), + ], + ]; +} + +/* ───────────────────────────────────────────── + UPDATE skor kemandirian batch + simpan riwayat +───────────────────────────────────────────── */ +function updateKemandirianBatch(PDO $pdo, bool $simpanRiwayat = false): int { + $thres = getKonfig($pdo, 'threshold_penghasilan_mandiri', 3500000); + $bPeng = getKonfig($pdo, 'bobot_penghasilan', 40); + $bKerja = getKonfig($pdo, 'bobot_status_kerja', 35); + $bTangg = getKonfig($pdo, 'bobot_tanggungan', 25); + + $wargaList = $pdo->query(" + SELECT id, penghasilan_bulanan, jumlah_tanggungan, status_pekerjaan, + skor_kemandirian as skor_lama + FROM rumah_warga + ")->fetchAll(PDO::FETCH_ASSOC); + + $stmtAnggota = $pdo->prepare(" + SELECT nama, tanggal_lahir, status_bekerja, kondisi_kesehatan, dapat_bekerja + FROM anggota_keluarga WHERE rumah_warga_id = ? + "); + $stmtUpdate = $pdo->prepare(" + UPDATE rumah_warga SET skor_kemandirian = ?, status_kemandirian = ? WHERE id = ? + "); + $stmtRiwayat = $pdo->prepare(" + INSERT INTO riwayat_kemandirian + (rumah_warga_id, tanggal, skor, status, penghasilan_saat_ini) + VALUES (?, CURDATE(), ?, ?, ?) + "); + + $updated = 0; + foreach ($wargaList as $w) { + $stmtAnggota->execute([(int)$w['id']]); + $anggota = $stmtAnggota->fetchAll(PDO::FETCH_ASSOC); + $calc = hitungSkorKemandirian($pdo, $w, $anggota, $thres, $bPeng, $bKerja, $bTangg); + + $stmtUpdate->execute([$calc['skor_total'], $calc['status'], (int)$w['id']]); + + if ($simpanRiwayat && $calc['skor_total'] !== (int)$w['skor_lama']) { + $stmtRiwayat->execute([ + (int)$w['id'], + $calc['skor_total'], + $calc['status'], + (float)$w['penghasilan_bulanan'], + ]); + } + $updated++; + } + return $updated; +} + +/* ───────────────────────────────────────────── + ROUTING +───────────────────────────────────────────── */ +try { + // Hanya Admin dan Petugas Lapangan yang memiliki akses ke modul kemandirian warga + requireAuth(['admin', 'petugas']); + + $method = $_SERVER['REQUEST_METHOD']; + $action = $_GET['action'] ?? 'list'; + + if ($method === 'GET') { + + if ($action === 'list') { + // Recalculate dulu + updateKemandirianBatch($pdo, true); + + $statusFilter = $_GET['status'] ?? null; + $minSkor = (int)($_GET['min_skor'] ?? 0); + $limit = (int)($_GET['limit'] ?? 50); + $thresholdSk = getKonfig($pdo, 'threshold_skor_mandiri', 70); + + $where = []; + if ($statusFilter) $where[] = "rw.status_kemandirian = " . $pdo->quote($statusFilter); + if ($minSkor > 0) $where[] = "rw.skor_kemandirian >= $minSkor"; + $whereStr = $where ? "WHERE " . implode(" AND ", $where) : ""; + + $rows = $pdo->query(" + SELECT rw.id, rw.nama_kepala_keluarga, rw.no_kk, + rw.penghasilan_bulanan, rw.jumlah_tanggungan, + rw.status_pekerjaan, rw.kategori_kemiskinan, + rw.skor_kemandirian, rw.status_kemandirian, + rw.catatan_kemandirian, rw.tanggal_exit_plan, + rw.rekomendasi_bantuan, rw.status_bantuan, + rw.kelurahan, rw.kecamatan, + rw.latitude, rw.longitude + FROM rumah_warga rw + $whereStr + ORDER BY rw.skor_kemandirian DESC + LIMIT $limit + ")->fetchAll(PDO::FETCH_ASSOC); + + // Tambah detail skor per row + $thres = getKonfig($pdo, 'threshold_penghasilan_mandiri', 3500000); + $bPeng = getKonfig($pdo, 'bobot_penghasilan', 40); + $bKerja = getKonfig($pdo, 'bobot_status_kerja', 35); + $bTangg = getKonfig($pdo, 'bobot_tanggungan', 25); + $stmtA = $pdo->prepare("SELECT * FROM anggota_keluarga WHERE rumah_warga_id = ?"); + + foreach ($rows as &$r) { + $stmtA->execute([(int)$r['id']]); + $anggota = $stmtA->fetchAll(PDO::FETCH_ASSOC); + $r['detail_skor'] = hitungSkorKemandirian($pdo, $r, $anggota, $thres, $bPeng, $bKerja, $bTangg); + } + + $summary = $pdo->query(" + SELECT status_kemandirian, COUNT(*) as jumlah, ROUND(AVG(skor_kemandirian)) as avg_skor + FROM rumah_warga GROUP BY status_kemandirian + ")->fetchAll(PDO::FETCH_ASSOC); + + $siapExit = $pdo->query(" + SELECT COUNT(*) FROM rumah_warga + WHERE skor_kemandirian >= $thresholdSk + AND status_bantuan != 'sudah_terima' + ")->fetchColumn(); + + echo json_encode([ + 'success' => true, + 'data' => $rows, + 'summary' => $summary, + 'siap_exit' => (int)$siapExit, + ]); + + } elseif ($action === 'detail') { + $wargaId = (int)($_GET['id'] ?? 0); + + $warga = $pdo->prepare("SELECT * FROM rumah_warga WHERE id = ?"); + $warga->execute([$wargaId]); + $w = $warga->fetch(PDO::FETCH_ASSOC); + if (!$w) { echo json_encode(['success' => false, 'message' => 'Warga tidak ditemukan']); exit; } + + $stmtA = $pdo->prepare("SELECT * FROM anggota_keluarga WHERE rumah_warga_id = ?"); + $stmtA->execute([$wargaId]); + $anggota = $stmtA->fetchAll(PDO::FETCH_ASSOC); + + $thres = getKonfig($pdo, 'threshold_penghasilan_mandiri', 3500000); + $bPeng = getKonfig($pdo, 'bobot_penghasilan', 40); + $bKerja = getKonfig($pdo, 'bobot_status_kerja', 35); + $bTangg = getKonfig($pdo, 'bobot_tanggungan', 25); + + $skor = hitungSkorKemandirian($pdo, $w, $anggota, $thres, $bPeng, $bKerja, $bTangg); + + // Riwayat + $riwayat = $pdo->prepare(" + SELECT * FROM riwayat_kemandirian WHERE rumah_warga_id = ? + ORDER BY tanggal DESC LIMIT 12 + "); + $riwayat->execute([$wargaId]); + + echo json_encode([ + 'success' => true, + 'data' => $w, + 'skor' => $skor, + 'anggota' => $anggota, + 'riwayat' => $riwayat->fetchAll(PDO::FETCH_ASSOC), + ]); + + } elseif ($action === 'statistik') { + updateKemandirianBatch($pdo, false); + $thresholdSk = getKonfig($pdo, 'threshold_skor_mandiri', 70); + + echo json_encode([ + 'success' => true, + 'data' => [ + 'distribusi_status' => $pdo->query(" + SELECT status_kemandirian as status, COUNT(*) as jumlah, + ROUND(AVG(skor_kemandirian)) as avg_skor, + ROUND(AVG(penghasilan_bulanan)) as avg_penghasilan + FROM rumah_warga GROUP BY status_kemandirian ORDER BY avg_skor DESC + ")->fetchAll(PDO::FETCH_ASSOC), + 'siap_exit_plan' => (int)$pdo->query(" + SELECT COUNT(*) FROM rumah_warga WHERE skor_kemandirian >= $thresholdSk + ")->fetchColumn(), + 'sudah_punya_exit_plan' => (int)$pdo->query(" + SELECT COUNT(*) FROM rumah_warga WHERE tanggal_exit_plan IS NOT NULL + ")->fetchColumn(), + 'avg_skor_global' => (float)$pdo->query(" + SELECT ROUND(AVG(skor_kemandirian),1) FROM rumah_warga + ")->fetchColumn(), + 'trend_bulan_ini' => $pdo->query(" + SELECT DATE(tanggal) as tgl, ROUND(AVG(skor)) as avg_skor, COUNT(*) as jumlah + FROM riwayat_kemandirian + WHERE tanggal >= DATE_SUB(NOW(), INTERVAL 30 DAY) + GROUP BY DATE(tanggal) ORDER BY tgl + ")->fetchAll(PDO::FETCH_ASSOC), + ], + ]); + } + + } elseif ($method === 'POST') { + // Catat perkembangan/catatan manual dari petugas + $body = json_decode(file_get_contents('php://input'), true); + $wargaId = (int)($body['warga_id'] ?? 0); + + if (!$wargaId) { echo json_encode(['success' => false, 'message' => 'warga_id wajib']); exit; } + + $stmt = $pdo->prepare(" + UPDATE rumah_warga + SET catatan_kemandirian = ?, tanggal_exit_plan = ?, updated_at = NOW() + WHERE id = ? + "); + $stmt->execute([ + $body['catatan'] ?? null, + $body['tanggal_exit_plan'] ?? null, + $wargaId, + ]); + + // Hitung ulang dan simpan ke riwayat + $warga = $pdo->prepare("SELECT * FROM rumah_warga WHERE id = ?"); + $warga->execute([$wargaId]); + $w = $warga->fetch(PDO::FETCH_ASSOC); + + $stmtA = $pdo->prepare("SELECT * FROM anggota_keluarga WHERE rumah_warga_id = ?"); + $stmtA->execute([$wargaId]); + $anggota = $stmtA->fetchAll(PDO::FETCH_ASSOC); + + $thres = getKonfig($pdo, 'threshold_penghasilan_mandiri', 3500000); + $bPeng = getKonfig($pdo, 'bobot_penghasilan', 40); + $bKerja = getKonfig($pdo, 'bobot_status_kerja', 35); + $bTangg = getKonfig($pdo, 'bobot_tanggungan', 25); + $calc = hitungSkorKemandirian($pdo, $w, $anggota, $thres, $bPeng, $bKerja, $bTangg); + + $pdo->prepare(" + INSERT INTO riwayat_kemandirian + (rumah_warga_id, tanggal, skor, status, penghasilan_saat_ini, catatan) + VALUES (?, CURDATE(), ?, ?, ?, ?) + ")->execute([ + $wargaId, $calc['skor_total'], $calc['status'], + (float)$w['penghasilan_bulanan'], $body['catatan'] ?? null + ]); + + echo json_encode([ + 'success' => true, + 'message' => 'Catatan kemandirian berhasil disimpan', + 'skor' => $calc, + ]); + + } elseif ($method === 'PUT') { + // Update status kemandirian manual oleh admin (override sistem) + $body = json_decode(file_get_contents('php://input'), true); + $wargaId = (int)($body['warga_id'] ?? 0); + + $stmt = $pdo->prepare(" + UPDATE rumah_warga + SET status_kemandirian = ?, + catatan_kemandirian = ?, + tanggal_exit_plan = ?, + updated_at = NOW() + WHERE id = ? + "); + $stmt->execute([ + $body['status_kemandirian'] ?? null, + $body['catatan'] ?? null, + $body['tanggal_exit_plan'] ?? null, + $wargaId, + ]); + + echo json_encode(['success' => true, 'message' => 'Status kemandirian diperbarui']); + } + +} catch (Exception $e) { + http_response_code(500); + echo json_encode(['success' => false, 'message' => $e->getMessage()]); +} \ No newline at end of file diff --git a/04/api/kontribusi.php b/04/api/kontribusi.php new file mode 100644 index 0000000..e52ed89 --- /dev/null +++ b/04/api/kontribusi.php @@ -0,0 +1,216 @@ +prepare(" + SELECT ko.id, ko.user_id, ko.kebutuhan_id, ko.status, ko.created_at, ko.updated_at, + ko.jumlah, + ko.jenis_kontribusi AS tipe_kontribusi, + ko.detail_barang_jasa AS catatan, + k.jenis_bantuan, + ri.nama AS nama_rumah_ibadah, + ri.nama AS nama_ibadah + FROM kontribusi ko + JOIN kebutuhan k ON ko.kebutuhan_id = k.id + JOIN rumah_ibadah ri ON k.rumah_ibadah_id = ri.id + WHERE ko.user_id = ? + ORDER BY ko.created_at DESC + "); + $stmt->execute([$currentUser['id']]); + $rows = $stmt->fetchAll(); + } else { + // Admin atau Koordinator melihat kontribusi + $where = ""; + $params = []; + if ($currentUser['role'] === 'koordinator') { + $where = "WHERE k.rumah_ibadah_id = ?"; + $params[] = $currentUser['rumah_ibadah_id']; + } + + $stmt = $pdo->prepare(" + SELECT ko.id, ko.user_id, ko.kebutuhan_id, ko.status, ko.created_at, ko.updated_at, + ko.jumlah, + ko.jenis_kontribusi AS tipe_kontribusi, + ko.detail_barang_jasa AS catatan, + k.jenis_bantuan, + ri.nama AS nama_rumah_ibadah, + ri.nama AS nama_ibadah, + u.username AS nama_kontributor, + u.email AS kontak_kontributor + FROM kontribusi ko + JOIN kebutuhan k ON ko.kebutuhan_id = k.id + JOIN rumah_ibadah ri ON k.rumah_ibadah_id = ri.id + JOIN users u ON ko.user_id = u.id + $where + ORDER BY ko.created_at DESC + "); + $stmt->execute($params); + $rows = $stmt->fetchAll(); + } + + echo json_encode(['success' => true, 'data' => $rows]); + break; + + case 'POST': + // Wajib login sebagai Kontributor untuk berdonasi + $currentUser = requireAuth(['kontributor']); + $body = json_decode(file_get_contents('php://input'), true) ?: $_POST; + + $kebutuhanId = (int)($body['kebutuhan_id'] ?? 0); + $jenisKontribusi = trim($body['jenis_kontribusi'] ?? $body['tipe_kontribusi'] ?? ''); // uang/barang/jasa + $jumlahDonasi = (float)($body['jumlah'] ?? 0); + $detail = trim($body['detail_barang_jasa'] ?? $body['catatan'] ?? ''); + + if (!$kebutuhanId || !$jenisKontribusi || $jumlahDonasi <= 0) { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Input data donasi tidak lengkap/valid.']); + exit; + } + + // ════════════════════════════════════════════════════════════════════════════ + // VALIDASI SISA KEBUTUHAN + // ════════════════════════════════════════════════════════════════════════════ + $stmt = $pdo->prepare(" + SELECT k.jumlah_dibutuhkan, k.kategori, + COALESCE(SUM(CASE WHEN ko.status = 'confirmed' THEN ko.jumlah ELSE 0 END), 0) AS jumlah_terkumpul + FROM kebutuhan k + LEFT JOIN kontribusi ko ON k.id = ko.kebutuhan_id + WHERE k.id = ? + GROUP BY k.id + "); + $stmt->execute([$kebutuhanId]); + $kebutuhan = $stmt->fetch(); + + if (!$kebutuhan) { + http_response_code(404); + echo json_encode(['success' => false, 'message' => 'Data kebutuhan bantuan tidak ditemukan.']); + exit; + } + + // Hitung sisa kebutuhan secara dinamis + $sisaKebutuhan = (float)$kebutuhan['jumlah_dibutuhkan'] - (float)$kebutuhan['jumlah_terkumpul']; + + if ($jumlahDonasi > $sisaKebutuhan) { + http_response_code(400); + echo json_encode([ + 'success' => false, + 'message' => sprintf( + 'Donasi ditolak! Jumlah kontribusi (%.2f) melebihi sisa kebutuhan bantuan saat ini (%.2f).', + $jumlahDonasi, + $sisaKebutuhan + ) + ]); + exit; + } + + // Simpan kontribusi (default status = pending) + $stmt = $pdo->prepare(" + INSERT INTO kontribusi (user_id, kebutuhan_id, jenis_kontribusi, jumlah, detail_barang_jasa, status) + VALUES (?, ?, ?, ?, ?, 'pending') + "); + $stmt->execute([$currentUser['id'], $kebutuhanId, $jenisKontribusi, $jumlahDonasi, $detail]); + + echo json_encode([ + 'success' => true, + 'message' => 'Terima kasih atas kontribusi Anda! Status kontribusi saat ini menunggu verifikasi (pending).', + 'id' => $pdo->lastInsertId() + ]); + break; + + case 'PUT': + // Konfirmasi kontribusi (pending -> confirmed) + // Hanya Admin atau Koordinator terkait + $currentUser = requireAuth(['admin', 'koordinator']); + $body = json_decode(file_get_contents('php://input'), true) ?: $_POST; + $kontribusiId = (int)($body['id'] ?? 0); + $newStatus = trim($body['status'] ?? 'pending'); // pending/confirmed + + if (!$kontribusiId || !in_array($newStatus, ['pending', 'confirmed'])) { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Parameter input status tidak valid.']); + exit; + } + + // Ambil detail kontribusi + $stmt = $pdo->prepare(" + SELECT ko.jumlah, ko.status AS status_sebelumnya, ko.kebutuhan_id, k.rumah_ibadah_id + FROM kontribusi ko + JOIN kebutuhan k ON ko.kebutuhan_id = k.id + WHERE ko.id = ? + "); + $stmt->execute([$kontribusiId]); + $kontribusi = $stmt->fetch(); + + if (!$kontribusi) { + http_response_code(404); + echo json_encode(['success' => false, 'message' => 'Data kontribusi tidak ditemukan.']); + exit; + } + + // Batasan wilayah untuk Koordinator + if ($currentUser['role'] === 'koordinator' && $currentUser['rumah_ibadah_id'] != $kontribusi['rumah_ibadah_id']) { + http_response_code(403); + echo json_encode(['success' => false, 'message' => 'Koordinator hanya berhak mengonfirmasi donasi untuk rumah ibadahnya sendiri.']); + exit; + } + + // Jika status dirubah ke 'confirmed' dari 'pending' + if ($newStatus === 'confirmed' && $kontribusi['status_sebelumnya'] !== 'confirmed') { + $stmt = $pdo->prepare(" + SELECT k.jumlah_dibutuhkan, + COALESCE(SUM(CASE WHEN ko.status = 'confirmed' THEN ko.jumlah ELSE 0 END), 0) AS jumlah_terkumpul + FROM kebutuhan k + LEFT JOIN kontribusi ko ON k.id = ko.kebutuhan_id + WHERE k.id = ? + GROUP BY k.id + "); + $stmt->execute([$kontribusi['kebutuhan_id']]); + $kebutuhan = $stmt->fetch(); + + $sisaKebutuhan = (float)$kebutuhan['jumlah_dibutuhkan'] - (float)$kebutuhan['jumlah_terkumpul']; + + if ($kontribusi['jumlah'] > $sisaKebutuhan) { + http_response_code(400); + echo json_encode([ + 'success' => false, + 'message' => 'Gagal konfirmasi! Total confirmed donasi melebihi kapasitas kebutuhan.' + ]); + exit; + } + } + + $stmt = $pdo->prepare("UPDATE kontribusi SET status = ? WHERE id = ?"); + $stmt->execute([$newStatus, $kontribusiId]); + + echo json_encode(['success' => true, 'message' => 'Status kontribusi berhasil diperbarui ke ' . $newStatus]); + break; + + default: + http_response_code(405); + echo json_encode(['error' => 'Method not allowed']); + } +} catch (Exception $e) { + http_response_code(500); + echo json_encode(['success' => false, 'message' => $e->getMessage()]); +} diff --git a/04/api/laporan.php b/04/api/laporan.php new file mode 100644 index 0000000..4acfff0 --- /dev/null +++ b/04/api/laporan.php @@ -0,0 +1,305 @@ + false, 'message' => 'Forbidden']); + exit; + } + $stmtStats = $pdo->query(" + SELECT status, COUNT(*) as jumlah + FROM laporan_warga + GROUP BY status + "); + $stats = []; + while ($r = $stmtStats->fetch(PDO::FETCH_ASSOC)) { + $stats[$r['status']] = (int)$r['jumlah']; + } + $stmtTotal = $pdo->query("SELECT COUNT(*) FROM laporan_warga"); + echo json_encode(['success' => true, 'stats' => $stats, 'total' => (int)$stmtTotal->fetchColumn()]); + exit; + } + + // Filter berdasarkan role + $where = []; + $params = []; + + // Jika ada action=get_petugas + if ($action === 'get_petugas') { + if (!in_array($role, ['admin', 'petugas'])) { + http_response_code(403); echo json_encode(['success' => false, 'message' => 'Forbidden']); exit; + } + $stmt = $pdo->query("SELECT username FROM users WHERE role = 'petugas' ORDER BY username ASC"); + $petugas = $stmt->fetchAll(PDO::FETCH_COLUMN); + echo json_encode(['success' => true, 'petugas' => $petugas]); + exit; + } + + if ($role === 'kontributor') { + // Kontributor hanya lihat laporan milik sendiri + $where[] = 'lw.pelapor_id = ?'; + $params[] = $userId; + } elseif ($role === 'koordinator') { + // Koordinator lihat laporan dari wilayahnya + laporan milik sendiri + $where[] = '(lw.pelapor_id = ? OR lw.koordinator_ibadah_id = ?)'; + $params[] = $userId; + $params[] = !empty($currentUser['rumah_ibadah_id']) ? (int)$currentUser['rumah_ibadah_id'] : 0; + } elseif ($role === 'petugas') { + // Petugas hanya lihat laporan yang ditugaskan ke mereka + $where[] = 'lw.ditugaskan_ke = ?'; + $params[] = $currentUser['username']; + } + // Admin: lihat semua + + // Filter status + if (!empty($_GET['status'])) { + $where[] = 'lw.status = ?'; + $params[] = $_GET['status']; + } + + // Filter prioritas + if (!empty($_GET['prioritas'])) { + $where[] = 'lw.prioritas = ?'; + $params[] = $_GET['prioritas']; + } + + $whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : ''; + + $sql = " + SELECT + lw.*, + rw.nama_kepala_keluarga, + rw.kelurahan, + rw.kecamatan, + rw.kategori_kemiskinan, + rw.status_bantuan, + u.username AS pelapor_nama, + u.role AS pelapor_role + FROM laporan_warga lw + JOIN rumah_warga rw ON rw.id = lw.rumah_warga_id + JOIN users u ON u.id = lw.pelapor_id + $whereSql + ORDER BY lw.created_at DESC + LIMIT 100 + "; + + $stmt = $pdo->prepare($sql); + $stmt->execute($params); + $data = $stmt->fetchAll(PDO::FETCH_ASSOC); + + // Summary counts for filter badges + $stmtSummary = $pdo->query("SELECT status, COUNT(*) as n FROM laporan_warga GROUP BY status"); + $summary = []; + while ($r = $stmtSummary->fetch(PDO::FETCH_ASSOC)) { + $summary[$r['status']] = (int)$r['n']; + } + + $response = ['success' => true, 'data' => $data, 'summary' => $summary, 'total' => count($data)]; + + // Sertakan list petugas jika user admin + if ($role === 'admin' || $role === 'petugas') { + $stmtP = $pdo->query("SELECT username FROM users WHERE role = 'petugas' ORDER BY username ASC"); + $response['petugas'] = $stmtP->fetchAll(PDO::FETCH_COLUMN); + } + + echo json_encode($response); + break; + + // ── POST: Buat laporan baru ────────────────────────────────────────── + case 'POST': + $currentUser = requireAuth(['koordinator', 'kontributor']); + + $input = json_decode(file_get_contents('php://input'), true) ?: $_POST; + + $rumahWargaId = (int)($input['rumah_warga_id'] ?? 0); + $jenisLaporan = trim($input['jenis_laporan'] ?? ''); + $deskripsi = trim($input['deskripsi'] ?? ''); + + // Penentuan prioritas otomatis berdasarkan jenis laporan + $prioritasMap = [ + 'warga_meninggal' => 'tinggi', + 'warga_pindah' => 'tinggi', + 'warga_mampu' => 'tinggi', + 'data_ganda' => 'sedang', + 'data_tidak_sesuai'=> 'sedang', + 'lainnya' => 'rendah' + ]; + $prioritas = $prioritasMap[$jenisLaporan] ?? 'sedang'; + + if (!$rumahWargaId || !$jenisLaporan || !$deskripsi) { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Data tidak lengkap: rumah_warga_id, jenis_laporan, dan deskripsi wajib diisi.']); + exit; + } + + // Cek warga ada + $stmtCek = $pdo->prepare("SELECT id, nama_kepala_keluarga FROM rumah_warga WHERE id = ?"); + $stmtCek->execute([$rumahWargaId]); + $warga = $stmtCek->fetch(); + if (!$warga) { + http_response_code(404); + echo json_encode(['success' => false, 'message' => 'Data warga tidak ditemukan.']); + exit; + } + + // Cek apakah laporan duplikat dalam 7 hari terakhir + $stmtDupCheck = $pdo->prepare(" + SELECT id FROM laporan_warga + WHERE rumah_warga_id = ? AND pelapor_id = ? + AND status NOT IN ('selesai', 'ditolak') + AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY) + "); + $stmtDupCheck->execute([$rumahWargaId, $currentUser['id']]); + if ($stmtDupCheck->fetch()) { + http_response_code(409); + echo json_encode(['success' => false, 'message' => 'Anda sudah membuat laporan untuk warga ini dalam 7 hari terakhir. Harap tunggu laporan sebelumnya diproses.']); + exit; + } + + $koordinatorIbadahId = !empty($currentUser['rumah_ibadah_id']) ? (int)$currentUser['rumah_ibadah_id'] : null; + + $stmtInsert = $pdo->prepare(" + INSERT INTO laporan_warga + (rumah_warga_id, pelapor_id, koordinator_ibadah_id, jenis_laporan, deskripsi, prioritas, status) + VALUES (?, ?, ?, ?, ?, ?, 'pending') + "); + $stmtInsert->execute([ + $rumahWargaId, + $currentUser['id'], + $koordinatorIbadahId, + $jenisLaporan, + $deskripsi, + $prioritas, + ]); + + echo json_encode([ + 'success' => true, + 'message' => 'Laporan berhasil dikirim. Admin akan meninjau laporan Anda segera.', + 'id' => $pdo->lastInsertId() + ]); + break; + + case 'PUT': + $currentUser = requireAuth(['admin', 'petugas']); + $input = json_decode(file_get_contents('php://input'), true); + if (!$input) $input = $_POST; + + $id = (int)($input['id'] ?? 0); + $status = $input['status'] ?? ''; + $catatanAdmin = trim($input['catatan_admin'] ?? ''); + $ditugaskanKe = trim($input['ditugaskan_ke'] ?? ''); + + // Ambil dari old state + $stmtDok = $pdo->prepare("SELECT dokumentasi FROM laporan_warga WHERE id = ?"); + $stmtDok->execute([$id]); + $oldDok = $stmtDok->fetchColumn(); + + $dokumentasi = trim($input['dokumentasi'] ?? $oldDok ?? ''); + + // Handle file upload + if (isset($_FILES['dokumentasi_file']) && $_FILES['dokumentasi_file']['error'] === UPLOAD_ERR_OK) { + $uploadDir = '../uploads/dokumentasi/'; + if (!is_dir($uploadDir)) { + mkdir($uploadDir, 0755, true); + } + $ext = strtolower(pathinfo($_FILES['dokumentasi_file']['name'], PATHINFO_EXTENSION)); + $allowedExts = ['jpg', 'jpeg', 'png', 'pdf']; + if (in_array($ext, $allowedExts)) { + $filename = 'dok_' . $id . '_' . time() . '.' . $ext; + $dest = $uploadDir . $filename; + if (move_uploaded_file($_FILES['dokumentasi_file']['tmp_name'], $dest)) { + $dokumentasi = 'uploads/dokumentasi/' . $filename; + } + } + } + + $allowedStatus = ['pending', 'ditinjau', 'diproses', 'selesai', 'ditolak']; + if (!$id || !in_array($status, $allowedStatus)) { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'ID dan status valid wajib diisi.']); + exit; + } + + // Jika petugas, pastikan hanya bisa update dokumentasi, status, catatan. (ditugaskan_ke biarkan tetap) + if ($currentUser['role'] === 'petugas') { + $stmt = $pdo->prepare(" + UPDATE laporan_warga + SET status = ?, catatan_admin = ?, dokumentasi = ?, updated_at = NOW() + WHERE id = ? AND ditugaskan_ke = ? + "); + $stmt->execute([$status, $catatanAdmin, $dokumentasi, $id, $currentUser['username']]); + } else { + // Admin bisa update semua + $stmt = $pdo->prepare(" + UPDATE laporan_warga + SET status = ?, catatan_admin = ?, ditugaskan_ke = ?, dokumentasi = ?, updated_at = NOW() + WHERE id = ? + "); + $stmt->execute([$status, $catatanAdmin, $ditugaskanKe ?: null, $dokumentasi, $id]); + } + + $statusLabel = [ + 'pending' => 'Menunggu', + 'ditinjau' => 'Sedang Ditinjau', + 'diproses' => 'Sedang Diproses', + 'selesai' => 'Selesai', + 'ditolak' => 'Ditolak', + ]; + + echo json_encode([ + 'success' => true, + 'message' => 'Laporan berhasil diperbarui ke status: ' . ($statusLabel[$status] ?? $status) + ]); + break; + + // ── DELETE: Hapus laporan (admin only) ────────────────────────────── + case 'DELETE': + $currentUser = requireAuth(['admin']); + $id = (int)($_GET['id'] ?? 0); + if (!$id) { + $input = json_decode(file_get_contents('php://input'), true); + $id = (int)($input['id'] ?? 0); + } + if (!$id) { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'ID laporan wajib diisi.']); + exit; + } + $stmt = $pdo->prepare("DELETE FROM laporan_warga WHERE id = ?"); + $stmt->execute([$id]); + echo json_encode(['success' => true, 'message' => 'Laporan berhasil dihapus.']); + break; + + default: + http_response_code(405); + echo json_encode(['error' => 'Method not allowed']); + } +} catch (Exception $e) { + http_response_code(500); + echo json_encode(['success' => false, 'message' => 'Kesalahan sistem: ' . $e->getMessage()]); +} diff --git a/04/api/middleware.php b/04/api/middleware.php new file mode 100644 index 0000000..312b1f6 --- /dev/null +++ b/04/api/middleware.php @@ -0,0 +1,59 @@ + false, + 'message' => 'Unauthorized: Silakan login terlebih dahulu untuk mengakses fitur ini.' + ]); + exit; + } + + $user = $_SESSION['user']; + + // Cek kecocokan role (RBAC) jika list allowedRoles tidak kosong + if (!empty($allowedRoles) && !in_array($user['role'], $allowedRoles)) { + http_response_code(403); + echo json_encode([ + 'success' => false, + 'message' => 'Forbidden: Peran Anda (' . $user['role'] . ') tidak memiliki hak akses ke fitur ini.' + ]); + exit; + } + + return $user; +} + +/** + * Middleware Opsional untuk mendukung Guest Mode. + * Jika user login, akan mengembalikan data user. + * Jika belum login, akan mengembalikan null (sebagai Guest) dan proses tetap berlanjut. + * + * @return array|null Data user jika login, atau null jika guest + */ +function optionalAuth(): ?array +{ + if (isset($_SESSION['user']) && is_array($_SESSION['user'])) { + return $_SESSION['user']; + } + return null; +} diff --git a/04/api/migrate_laporan.php b/04/api/migrate_laporan.php new file mode 100644 index 0000000..f773892 --- /dev/null +++ b/04/api/migrate_laporan.php @@ -0,0 +1,42 @@ +exec($sql); + echo "Tabel laporan_warga berhasil dibuat!"; +} catch (Exception $e) { + echo "Error: " . $e->getMessage(); +} diff --git a/04/api/redistribusi.php b/04/api/redistribusi.php new file mode 100644 index 0000000..3fe86e6 --- /dev/null +++ b/04/api/redistribusi.php @@ -0,0 +1,401 @@ +prepare("SELECT nilai, tipe FROM konfigurasi_sistem WHERE kunci = ?"); + $stmt->execute([$kunci]); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (!$row) return $default; + return match($row['tipe']) { + 'integer' => (int)$row['nilai'], + 'decimal' => (float)$row['nilai'], + 'boolean' => filter_var($row['nilai'], FILTER_VALIDATE_BOOLEAN), + default => $row['nilai'], + }; +} + +/* ───────────────────────────────────────────── + CORE: Hitung beban tiap rumah ibadah + berdasarkan radius dan jumlah warga miskin +───────────────────────────────────────────── */ +function hitungBebanIbadah(PDO $pdo, float $radius): array { + // Ambil semua rumah ibadah + $ibadahList = $pdo->query("SELECT * FROM rumah_ibadah ORDER BY id")->fetchAll(PDO::FETCH_ASSOC); + + // Ambil semua rumah warga dengan status relevan + $wargaList = $pdo->query(" + SELECT id, latitude, longitude, kategori_kemiskinan, + status_bantuan, rekomendasi_bantuan, jumlah_tanggungan, + penghasilan_bulanan, status_kemandirian, skor_kemandirian, + dikelola_oleh_ibadah_id + FROM rumah_warga + WHERE status_kemandirian NOT IN ('mandiri') + AND kategori_kemiskinan IN ('sangat_miskin','miskin','rentan_miskin') + ")->fetchAll(PDO::FETCH_ASSOC); + + $result = []; + + foreach ($ibadahList as $ibadah) { + $lat1 = (float)$ibadah['latitude']; + $lng1 = (float)$ibadah['longitude']; + $rad = $ibadah['radius_primer'] ?? $radius; + $kap = (int)($ibadah['kapasitas_kk'] ?? 20); + + $wargaDalam = []; + foreach ($wargaList as $warga) { + $kelolaId = $warga['dikelola_oleh_ibadah_id']; + if ($kelolaId !== null) { + if ((int)$kelolaId === (int)$ibadah['id']) { + // Explicitly assigned to this ibadah + $d = haversine($lat1, $lng1, (float)$warga['latitude'], (float)$warga['longitude']); + $wargaDalam[] = array_merge($warga, ['jarak_meter' => round($d)]); + } + } else { + // Not assigned explicitly, check radius + $d = haversine($lat1, $lng1, (float)$warga['latitude'], (float)$warga['longitude']); + if ($d <= $rad) { + $wargaDalam[] = array_merge($warga, ['jarak_meter' => round($d)]); + } + } + } + + // Hitung kategori + $sangat = count(array_filter($wargaDalam, fn($w) => $w['kategori_kemiskinan'] === 'sangat_miskin')); + $miskin = count(array_filter($wargaDalam, fn($w) => $w['kategori_kemiskinan'] === 'miskin')); + $rentan = count(array_filter($wargaDalam, fn($w) => $w['kategori_kemiskinan'] === 'rentan_miskin')); + $total = count($wargaDalam); + + // Indeks beban: (sangat_miskin*3 + miskin*2 + rentan*1) / kapasitas + // Skor 0-100, > 100 = overload + $bobotBeban = ($sangat * 3 + $miskin * 2 + $rentan * 1); + $maxBobot = $kap * 3; // kalau semua sangat miskin + $pctBeban = $maxBobot > 0 ? round(min($bobotBeban / $maxBobot * 100, 200)) : 0; + + // Rasio kemiskinan: berapa % warga dalam radius yang masuk kategori miskin + $rasioMiskin = $total > 0 ? round(($sangat + $miskin) / $total * 100) : 0; + + // Status beban + $statusBeban = match(true) { + $total === 0 => 'kosong', + $total < $kap * 0.5 => 'ringan', + $total <= $kap => 'normal', + $total <= $kap * 1.3 => 'padat', + default => 'overload', + }; + + $result[$ibadah['id']] = [ + 'ibadah' => $ibadah, + 'radius_dipakai' => $rad, + 'kapasitas' => $kap, + 'total_warga' => $total, + 'sangat_miskin' => $sangat, + 'miskin' => $miskin, + 'rentan_miskin' => $rentan, + 'rasio_miskin' => $rasioMiskin, + 'bobot_beban' => $bobotBeban, + 'pct_beban' => $pctBeban, + 'status_beban' => $statusBeban, + 'surplus_kapasitas' => max(0, $kap - $total), + 'defisit_kapasitas' => max(0, $total - $kap), + 'warga_list' => $wargaDalam, + ]; + } + + return $result; +} + +/* ───────────────────────────────────────────── + CORE: Generate saran redistribusi otomatis +───────────────────────────────────────────── */ +function generateSaranRedistribusi(array $bebanData): array { + $saranList = []; + + // Pisahkan ibadah surplus vs defisit + $surplus = array_filter($bebanData, fn($d) => $d['status_beban'] === 'ringan' || $d['status_beban'] === 'kosong'); + $defisit = array_filter($bebanData, fn($d) => in_array($d['status_beban'], ['padat','overload'])); + + // Sort: defisit paling parah dulu, surplus paling longgar dulu + uasort($surplus, fn($a,$b) => $a['pct_beban'] <=> $b['pct_beban']); + uasort($defisit, fn($a,$b) => $b['pct_beban'] <=> $a['pct_beban']); + + foreach ($defisit as $def) { + foreach ($surplus as &$sur) { + if ($sur['surplus_kapasitas'] <= 0) continue; + + $ibadahDef = $def['ibadah']; + $ibadahSur = $sur['ibadah']; + + // Hitung jarak antar kedua ibadah + $jarakKm = round(haversine( + (float)$ibadahDef['latitude'], (float)$ibadahDef['longitude'], + (float)$ibadahSur['latitude'], (float)$ibadahSur['longitude'] + ) / 1000, 2); + + // Hanya sarankan jika jarak tidak terlalu jauh (< 5km) + if ($jarakKm > 5) continue; + + $jumlahPindah = min($def['defisit_kapasitas'], $sur['surplus_kapasitas']); + if ($jumlahPindah <= 0) continue; + + // Estimasi dana: rata-rata penghasilan warga * faktor bantuan + $estDana = $jumlahPindah * 200000; // estimasi Rp200rb/KK/bulan + + // Tingkat prioritas + $prioritas = match(true) { + $def['rasio_miskin'] >= 80 => 'tinggi', + $def['rasio_miskin'] >= 50 => 'sedang', + default => 'rendah', + }; + + $saranList[] = [ + 'pengirim_id' => (int)$ibadahSur['id'], + 'pengirim_nama' => $ibadahSur['nama'], + 'penerima_id' => (int)$ibadahDef['id'], + 'penerima_nama' => $ibadahDef['nama'], + 'jumlah_kk' => $jumlahPindah, + 'estimasi_dana' => $estDana, + 'jarak_km' => $jarakKm, + 'prioritas' => $prioritas, + 'alasan' => sprintf( + '%s memiliki surplus %d kapasitas (beban %d%%), sementara %s kelebihan %d KK (beban %d%%) dengan rasio kemiskinan %d%%.', + $ibadahSur['nama'], + $sur['surplus_kapasitas'], + $sur['pct_beban'], + $ibadahDef['nama'], + $def['defisit_kapasitas'], + $def['pct_beban'], + $def['rasio_miskin'] + ), + ]; + + // Kurangi surplus yang sudah dipakai + $sur['surplus_kapasitas'] -= $jumlahPindah; + break; + } + } + + // Sort hasil saran berdasarkan prioritas + usort($saranList, function($a, $b) { + $p = ['tinggi' => 0, 'sedang' => 1, 'rendah' => 2]; + return ($p[$a['prioritas']] ?? 9) <=> ($p[$b['prioritas']] ?? 9); + }); + + return $saranList; +} + +function haversine(float $lat1, float $lng1, float $lat2, float $lng2): float { + $R = 6371000; + $dLat = deg2rad($lat2 - $lat1); + $dLng = deg2rad($lng2 - $lng1); + $a = sin($dLat/2)**2 + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLng/2)**2; + return $R * 2 * atan2(sqrt($a), sqrt(1-$a)); +} + +/* ───────────────────────────────────────────── + ROUTING +───────────────────────────────────────────── */ +try { + // Hanya Admin yang bisa mengakses fitur redistribusi bantuan / analisis spasial + requireAuth(['admin']); + + // Guard: PDO connection must be available + if (!$pdo) { + throw new RuntimeException('Koneksi database (PDO) tidak tersedia. Pastikan XAMPP MySQL aktif.'); + } + + $method = $_SERVER['REQUEST_METHOD']; + + if ($method === 'GET') { + $action = $_GET['action'] ?? 'analisis'; + + if ($action === 'analisis') { + // Analisis beban semua rumah ibadah + $radius = (float)($_GET['radius'] ?? getKonfig($pdo, 'kapasitas_default_ibadah') ?? 500); + $beban = hitungBebanIbadah($pdo, $radius); + + // Hapus warga_list dari response utama (terlalu besar) + $ringkasan = array_map(function($d) { + $r = $d; + unset($r['warga_list']); + return $r; + }, $beban); + + echo json_encode([ + 'success' => true, + 'data' => array_values($ringkasan), + 'saran' => generateSaranRedistribusi($beban), + 'summary' => [ + 'total_ibadah' => count($beban), + 'overload' => count(array_filter($beban, fn($d) => $d['status_beban'] === 'overload')), + 'padat' => count(array_filter($beban, fn($d) => $d['status_beban'] === 'padat')), + 'normal' => count(array_filter($beban, fn($d) => $d['status_beban'] === 'normal')), + 'ringan' => count(array_filter($beban, fn($d) => $d['status_beban'] === 'ringan')), + 'kosong' => count(array_filter($beban, fn($d) => $d['status_beban'] === 'kosong')), + ], + ]); + + } elseif ($action === 'riwayat') { + // Riwayat keputusan redistribusi + $limit = (int)($_GET['limit'] ?? 20); + $status = $_GET['status'] ?? null; + $sql = "SELECT r.*, + ip.nama as pengirim_nama, ip.jenis as pengirim_jenis, + ir.nama as penerima_nama, ir.jenis as penerima_jenis + FROM redistribusi_bantuan r + JOIN rumah_ibadah ip ON r.ibadah_pengirim_id = ip.id + JOIN rumah_ibadah ir ON r.ibadah_penerima_id = ir.id"; + if ($status) $sql .= " WHERE r.status = " . $pdo->quote($status); + $sql .= " ORDER BY r.tanggal_saran DESC LIMIT $limit"; + $rows = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC); + echo json_encode(['success' => true, 'data' => $rows]); + + } elseif ($action === 'detail') { + // Detail beban 1 ibadah + $id = (int)($_GET['id'] ?? 0); + $radius = (float)($_GET['radius'] ?? 500); + $beban = hitungBebanIbadah($pdo, $radius); + if (!isset($beban[$id])) { + echo json_encode(['success' => false, 'message' => 'Rumah ibadah tidak ditemukan']); + exit; + } + echo json_encode(['success' => true, 'data' => $beban[$id]]); + } + + } elseif ($method === 'POST') { + // Simpan saran redistribusi (dari sistem atau manual admin) + $body = json_decode(file_get_contents('php://input'), true); + + $stmt = $pdo->prepare(" + INSERT INTO redistribusi_bantuan + (ibadah_pengirim_id, ibadah_penerima_id, jumlah_kk, estimasi_dana, + status, catatan_sistem, catatan_admin, periode_bulan) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + "); + $stmt->execute([ + (int)$body['pengirim_id'], + (int)$body['penerima_id'], + (int)($body['jumlah_kk'] ?? 0), + (float)($body['estimasi_dana'] ?? 0), + $body['status'] ?? 'saran_sistem', + $body['catatan_sistem'] ?? null, + $body['catatan_admin'] ?? null, + $body['periode_bulan'] ?? date('Y-m'), + ]); + + $redistId = (int)$pdo->lastInsertId(); + + // Jika disetujui, alihkan warga dari penerima (defisit) ke pengirim (surplus) + if (($body['status'] ?? '') === 'disetujui') { + $penerimaId = (int)$body['penerima_id']; + $pengirimId = (int)$body['pengirim_id']; + $limit = (int)($body['jumlah_kk'] ?? 0); + + if (isset($body['warga_ids']) && is_array($body['warga_ids']) && count($body['warga_ids']) > 0) { + $ids = array_map('intval', $body['warga_ids']); + $placeholders = str_repeat('?,', count($ids) - 1) . '?'; + $updateStmt = $pdo->prepare("UPDATE rumah_warga SET dikelola_oleh_ibadah_id = ? WHERE id IN ($placeholders)"); + $params = array_merge([$pengirimId], $ids); + $updateStmt->execute($params); + } elseif ($limit > 0) { + // Ambil radius penerima (untuk mencari warga yang saat ini masuk radiusnya) + $stmtPen = $pdo->prepare("SELECT latitude, longitude, radius_primer FROM rumah_ibadah WHERE id = ?"); + $stmtPen->execute([$penerimaId]); + $penerima = $stmtPen->fetch(PDO::FETCH_ASSOC); + + // Ambil koordinat pengirim (untuk mencari warga terdekat ke pengirim) + $stmtPeng = $pdo->prepare("SELECT latitude, longitude FROM rumah_ibadah WHERE id = ?"); + $stmtPeng->execute([$pengirimId]); + $pengirim = $stmtPeng->fetch(PDO::FETCH_ASSOC); + + if ($penerima && $pengirim) { + $radPenerima = (float)($penerima['radius_primer'] ?? 500); + + // Ambil warga yang dikelola oleh penerima atau belum dikelola siapapun + $stmtWarga = $pdo->prepare("SELECT id, latitude, longitude, dikelola_oleh_ibadah_id FROM rumah_warga WHERE (dikelola_oleh_ibadah_id = ? OR dikelola_oleh_ibadah_id IS NULL) AND kategori_kemiskinan IN ('sangat_miskin', 'miskin', 'rentan_miskin') AND status_kemandirian != 'mandiri'"); + $stmtWarga->execute([$penerimaId]); + $wargaList = $stmtWarga->fetchAll(PDO::FETCH_ASSOC); + + $eligible = []; + foreach ($wargaList as $w) { + // Jika sudah secara eksplisit dikelola oleh penerima, atau jika NULL dan masih dalam radius penerima + $isManaged = ($w['dikelola_oleh_ibadah_id'] == $penerimaId); + $distPenerima = haversine((float)$w['latitude'], (float)$w['longitude'], (float)$penerima['latitude'], (float)$penerima['longitude']); + + if ($isManaged || $distPenerima <= $radPenerima) { + $distPengirim = haversine((float)$w['latitude'], (float)$w['longitude'], (float)$pengirim['latitude'], (float)$pengirim['longitude']); + $w['dist_pengirim'] = $distPengirim; + $eligible[] = $w; + } + } + + // Urutkan warga berdasarkan jarak terdekat ke rumah ibadah pengirim (surplus) + usort($eligible, fn($a, $b) => $a['dist_pengirim'] <=> $b['dist_pengirim']); + + // Ambil sejumlah $limit + $toTransfer = array_slice($eligible, 0, $limit); + + if (!empty($toTransfer)) { + $ids = array_column($toTransfer, 'id'); + $placeholders = str_repeat('?,', count($ids) - 1) . '?'; + $updateStmt = $pdo->prepare("UPDATE rumah_warga SET dikelola_oleh_ibadah_id = ? WHERE id IN ($placeholders)"); + $params = array_merge([$pengirimId], $ids); + $updateStmt->execute($params); + } + } + } + } + + echo json_encode([ + 'success' => true, + 'message' => 'Saran redistribusi berhasil disimpan', + 'id' => $redistId, + ]); + + } elseif ($method === 'PUT') { + // Update status keputusan admin + $body = json_decode(file_get_contents('php://input'), true); + $id = (int)($body['id'] ?? 0); + + $stmt = $pdo->prepare(" + UPDATE redistribusi_bantuan + SET status = ?, + catatan_admin = ?, + disetujui_oleh = ?, + tanggal_keputusan = NOW() + WHERE id = ? + "); + $stmt->execute([ + $body['status'], + $body['catatan_admin'] ?? null, + $body['disetujui_oleh'] ?? null, + $id, + ]); + + echo json_encode(['success' => true, 'message' => 'Status redistribusi diperbarui']); + + } elseif ($method === 'DELETE') { + $id = (int)($_GET['id'] ?? 0); + $stmt = $pdo->prepare("DELETE FROM redistribusi_bantuan WHERE id = ?"); + $stmt->execute([$id]); + echo json_encode(['success' => true, 'message' => 'Data redistribusi dihapus']); + } + +} catch (Throwable $e) { + http_response_code(500); + echo json_encode(['success' => false, 'message' => $e->getMessage()]); +} \ No newline at end of file diff --git a/04/api/rumah_ibadah.php b/04/api/rumah_ibadah.php new file mode 100644 index 0000000..c621cc9 --- /dev/null +++ b/04/api/rumah_ibadah.php @@ -0,0 +1,136 @@ +query("SELECT * FROM rumah_ibadah WHERE id = $id"); + if ($row = $result->fetch_assoc()) { + echo json_encode(['success' => true, 'data' => $row]); + } else { + echo json_encode(['success' => false, 'message' => 'Data tidak ditemukan']); + } + } else { + // Filter opsional + $where = []; + if (!empty($_GET['jenis'])) $where[] = "jenis = '" . $conn->real_escape_string($_GET['jenis']) . "'"; + if (!empty($_GET['kecamatan'])) $where[] = "kecamatan LIKE '%" . $conn->real_escape_string($_GET['kecamatan']) . "%'"; + if (!empty($_GET['search'])) $where[] = "(nama LIKE '%" . $conn->real_escape_string($_GET['search']) . "%' OR alamat LIKE '%" . $conn->real_escape_string($_GET['search']) . "%')"; + + $sql = "SELECT * FROM rumah_ibadah" . ($where ? " WHERE " . implode(' AND ', $where) : "") . " ORDER BY nama ASC"; + $result = $conn->query($sql); + if ($result === false) { + echo json_encode([ + 'success' => false, + 'message' => 'Gagal mengambil data dari tabel "rumah_ibadah": ' . $conn->error . '. Pastikan Anda telah mengimpor berkas database "database/bansos_gis.sql" di phpMyAdmin Anda!' + ]); + break; + } + $data = []; + while ($row = $result->fetch_assoc()) $data[] = $row; + echo json_encode(['success' => true, 'data' => $data, 'total' => count($data)]); + } + break; + + // ── POST ───────────────────────────────────────────────────────────────── + case 'POST': + $input = json_decode(file_get_contents('php://input'), true) ?: $_POST; + + $nama = $conn->real_escape_string(trim($input['nama'] ?? '')); + $jenis = $conn->real_escape_string($input['jenis'] ?? 'masjid'); + $alamat = $conn->real_escape_string($input['alamat'] ?? ''); + $kelurahan = $conn->real_escape_string($input['kelurahan'] ?? ''); + $kecamatan = $conn->real_escape_string($input['kecamatan'] ?? ''); + $kota = $conn->real_escape_string($input['kota'] ?? ''); + $lat = (float)($input['latitude'] ?? 0); + $lng = (float)($input['longitude'] ?? 0); + $pj = $conn->real_escape_string($input['penanggung_jawab'] ?? ''); + $kontak = $conn->real_escape_string($input['kontak'] ?? ''); + $ket = $conn->real_escape_string($input['keterangan'] ?? ''); + + $kap = (int)($input['kapasitas_kk'] ?? 20); + $rad = (int)($input['radius_primer'] ?? 500); + $anggaran = (float)($input['anggaran_bulanan'] ?? 0); + + if (!$nama || !$lat || !$lng) { + echo json_encode(['success' => false, 'message' => 'Nama, latitude, dan longitude wajib diisi']); + break; + } + + $sql = "INSERT INTO rumah_ibadah (nama,jenis,alamat,kelurahan,kecamatan,kota,latitude,longitude,penanggung_jawab,kontak,keterangan,kapasitas_kk,radius_primer,anggaran_bulanan) + VALUES ('$nama','$jenis','$alamat','$kelurahan','$kecamatan','$kota',$lat,$lng,'$pj','$kontak','$ket',$kap,$rad,$anggaran)"; + + if ($conn->query($sql)) { + echo json_encode(['success' => true, 'message' => 'Data berhasil disimpan', 'id' => $conn->insert_id]); + } else { + echo json_encode(['success' => false, 'message' => 'Gagal menyimpan: ' . $conn->error]); + } + break; + + // ── PUT ────────────────────────────────────────────────────────────────── + case 'PUT': + $input = json_decode(file_get_contents('php://input'), true); + $id = (int)($input['id'] ?? 0); + $nama = $conn->real_escape_string(trim($input['nama'] ?? '')); + $jenis = $conn->real_escape_string($input['jenis'] ?? 'masjid'); + $alamat = $conn->real_escape_string($input['alamat'] ?? ''); + $kelurahan = $conn->real_escape_string($input['kelurahan'] ?? ''); + $kecamatan = $conn->real_escape_string($input['kecamatan'] ?? ''); + $kota = $conn->real_escape_string($input['kota'] ?? ''); + $lat = (float)($input['latitude'] ?? 0); + $lng = (float)($input['longitude'] ?? 0); + $pj = $conn->real_escape_string($input['penanggung_jawab'] ?? ''); + $kontak = $conn->real_escape_string($input['kontak'] ?? ''); + $ket = $conn->real_escape_string($input['keterangan'] ?? ''); + + $kap = (int)($input['kapasitas_kk'] ?? 20); + $rad = (int)($input['radius_primer'] ?? 500); + $anggaran = (float)($input['anggaran_bulanan'] ?? 0); + + if (!$id) { echo json_encode(['success' => false, 'message' => 'ID tidak valid']); break; } + + $sql = "UPDATE rumah_ibadah SET + nama='$nama', jenis='$jenis', alamat='$alamat', + kelurahan='$kelurahan', kecamatan='$kecamatan', kota='$kota', + latitude=$lat, longitude=$lng, + penanggung_jawab='$pj', kontak='$kontak', keterangan='$ket', + kapasitas_kk=$kap, radius_primer=$rad, anggaran_bulanan=$anggaran + WHERE id=$id"; + + if ($conn->query($sql)) { + echo json_encode(['success' => true, 'message' => 'Data berhasil diperbarui']); + } else { + echo json_encode(['success' => false, 'message' => 'Gagal memperbarui: ' . $conn->error]); + } + break; + + // ── DELETE ─────────────────────────────────────────────────────────────── + case 'DELETE': + $id = (int)($_GET['id'] ?? 0); + if (!$id) { + $input = json_decode(file_get_contents('php://input'), true); + $id = (int)($input['id'] ?? 0); + } + if ($conn->query("DELETE FROM rumah_ibadah WHERE id=$id")) { + echo json_encode(['success' => true, 'message' => 'Data berhasil dihapus']); + } else { + echo json_encode(['success' => false, 'message' => 'Gagal menghapus: ' . $conn->error]); + } + break; + + default: + http_response_code(405); + echo json_encode(['error' => 'Method not allowed']); +} +$conn->close(); \ No newline at end of file diff --git a/04/api/rumah_warga.php b/04/api/rumah_warga.php new file mode 100644 index 0000000..f9d0dee --- /dev/null +++ b/04/api/rumah_warga.php @@ -0,0 +1,511 @@ +query("SELECT tanggal_lahir, status_bekerja, dapat_bekerja, kondisi_kesehatan FROM anggota_keluarga WHERE rumah_warga_id = $wargaId"); + $anggota = []; + while ($r = $res->fetch_assoc()) $anggota[] = $r; + + $adaProduktif = false; // usia 18-55 th + $adaProduktifBisaKerja = false; // produktif & bisa kerja & belum bekerja tetap + $semuaProduktifTakBisa = true; // semua yg produktif tidak bisa kerja + + foreach ($anggota as $a) { + $usia = null; + if ($a['tanggal_lahir']) { + $lahir = new DateTime($a['tanggal_lahir']); + $usia = (int)$today->diff($lahir)->y; + } + + if ($usia !== null && $usia >= 18 && $usia <= 55) { + $adaProduktif = true; + $bisaKerja = (int)$a['dapat_bekerja'] === 1 + && in_array($a['kondisi_kesehatan'], ['sehat', 'sakit_ringan']); + + if ($bisaKerja) $semuaProduktifTakBisa = false; + + // Produktif, bisa kerja, belum bekerja tetap → kandidat pelatihan + if ($bisaKerja && $a['status_bekerja'] !== 'bekerja') { + $adaProduktifBisaKerja = true; + } + } + } + + // Kriteria bantuan rutin + $perluRutin = !$adaProduktif + || ($adaProduktif && $semuaProduktifTakBisa) + || $penghasilan < 500000 + || $statusPekerjaan === 'tidak_bekerja'; + + // Kriteria bantuan pelatihan + $perluPelatihan = $adaProduktifBisaKerja; + + if ($perluRutin && $perluPelatihan) return 'keduanya'; + if ($perluRutin) return 'bantuan_rutin'; + if ($perluPelatihan) return 'bantuan_pelatihan'; + return 'tidak_ada'; +} + +// ════════════════════════════════════════════════════════════════════════════ +// FUNGSI: Hitung kategori kesejahteraan otomatis berdasarkan data warga +// ════════════════════════════════════════════════════════════════════════════ +function hitungKategoriKesejahteraan(float $penghasilan, int $tanggungan, string $statusTempatTinggal, string $sumberAir): string +{ + // Logika sederhana: + // Jika penghasilan sangat rendah dan tanggungan banyak + $pendapatanPerKapita = $tanggungan > 0 ? $penghasilan / $tanggungan : $penghasilan; + + $skor = 0; + + if ($pendapatanPerKapita < 300000) { + $skor += 40; + } elseif ($pendapatanPerKapita < 600000) { + $skor += 20; + } + + if ($statusTempatTinggal === 'numpang' || $statusTempatTinggal === 'sewa') { + $skor += 20; + } + + if ($sumberAir === 'sungai' || $sumberAir === 'hujan') { + $skor += 10; + } + + if ($skor >= 50) return 'sangat_miskin'; + if ($skor >= 20) return 'miskin'; + return 'rentan_miskin'; +} + +// ════════════════════════════════════════════════════════════════════════════ +// FUNGSI: Memeriksa akses koordinator berdasarkan kelurahan/kecamatan ATAU radius geografis <= 5km +// ════════════════════════════════════════════════════════════════════════════ +function checkCoordinatorAccess(mysqli $conn, int $koorIbadahId, float $lat, float $lng, string $kelurahan, string $kecamatan): bool +{ + $wilRes = $conn->query("SELECT latitude, longitude, kelurahan, kecamatan FROM rumah_ibadah WHERE id = $koorIbadahId"); + if ($wil = $wilRes->fetch_assoc()) { + // Cek 1: Pencocokan nama kelurahan/kecamatan (sebagai fallback tangguh) + $kel = strtolower($wil['kelurahan']); + $kec = strtolower($wil['kecamatan']); + $wKel = strtolower(trim($kelurahan)); + $wKec = strtolower(trim($kecamatan)); + if (($kel !== '' && strpos($wKel, $kel) !== false) || ($kec !== '' && strpos($wKec, $kec) !== false)) { + return true; + } + + // Cek 2: Jarak radius geografis <= 5000m (karena batas slider maksimal 5km) + $lat1 = (float)$wil['latitude']; + $lng1 = (float)$wil['longitude']; + if ($lat1 !== 0.0 && $lng1 !== 0.0 && $lat !== 0.0 && $lng !== 0.0) { + $earthRadius = 6371000; + $dLat = deg2rad($lat - $lat1); + $dLng = deg2rad($lng - $lng1); + $a = sin($dLat/2) * sin($dLat/2) + cos(deg2rad($lat1)) * cos(deg2rad($lat)) * sin($dLng/2) * sin($dLng/2); + $c = 2 * atan2(sqrt($a), sqrt(1-$a)); + $dist = $earthRadius * $c; + if ($dist <= 5000.0) { + return true; + } + } + } + return false; +} + +// ════════════════════════════════════════════════════════════════════════════ +switch ($method) { + // ── GET ────────────────────────────────────────────────────────────────── + case 'GET': + // Izinkan guest mode (opsional) untuk melihat data rumah warga + $currentUser = optionalAuth(); + $isGuest = $currentUser === null; + + // Jika tidak login (guest), validasi akses + if ($isGuest) { + $currentUser = ['role' => 'guest']; + } else { + // Validasi role jika sudah login + if (!in_array($currentUser['role'], ['admin', 'petugas', 'koordinator', 'kontributor'])) { + http_response_code(403); + echo json_encode(['success' => false, 'message' => 'Forbidden: Role Anda tidak memiliki akses.']); + exit; + } + } + + if (isset($_GET['id'])) { + $id = (int)$_GET['id']; + $res = $conn->query("SELECT * FROM rumah_warga WHERE id=$id"); + if ($row = $res->fetch_assoc()) { + // Batasi Koordinator agar hanya bisa melihat detail warga di wilayah tugasnya sendiri (radius / kelurahan / kecamatan) + if ($currentUser['role'] === 'koordinator') { + $koorIbadahId = (int)$currentUser['rumah_ibadah_id']; + $lat = (float)($row['latitude'] ?? 0); + $lng = (float)($row['longitude'] ?? 0); + $kelurahan = $row['kelurahan'] ?? ''; + $kecamatan = $row['kecamatan'] ?? ''; + if (!checkCoordinatorAccess($conn, $koorIbadahId, $lat, $lng, $kelurahan, $kecamatan)) { + http_response_code(403); + echo json_encode(['success' => false, 'message' => 'Forbidden: Detail warga di luar wilayah tugas Anda.']); + exit; + } + } + + // Sertakan anggota keluarga jika bukan koordinator / kontributor / guest + if ($currentUser['role'] === 'koordinator') { + $row['anggota'] = []; + unset($row['no_kk']); + unset($row['nik']); + unset($row['penghasilan_bulanan']); + unset($row['pekerjaan']); + unset($row['status_pekerjaan']); + unset($row['luas_rumah']); + unset($row['jenis_lantai']); + unset($row['jenis_dinding']); + unset($row['sumber_air']); + unset($row['status_tempat_tinggal']); + unset($row['keterangan']); + unset($row['skor_kepercayaan']); + unset($row['is_rekomendasi_manual']); + unset($row['is_kategori_manual']); + // KEEP: kategori_kemiskinan, jumlah_tanggungan (needed for display) + } elseif ($currentUser['role'] === 'kontributor' || $currentUser['role'] === 'guest') { + $row['anggota'] = []; + unset($row['no_kk']); + unset($row['nik']); + unset($row['penghasilan_bulanan']); + unset($row['pekerjaan']); + unset($row['status_pekerjaan']); + unset($row['luas_rumah']); + unset($row['jenis_lantai']); + unset($row['jenis_dinding']); + unset($row['sumber_air']); + unset($row['status_tempat_tinggal']); + unset($row['keterangan']); + unset($row['skor_kepercayaan']); + unset($row['is_rekomendasi_manual']); + unset($row['is_kategori_manual']); + unset($row['created_at']); + unset($row['updated_at']); + // KEEP: kategori_kemiskinan, jumlah_tanggungan, nama_kepala_keluarga (needed for display) + } else { + $resA = $conn->query("SELECT * FROM anggota_keluarga WHERE rumah_warga_id=$id ORDER BY tanggal_lahir ASC"); + $anggota = []; + while ($a = $resA->fetch_assoc()) $anggota[] = $a; + $row['anggota'] = $anggota; + } + echo json_encode(['success' => true, 'data' => $row]); + } else { + echo json_encode(['success' => false, 'message' => 'Data tidak ditemukan']); + } + break; + } + + // Filter pencarian + $where = []; + if (!empty($_GET['kategori'])) $where[] = "kategori_kemiskinan = '" . $conn->real_escape_string($_GET['kategori']) . "'"; + if (!empty($_GET['status'])) $where[] = "status_bantuan = '" . $conn->real_escape_string($_GET['status']) . "'"; + if (!empty($_GET['rekomendasi'])) $where[] = "rekomendasi_bantuan = '" . $conn->real_escape_string($_GET['rekomendasi']) . "'"; + if (!empty($_GET['kecamatan'])) $where[] = "kecamatan LIKE '%" . $conn->real_escape_string($_GET['kecamatan']) . "%'"; + if (!empty($_GET['kelurahan'])) $where[] = "kelurahan LIKE '%" . $conn->real_escape_string($_GET['kelurahan']) . "%'"; + if (!empty($_GET['search'])) $where[] = "(nama_kepala_keluarga LIKE '%" . $conn->real_escape_string($_GET['search']) . "%' OR no_kk LIKE '%" . $conn->real_escape_string($_GET['search']) . "%' OR nik LIKE '%" . $conn->real_escape_string($_GET['search']) . "%')"; + + // Filter wilayah untuk koordinator tidak lagi dibatasi per kelurahan/kecamatan di SQL, + // melainkan akan difilter secara dinamis berdasarkan radius geografis di frontend. + + $sql = "SELECT * FROM rumah_warga" . ($where ? " WHERE " . implode(' AND ', $where) : "") . " ORDER BY nama_kepala_keluarga ASC"; + $result = $conn->query($sql); + if ($result === false) { + echo json_encode([ + 'success' => false, + 'message' => 'Gagal mengambil data dari tabel "rumah_warga": ' . $conn->error + ]); + break; + } + $data = []; + while ($row = $result->fetch_assoc()) { + if ($currentUser['role'] === 'koordinator') { + unset($row['no_kk']); + unset($row['nik']); + unset($row['penghasilan_bulanan']); + unset($row['pekerjaan']); + unset($row['status_pekerjaan']); + unset($row['luas_rumah']); + unset($row['jenis_lantai']); + unset($row['jenis_dinding']); + unset($row['sumber_air']); + unset($row['status_tempat_tinggal']); + unset($row['keterangan']); + unset($row['skor_kepercayaan']); + unset($row['is_rekomendasi_manual']); + // KEEP: kategori_kemiskinan, jumlah_tanggungan (needed for frontend display) + } elseif ($currentUser['role'] === 'kontributor' || $currentUser['role'] === 'guest') { + unset($row['no_kk']); + unset($row['nik']); + unset($row['penghasilan_bulanan']); + unset($row['pekerjaan']); + unset($row['status_pekerjaan']); + unset($row['luas_rumah']); + unset($row['jenis_lantai']); + unset($row['jenis_dinding']); + unset($row['sumber_air']); + unset($row['status_tempat_tinggal']); + unset($row['keterangan']); + unset($row['skor_kepercayaan']); + unset($row['is_rekomendasi_manual']); + unset($row['created_at']); + unset($row['updated_at']); + // KEEP: kategori_kemiskinan, jumlah_tanggungan, nama_kepala_keluarga (needed for frontend display) + } + $data[] = $row; + } + echo json_encode(['success' => true, 'data' => $data, 'total' => count($data)]); + break; + + // ── POST ───────────────────────────────────────────────────────────────── + case 'POST': + // Membatasi akses: Hanya Admin dan Petugas Lapangan yang bisa menginput + requireAuth(['admin', 'petugas']); + + $input = json_decode(file_get_contents('php://input'), true) ?: $_POST; + + $no_kk = $conn->real_escape_string(trim($input['no_kk'] ?? '')); + $nama = $conn->real_escape_string(trim($input['nama_kepala_keluarga'] ?? '')); + $nik = $conn->real_escape_string($input['nik'] ?? ''); + $alamat = $conn->real_escape_string($input['alamat'] ?? ''); + $kelurahan = $conn->real_escape_string($input['kelurahan'] ?? ''); + $kecamatan = $conn->real_escape_string($input['kecamatan'] ?? ''); + $kota = $conn->real_escape_string($input['kota'] ?? ''); + $lat = (float)($input['latitude'] ?? 0); + $lng = (float)($input['longitude'] ?? 0); + $tanggungan = (int)($input['jumlah_tanggungan'] ?? 1); + $penghasilan= (float)($input['penghasilan_bulanan'] ?? 0); + $pekerjaan = $conn->real_escape_string($input['pekerjaan'] ?? ''); + $stsPekerjaan = $conn->real_escape_string($input['status_pekerjaan'] ?? 'tidak_bekerja'); + $luas = !empty($input['luas_rumah']) ? (float)$input['luas_rumah'] : 'NULL'; + $lantai = $conn->real_escape_string($input['jenis_lantai'] ?? 'semen'); + $dinding = $conn->real_escape_string($input['jenis_dinding'] ?? 'tembok'); + $air = $conn->real_escape_string($input['sumber_air'] ?? 'sumur'); + $tinggal = $conn->real_escape_string($input['status_tempat_tinggal'] ?? 'milik_sendiri'); + $kategoriInput = $conn->real_escape_string($input['kategori_kemiskinan'] ?? 'miskin'); + $isKatManual = ($kategoriInput !== 'sistem') ? 1 : 0; + $kategori = ($isKatManual === 1) ? $kategoriInput : hitungKategoriKesejahteraan($penghasilan, $tanggungan, $tinggal, $air); + + $stsbansos = $conn->real_escape_string($input['status_bantuan'] ?? 'belum_terima'); + $ket = $conn->real_escape_string($input['keterangan'] ?? ''); + $dikelola = !empty($input['dikelola_oleh_ibadah_id']) ? (int)$input['dikelola_oleh_ibadah_id'] : 'NULL'; + + // Validasi + if (!$nama || !$lat || !$lng) { + echo json_encode(['success' => false, 'message' => 'Nama KK, latitude, dan longitude wajib diisi']); break; + } + if (!$no_kk || strlen(preg_replace('/\D/', '', $no_kk)) !== 16) { + echo json_encode(['success' => false, 'message' => 'Nomor KK harus 16 digit angka']); break; + } + // Cek duplikat KK + $cek = $conn->query("SELECT id FROM rumah_warga WHERE no_kk='$no_kk'"); + if ($cek->num_rows > 0) { + echo json_encode(['success' => false, 'message' => 'Nomor KK sudah terdaftar']); break; + } + + $luasVal = is_numeric($luas) ? $luas : 'NULL'; + $rekInput = $conn->real_escape_string($input['rekomendasi_bantuan'] ?? 'sistem'); + $isManual = ($rekInput !== 'sistem') ? 1 : 0; + + $sql = "INSERT INTO rumah_warga + (no_kk,nama_kepala_keluarga,nik,alamat,kelurahan,kecamatan,kota,latitude,longitude, + jumlah_tanggungan,penghasilan_bulanan,pekerjaan,status_pekerjaan, + luas_rumah,jenis_lantai,jenis_dinding,sumber_air,status_tempat_tinggal, + kategori_kemiskinan,is_kategori_manual,status_bantuan,rekomendasi_bantuan,is_rekomendasi_manual,keterangan, + dikelola_oleh_ibadah_id, tanggal_verifikasi, skor_kepercayaan, status_verifikasi) + VALUES + ('$no_kk','$nama','$nik','$alamat','$kelurahan','$kecamatan','$kota',$lat,$lng, + $tanggungan,$penghasilan,'$pekerjaan','$stsPekerjaan', + $luasVal,'$lantai','$dinding','$air','$tinggal', + '$kategori',$isKatManual,'$stsbansos','bantuan_rutin',$isManual,'$ket', + $dikelola, CURRENT_DATE(), 100, 'terverifikasi')"; + + if ($conn->query($sql)) { + $newId = $conn->insert_id; + + // Simpan anggota jika ada + if (!empty($input['anggota']) && is_array($input['anggota'])) { + simpanAnggota($conn, $newId, $input['anggota']); + } + + // Hitung rekomendasi otomatis + if ($isManual === 1) { + $rek = $rekInput; + } else { + $rek = hitungRekomendasi($conn, $newId, $penghasilan, $stsPekerjaan); + } + $conn->query("UPDATE rumah_warga SET rekomendasi_bantuan='$rek' WHERE id=$newId"); + + echo json_encode(['success' => true, 'message' => 'Data berhasil disimpan', 'id' => $newId, 'rekomendasi' => $rek]); + } else { + echo json_encode(['success' => false, 'message' => 'Gagal menyimpan: ' . $conn->error]); + } + break; + + // ── PUT ────────────────────────────────────────────────────────────────── + case 'PUT': + // Membatasi akses: Admin, Petugas Lapangan, dan Koordinator Rumah Ibadah + $currentUser = requireAuth(['admin', 'petugas', 'koordinator']); + + $input = json_decode(file_get_contents('php://input'), true); + $id = (int)($input['id'] ?? 0); + if (!$id) { echo json_encode(['success' => false, 'message' => 'ID tidak valid']); break; } + + if ($currentUser['role'] === 'koordinator') { + $stsbansos = $conn->real_escape_string($input['status_bantuan'] ?? 'belum_terima'); + + // Cek apakah warga berada di wilayah tugas koordinator (radius / kelurahan / kecamatan) + $koorIbadahId = (int)$currentUser['rumah_ibadah_id']; + $wargaRes = $conn->query("SELECT latitude, longitude, kelurahan, kecamatan FROM rumah_warga WHERE id = $id"); + if ($wargaRow = $wargaRes->fetch_assoc()) { + $lat = (float)($wargaRow['latitude'] ?? 0); + $lng = (float)($wargaRow['longitude'] ?? 0); + $kelurahan = $wargaRow['kelurahan'] ?? ''; + $kecamatan = $wargaRow['kecamatan'] ?? ''; + if (!checkCoordinatorAccess($conn, $koorIbadahId, $lat, $lng, $kelurahan, $kecamatan)) { + http_response_code(403); + echo json_encode(['success' => false, 'message' => 'Forbidden: Warga di luar wilayah tugas Anda.']); + exit; + } + } else { + echo json_encode(['success' => false, 'message' => 'Warga tidak ditemukan.']); + exit; + } + + $sql = "UPDATE rumah_warga SET status_bantuan='$stsbansos' WHERE id=$id"; + if ($conn->query($sql)) { + echo json_encode(['success' => true, 'message' => 'Status bantuan warga berhasil diperbarui']); + } else { + echo json_encode(['success' => false, 'message' => 'Gagal memperbarui status bantuan: ' . $conn->error]); + } + break; + } + + $no_kk = $conn->real_escape_string(trim($input['no_kk'] ?? '')); + $nama = $conn->real_escape_string(trim($input['nama_kepala_keluarga'] ?? '')); + $nik = $conn->real_escape_string($input['nik'] ?? ''); + $alamat = $conn->real_escape_string($input['alamat'] ?? ''); + $kelurahan = $conn->real_escape_string($input['kelurahan'] ?? ''); + $kecamatan = $conn->real_escape_string($input['kecamatan'] ?? ''); + $kota = $conn->real_escape_string($input['kota'] ?? ''); + $lat = (float)($input['latitude'] ?? 0); + $lng = (float)($input['longitude'] ?? 0); + $tanggungan = (int)($input['jumlah_tanggungan'] ?? 1); + $penghasilan= (float)($input['penghasilan_bulanan'] ?? 0); + $pekerjaan = $conn->real_escape_string($input['pekerjaan'] ?? ''); + $stsPekerjaan = $conn->real_escape_string($input['status_pekerjaan'] ?? 'tidak_bekerja'); + $luasVal = !empty($input['luas_rumah']) ? (float)$input['luas_rumah'] : 'NULL'; + $lantai = $conn->real_escape_string($input['jenis_lantai'] ?? 'semen'); + $dinding = $conn->real_escape_string($input['jenis_dinding'] ?? 'tembok'); + $air = $conn->real_escape_string($input['sumber_air'] ?? 'sumur'); + $tinggal = $conn->real_escape_string($input['status_tempat_tinggal'] ?? 'milik_sendiri'); + $kategoriInput = $conn->real_escape_string($input['kategori_kemiskinan'] ?? 'miskin'); + $isKatManual = ($kategoriInput !== 'sistem') ? 1 : 0; + $kategori = ($isKatManual === 1) ? $kategoriInput : hitungKategoriKesejahteraan($penghasilan, $tanggungan, $tinggal, $air); + + $stsbansos = $conn->real_escape_string($input['status_bantuan'] ?? 'belum_terima'); + $rekInput = $conn->real_escape_string($input['rekomendasi_bantuan'] ?? 'sistem'); + $isManual = ($rekInput !== 'sistem') ? 1 : 0; + $ket = $conn->real_escape_string($input['keterangan'] ?? ''); + $dikelola = isset($input['dikelola_oleh_ibadah_id']) && $input['dikelola_oleh_ibadah_id'] !== '' ? (int)$input['dikelola_oleh_ibadah_id'] : 'NULL'; + + // Cek duplikat KK (kecuali diri sendiri) + $cek = $conn->query("SELECT id FROM rumah_warga WHERE no_kk='$no_kk' AND id != $id"); + if ($cek->num_rows > 0) { + echo json_encode(['success' => false, 'message' => 'Nomor KK sudah digunakan data lain']); break; + } + + // Update anggota jika ada + if (!empty($input['anggota']) && is_array($input['anggota'])) { + $conn->query("DELETE FROM anggota_keluarga WHERE rumah_warga_id=$id"); + simpanAnggota($conn, $id, $input['anggota']); + } + + // Hitung rekomendasi otomatis + if ($isManual === 1) { + $rek = $rekInput; + } else { + $rek = hitungRekomendasi($conn, $id, $penghasilan, $stsPekerjaan); + } + + $sql = "UPDATE rumah_warga SET + no_kk='$no_kk', nama_kepala_keluarga='$nama', nik='$nik', + alamat='$alamat', kelurahan='$kelurahan', kecamatan='$kecamatan', kota='$kota', + latitude=$lat, longitude=$lng, + jumlah_tanggungan=$tanggungan, penghasilan_bulanan=$penghasilan, + pekerjaan='$pekerjaan', status_pekerjaan='$stsPekerjaan', + luas_rumah=$luasVal, jenis_lantai='$lantai', jenis_dinding='$dinding', + sumber_air='$air', status_tempat_tinggal='$tinggal', + kategori_kemiskinan='$kategori', is_kategori_manual=$isKatManual, status_bantuan='$stsbansos', + rekomendasi_bantuan='$rek', is_rekomendasi_manual=$isManual, keterangan='$ket', + dikelola_oleh_ibadah_id=$dikelola + WHERE id=$id"; + + if ($conn->query($sql)) { + echo json_encode(['success' => true, 'message' => 'Data berhasil diperbarui', 'rekomendasi' => $rek]); + } else { + echo json_encode(['success' => false, 'message' => 'Gagal memperbarui: ' . $conn->error]); + } + break; + + // ── DELETE ─────────────────────────────────────────────────────────────── + case 'DELETE': + // Membatasi akses: HANYA Admin yang boleh menghapus data warga + requireAuth(['admin']); + + $id = (int)($_GET['id'] ?? 0); + if (!$id) { + $input = json_decode(file_get_contents('php://input'), true); + $id = (int)($input['id'] ?? 0); + } + if ($conn->query("DELETE FROM rumah_warga WHERE id=$id")) { + echo json_encode(['success' => true, 'message' => 'Data berhasil dihapus']); + } else { + echo json_encode(['success' => false, 'message' => 'Gagal menghapus: ' . $conn->error]); + } + break; + + default: + http_response_code(405); + echo json_encode(['error' => 'Method not allowed']); +} +$conn->close(); + +// ════════════════════════════════════════════════════════════════════════════ +function simpanAnggota(mysqli $conn, int $wargaId, array $list): void +{ + foreach ($list as $a) { + $nama = $conn->real_escape_string(trim($a['nama'] ?? '')); + if (!$nama) continue; + $nik = $conn->real_escape_string($a['nik'] ?? ''); + $tl = !empty($a['tanggal_lahir']) ? "'" . $conn->real_escape_string($a['tanggal_lahir']) . "'" : 'NULL'; + $jk = $conn->real_escape_string($a['jenis_kelamin'] ?? 'laki_laki'); + $pend = $conn->real_escape_string($a['pendidikan'] ?? 'sd'); + $pek = $conn->real_escape_string($a['pekerjaan'] ?? ''); + $stsBk = $conn->real_escape_string($a['status_bekerja'] ?? 'tidak_bekerja'); + $kes = $conn->real_escape_string($a['kondisi_kesehatan'] ?? 'sehat'); + $bisa = (int)($a['dapat_bekerja'] ?? 1); + + $conn->query("INSERT INTO anggota_keluarga (rumah_warga_id,nama,nik,tanggal_lahir,jenis_kelamin,pendidikan,pekerjaan,status_bekerja,kondisi_kesehatan,dapat_bekerja) + VALUES ($wargaId,'$nama','$nik',$tl,'$jk','$pend','$pek','$stsBk','$kes',$bisa)"); + } +} \ No newline at end of file diff --git a/04/api/statistik.php b/04/api/statistik.php new file mode 100644 index 0000000..47bbfee --- /dev/null +++ b/04/api/statistik.php @@ -0,0 +1,60 @@ +query("SELECT jenis, COUNT(*) as total FROM rumah_ibadah GROUP BY jenis"); +if ($result === false) { + http_response_code(500); + echo json_encode([ + 'success' => false, + 'message' => 'Gagal memuat statistik. Tabel "rumah_ibadah" tidak ditemukan. Pastikan Anda telah mengimpor berkas database "database/bansos_gis.sql" di phpMyAdmin Anda!' + ]); + exit; +} +$ibadah = []; +while ($row = $result->fetch_assoc()) $ibadah[$row['jenis']] = (int)$row['total']; +$stats['rumah_ibadah'] = $ibadah; +$stats['total_ibadah'] = array_sum($ibadah); + +// Total rumah warga per kategori +$result = $conn->query("SELECT kategori_kemiskinan, COUNT(*) as total FROM rumah_warga GROUP BY kategori_kemiskinan"); +$warga = []; +while ($row = $result->fetch_assoc()) $warga[$row['kategori_kemiskinan']] = (int)$row['total']; +$stats['kategori_kemiskinan'] = $warga; +$stats['total_warga'] = array_sum($warga); + +// Status bantuan +$result = $conn->query("SELECT status_bantuan, COUNT(*) as total FROM rumah_warga GROUP BY status_bantuan"); +$bansos = []; +while ($row = $result->fetch_assoc()) $bansos[$row['status_bantuan']] = (int)$row['total']; +$stats['status_bansos'] = $bansos; + +// Rekomendasi bantuan +$result = $conn->query("SELECT rekomendasi_bantuan, COUNT(*) as total FROM rumah_warga GROUP BY rekomendasi_bantuan"); +$rek = []; +while ($row = $result->fetch_assoc()) $rek[$row['rekomendasi_bantuan']] = (int)$row['total']; +$stats['rekomendasi_bantuan'] = $rek; + +// Total jiwa +$result = $conn->query("SELECT SUM(jumlah_tanggungan) as total FROM rumah_warga"); +$row = $result->fetch_assoc(); +$stats['total_jiwa'] = (int)$row['total']; + +// Total anggota di DB +$result = $conn->query("SELECT COUNT(*) as total FROM anggota_keluarga"); +$row = $result->fetch_assoc(); +$stats['total_anggota_db'] = (int)$row['total']; + +// Rata-rata penghasilan +$result = $conn->query("SELECT AVG(penghasilan_bulanan) as avg_ph FROM rumah_warga"); +$row = $result->fetch_assoc(); +$stats['rata_penghasilan'] = round((float)$row['avg_ph'], 0); + +echo json_encode(['success' => true, 'data' => $stats]); +$conn->close(); \ No newline at end of file diff --git a/04/api/verifikasi.php b/04/api/verifikasi.php new file mode 100644 index 0000000..8a8fda7 --- /dev/null +++ b/04/api/verifikasi.php @@ -0,0 +1,316 @@ +prepare("SELECT nilai, tipe FROM konfigurasi_sistem WHERE kunci = ?"); + $stmt->execute([$kunci]); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (!$row) return $default; + return match($row['tipe']) { + 'integer' => (int)$row['nilai'], + 'decimal' => (float)$row['nilai'], + 'boolean' => filter_var($row['nilai'], FILTER_VALIDATE_BOOLEAN), + default => $row['nilai'], + }; +} + +/* ───────────────────────────────────────────── + CORE: Hitung skor kepercayaan data + Formula: mulai 100, kurangi seiring waktu + Decay rate: linear -100/interval_bulan per bulan +───────────────────────────────────────────── */ +function hitungSkorKepercayaan( + ?string $tanggalVerifikasi, + int $intervalBulan, + int $minSkor = 40 +): array { + if (!$tanggalVerifikasi) { + return ['skor' => 0, 'hari_sejak' => null, 'status' => 'kadaluarsa']; + } + + $tglVerif = new DateTime($tanggalVerifikasi); + $today = new DateTime(); + + // Jika tanggal verifikasi di masa depan (karena beda timezone / lag server), anggap 0 hari + if ($tglVerif > $today) { + $hariSejak = 0; + } else { + $diff = $today->diff($tglVerif); + $hariSejak = $diff->days; + } + $intervalHari = $intervalBulan * 30; + + // Skor linear: 100 di hari 0, turun ke 0 di hari intervalHari + $skor = max(0, (int)round(100 - ($hariSejak / $intervalHari * 100))); + + $status = match(true) { + $skor >= 80 => 'terverifikasi', + $skor >= $minSkor => 'perlu_update', + default => 'kadaluarsa', + }; + + // Berapa hari lagi sampai perlu_update dan kadaluarsa + $hariMenujuPerluUpdate = max(0, (int)($intervalHari * 0.2) - $hariSejak); + $hariMenujuKadaluarsa = max(0, (int)($intervalHari * ((100 - $minSkor) / 100)) - $hariSejak); + + return [ + 'skor' => $skor, + 'hari_sejak_verifikasi' => $hariSejak, + 'interval_hari' => $intervalHari, + 'status' => $status, + 'hari_menuju_perlu_update' => $hariMenujuPerluUpdate, + 'hari_menuju_kadaluarsa' => $hariMenujuKadaluarsa, + 'tanggal_kadaluarsa' => (clone $tglVerif)->modify("+{$intervalHari} days")->format('Y-m-d'), + ]; +} + +/* ───────────────────────────────────────────── + UPDATE skor semua warga secara batch + Formula: mulai 100, kurangi seiring waktu +───────────────────────────────────────────── */ +function updateSkopBatch(PDO $pdo): int { + $interval = getKonfig($pdo, 'interval_verifikasi_bulan', 6); + $minSkor = getKonfig($pdo, 'skor_data_kadaluarsa', 40); + + $wargaList = $pdo->query("SELECT id, tanggal_verifikasi FROM rumah_warga")->fetchAll(PDO::FETCH_ASSOC); + $updated = 0; + + $stmt = $pdo->prepare(" + UPDATE rumah_warga SET skor_kepercayaan = ?, status_verifikasi = ? WHERE id = ? + "); + + foreach ($wargaList as $w) { + $calc = hitungSkorKepercayaan($w['tanggal_verifikasi'], $interval, $minSkor); + $stmt->execute([$calc['skor'], $calc['status'], (int)$w['id']]); + $updated++; + } + + return $updated; +} + +/* ───────────────────────────────────────────── + ROUTING +───────────────────────────────────────────── */ +try { + $method = $_SERVER['REQUEST_METHOD']; + $action = $_GET['action'] ?? 'list'; + + if ($method === 'GET') { + // Hanya Admin dan Petugas Lapangan yang boleh melihat data verifikasi + requireAuth(['admin', 'petugas']); + + if ($action === 'list') { + // Daftar warga yang perlu diverifikasi + $statusFilter = $_GET['status'] ?? null; // terverifikasi|perlu_update|kadaluarsa + $limit = (int)($_GET['limit'] ?? 50); + $offset = (int)($_GET['offset'] ?? 0); + + // Update skor dulu secara batch + updateSkopBatch($pdo); + + if ($statusFilter === 'semua') { + $where = ""; + } elseif ($statusFilter === 'terverifikasi' || $statusFilter === 'perlu_update' || $statusFilter === 'kadaluarsa') { + $where = "WHERE rw.status_verifikasi = " . $pdo->quote($statusFilter); + } else { + // Default: Hanya warga yang perlu verifikasi (perlu_update atau kadaluarsa) + $where = "WHERE rw.status_verifikasi IN ('perlu_update', 'kadaluarsa')"; + } + $sql = " + SELECT rw.id, rw.nama_kepala_keluarga, rw.no_kk, + rw.kelurahan, rw.kecamatan, + rw.tanggal_verifikasi, rw.petugas_verifikasi, + rw.skor_kepercayaan, rw.status_verifikasi, + rw.kategori_kemiskinan, rw.status_bantuan, + rw.latitude, rw.longitude + FROM rumah_warga rw + $where + ORDER BY rw.skor_kepercayaan ASC, rw.tanggal_verifikasi ASC + LIMIT $limit OFFSET $offset + "; + $rows = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC); + + // Enrichment: hitung detail decay per row + $interval = getKonfig($pdo, 'interval_verifikasi_bulan', 6); + $minSkor = getKonfig($pdo, 'skor_data_kadaluarsa', 40); + foreach ($rows as &$r) { + $r['detail_skor'] = hitungSkorKepercayaan( + $r['tanggal_verifikasi'], $interval, $minSkor + ); + } + + // Summary count + $summary = $pdo->query(" + SELECT status_verifikasi, COUNT(*) as jumlah + FROM rumah_warga + GROUP BY status_verifikasi + ")->fetchAll(PDO::FETCH_KEY_PAIR); + + echo json_encode([ + 'success' => true, + 'data' => $rows, + 'summary' => $summary, + 'total' => count($rows), + ]); + + } elseif ($action === 'config') { + // Ambil semua konfigurasi + $rows = $pdo->query("SELECT * FROM konfigurasi_sistem ORDER BY id")->fetchAll(PDO::FETCH_ASSOC); + echo json_encode(['success' => true, 'data' => $rows]); + + } elseif ($action === 'riwayat') { + // Riwayat verifikasi untuk 1 warga + $wargaId = (int)($_GET['warga_id'] ?? 0); + $rows = $pdo->prepare(" + SELECT * FROM riwayat_verifikasi + WHERE rumah_warga_id = ? + ORDER BY tanggal DESC LIMIT 20 + "); + $rows->execute([$wargaId]); + echo json_encode(['success' => true, 'data' => $rows->fetchAll(PDO::FETCH_ASSOC)]); + + } elseif ($action === 'statistik') { + updateSkopBatch($pdo); + $data = [ + 'per_status' => $pdo->query(" + SELECT status_verifikasi, COUNT(*) as jumlah, + ROUND(AVG(skor_kepercayaan)) as avg_skor + FROM rumah_warga GROUP BY status_verifikasi + ")->fetchAll(PDO::FETCH_ASSOC), + 'avg_skor_global' => (float)$pdo->query("SELECT ROUND(AVG(skor_kepercayaan),1) FROM rumah_warga")->fetchColumn(), + 'paling_lama' => $pdo->query(" + SELECT nama_kepala_keluarga, tanggal_verifikasi, skor_kepercayaan + FROM rumah_warga ORDER BY skor_kepercayaan ASC LIMIT 5 + ")->fetchAll(PDO::FETCH_ASSOC), + ]; + echo json_encode(['success' => true, 'data' => $data]); + } + + } elseif ($method === 'POST') { + // Catat verifikasi baru untuk 1 warga + // Hanya Admin, Petugas Lapangan, dan Koordinator + $currentUser = requireAuth(['admin', 'petugas', 'koordinator']); + + $body = json_decode(file_get_contents('php://input'), true); + $wargaId = (int)($body['warga_id'] ?? 0); + $petugas = trim($body['petugas'] ?? ''); + $catatan = $body['catatan'] ?? null; + $tanggal = $body['tanggal'] ?? date('Y-m-d'); + + if (!$wargaId) { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'warga_id wajib diisi']); + exit; + } + + // Jika koordinator, lakukan pengecekan wilayah + if ($currentUser['role'] === 'koordinator') { + $koorIbadahId = (int)$currentUser['rumah_ibadah_id']; + $wilayahRes = $pdo->prepare("SELECT kelurahan, kecamatan FROM rumah_ibadah WHERE id = ?"); + $wilayahRes->execute([$koorIbadahId]); + if ($wil = $wilayahRes->fetch(PDO::FETCH_ASSOC)) { + $kel = $wil['kelurahan']; + $kec = $wil['kecamatan']; + + $wargaRes = $pdo->prepare("SELECT kelurahan, kecamatan FROM rumah_warga WHERE id = ?"); + $wargaRes->execute([$wargaId]); + if ($wargaRow = $wargaRes->fetch(PDO::FETCH_ASSOC)) { + $wKel = strtolower(trim($wargaRow['kelurahan'] ?? '')); + $wKec = strtolower(trim($wargaRow['kecamatan'] ?? '')); + if (strpos($wKel, strtolower($kel)) === false && strpos($wKec, strtolower($kec)) === false) { + http_response_code(403); + echo json_encode(['success' => false, 'message' => 'Forbidden: Warga di luar wilayah tugas Anda.']); + exit; + } + } else { + http_response_code(404); + echo json_encode(['success' => false, 'message' => 'Warga tidak ditemukan.']); + exit; + } + } else { + http_response_code(403); + echo json_encode(['success' => false, 'message' => 'Forbidden: Wilayah tugas tidak terkonfigurasi.']); + exit; + } + } + + // Ambil skor lama + $oldRow = $pdo->prepare("SELECT skor_kepercayaan, status_verifikasi FROM rumah_warga WHERE id = ?"); + $oldRow->execute([$wargaId]); + $old = $oldRow->fetch(PDO::FETCH_ASSOC); + $skorLama = $old ? (int)$old['skor_kepercayaan'] : null; + + // Reset skor ke 100 & update tanggal verifikasi + $stmt = $pdo->prepare(" + UPDATE rumah_warga + SET tanggal_verifikasi = ?, + petugas_verifikasi = ?, + skor_kepercayaan = 100, + status_verifikasi = 'terverifikasi', + updated_at = NOW() + WHERE id = ? + "); + $stmt->execute([$tanggal, $petugas ?: null, $wargaId]); + + // Simpan riwayat + $riwayat = $pdo->prepare(" + INSERT INTO riwayat_verifikasi + (rumah_warga_id, tanggal, petugas, skor_sebelum, skor_sesudah, catatan) + VALUES (?, ?, ?, ?, 100, ?) + "); + $riwayat->execute([$wargaId, $tanggal, $petugas ?: null, $skorLama, $catatan]); + + echo json_encode([ + 'success' => true, + 'message' => 'Verifikasi berhasil dicatat. Skor kepercayaan data dikembalikan ke 100.', + 'skor_lama' => $skorLama, + 'skor_baru' => 100, + ]); + + } elseif ($method === 'PUT') { + // Update konfigurasi sistem + // HANYA Admin yang boleh merubah konfigurasi sistem + requireAuth(['admin']); + + $body = json_decode(file_get_contents('php://input'), true); + if (!is_array($body)) { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Body tidak valid']); + exit; + } + + $stmt = $pdo->prepare("UPDATE konfigurasi_sistem SET nilai = ? WHERE kunci = ?"); + $updated = 0; + foreach ($body as $kunci => $nilai) { + $stmt->execute([(string)$nilai, $kunci]); + $updated += $stmt->rowCount(); + } + + // Setelah konfigurasi diupdate, recalculate semua skor + updateSkopBatch($pdo); + + echo json_encode([ + 'success' => true, + 'message' => "$updated konfigurasi berhasil diperbarui. Skor kepercayaan seluruh data telah dihitung ulang.", + ]); + } + +} catch (Exception $e) { + http_response_code(500); + echo json_encode(['success' => false, 'message' => $e->getMessage()]); +} \ No newline at end of file diff --git a/04/assets/css/fitur_baru.css b/04/assets/css/fitur_baru.css new file mode 100644 index 0000000..b844c3a --- /dev/null +++ b/04/assets/css/fitur_baru.css @@ -0,0 +1,943 @@ +/* ================================================================ + TAMBAHAN CSS — 3 Fitur Baru WebGIS Bansos + Append ke bagian bawah assets/css/style.css yang sudah ada +================================================================ */ + +/* ─── Panel Header ─── */ +.fb-panel-header { + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; + font-weight: 600; + color: var(--text-primary); + padding: 10px 0 8px; + border-bottom: 1px solid var(--border); + margin-bottom: 10px; +} + +/* ─── Sub Tabs ─── */ +.fb-sub-tabs { + display: flex; + gap: 2px; + background: var(--bg-tertiary); + border-radius: 8px; + padding: 3px; + margin-bottom: 10px; +} + +.fb-sub-tab { + flex: 1; + padding: 5px 4px; + font-size: 10px; + font-weight: 500; + border: none; + border-radius: 6px; + background: transparent; + color: var(--text-muted); + cursor: pointer; + transition: all 0.15s; + text-align: center; +} + +.fb-sub-tab.active { + background: var(--bg-primary); + color: var(--text-primary); + box-shadow: 0 1px 3px rgba(0,0,0,0.1); +} + +.fb-sub-panel { display: none; } +.fb-sub-panel.active { display: block; } + +/* ─── States ─── */ +.fb-loading { + display: flex; + align-items: center; + gap: 8px; + padding: 20px; + color: var(--text-muted); + font-size: 11px; + justify-content: center; + animation: pulse 1.5s infinite; +} + +.fb-empty { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + padding: 24px 16px; + color: var(--text-muted); + font-size: 11px; + text-align: center; +} + +.fb-error { + display: flex; + align-items: center; + gap: 6px; + padding: 12px; + background: rgba(239,68,68,0.08); + border-radius: 8px; + color: var(--danger); + font-size: 11px; +} + +.fb-badge { + display: inline-flex; + align-items: center; + gap: 3px; + padding: 2px 8px; + border-radius: 20px; + font-size: 10px; + font-weight: 600; + white-space: nowrap; +} + +/* ================================================================ + FITUR 1: REDISTRIBUSI +================================================================ */ +.redistribusi-summary { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 4px; + margin-bottom: 10px; +} + +.redist-sum-item { + border-radius: 6px; + border: 1px solid transparent; + padding: 6px 4px; + text-align: center; +} + +.redist-sum-val { + display: block; + font-size: 16px; + font-weight: 700; + line-height: 1; +} + +.redist-sum-key { + display: block; + font-size: 9px; + color: var(--text-muted); + margin-top: 2px; +} + +/* ── Beban Card ── */ +.redist-card { + background: var(--bg-secondary); + border-radius: 10px; + padding: 10px; + margin-bottom: 8px; + border: 1px solid var(--border); + transition: box-shadow 0.2s; +} + +.redist-card:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.1); } + +.redist-card-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 6px; + margin-bottom: 8px; +} + +.redist-card-nama { + font-size: 12px; + font-weight: 600; + color: var(--text-primary); +} + +.redist-card-sub { + font-size: 10px; + color: var(--text-muted); + margin-top: 1px; +} + +.redist-progress-wrap { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 8px; +} + +.redist-progress-bar { + flex: 1; + height: 5px; + background: var(--border); + border-radius: 3px; + overflow: hidden; +} + +.redist-progress-fill { + height: 100%; + border-radius: 3px; + transition: width 0.4s ease; +} + +.redist-progress-pct { + font-size: 10px; + font-weight: 600; + color: var(--text-secondary); + min-width: 32px; + text-align: right; +} + +.redist-stat-row { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 4px; +} + +.redist-stat-item { + text-align: center; + padding: 4px 2px; + background: var(--bg-tertiary); + border-radius: 6px; +} + +.redist-stat-val { + display: block; + font-size: 13px; + font-weight: 700; + line-height: 1; +} + +.redist-stat-key { + display: block; + font-size: 9px; + color: var(--text-muted); + margin-top: 2px; +} + +/* ── Saran Card ── */ +.saran-card { + background: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: 10px; + padding: 10px; + margin-bottom: 8px; +} + +.saran-card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.saran-flow { + display: flex; + align-items: center; + gap: 4px; + margin-bottom: 6px; + flex-wrap: wrap; +} + +.saran-ibadah { + display: inline-flex; + align-items: center; + gap: 3px; + padding: 3px 8px; + border-radius: 6px; + font-size: 11px; + font-weight: 600; +} + +.saran-ibadah.surplus { + background: rgba(16,185,129,0.12); + color: #10b981; +} + +.saran-ibadah.defisit { + background: rgba(239,68,68,0.12); + color: #ef4444; +} + +.saran-arrow { + font-size: 10px; + color: var(--text-muted); + font-weight: 600; + padding: 0 2px; +} + +.saran-alasan { + font-size: 10px; + color: var(--text-muted); + line-height: 1.5; + margin-bottom: 4px; +} + +.saran-actions { + display: flex; + gap: 6px; + margin-top: 8px; +} + +/* ── Riwayat Item ── */ +.riwayat-item { + padding: 8px 10px; + background: var(--bg-secondary); + border-radius: 8px; + border: 1px solid var(--border); + margin-bottom: 6px; +} + +/* ================================================================ + FITUR 2: VERIFIKASI / KETERBARUAN DATA +================================================================ */ +.verif-summary-row { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 6px; + margin-bottom: 10px; +} + +.verif-sum-card { + border-radius: 8px; + padding: 8px 6px; + text-align: center; + border: 1px solid transparent; +} + +.verif-sum-card.ok { background: rgba(16,185,129,0.1); border-color: rgba(16,185,129,0.3); } +.verif-sum-card.warn { background: rgba(245,158,11,0.1); border-color: rgba(245,158,11,0.3); } +.verif-sum-card.err { background: rgba(239,68,68,0.1); border-color: rgba(239,68,68,0.3); } + +.verif-sum-card span { + display: block; + font-size: 18px; + font-weight: 700; + line-height: 1.1; + margin-bottom: 2px; +} + +.verif-sum-card.ok span { color: #10b981; } +.verif-sum-card.warn span { color: #f59e0b; } +.verif-sum-card.err span { color: #ef4444; } + +.verif-sum-card small { + display: block; + font-size: 9px; + color: var(--text-muted); + line-height: 1.1; +} + +/* ── Verif Item ── */ +.verif-item { + background: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: 10px; + padding: 10px; + margin-bottom: 8px; +} + +.verif-item-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 6px; + margin-bottom: 8px; +} + +.verif-item-nama { + font-size: 12px; + font-weight: 600; + color: var(--text-primary); +} + +.verif-item-sub { + font-size: 10px; + color: var(--text-muted); + margin-top: 1px; +} + +.skor-bar-wrap { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 6px; +} + +.skor-bar { + flex: 1; + height: 6px; + background: var(--border); + border-radius: 3px; + overflow: hidden; +} + +.skor-bar-fill { + height: 100%; + border-radius: 3px; + transition: width 0.4s ease; +} + +.skor-pct { + font-size: 10px; + font-weight: 700; + min-width: 28px; + text-align: right; +} + +.verif-meta { + display: flex; + flex-wrap: wrap; + gap: 6px; + font-size: 10px; + color: var(--text-muted); + align-items: center; +} + +.verif-info-box { + display: flex; + align-items: center; + gap: 6px; + background: rgba(99,102,241,0.08); + border: 1px solid rgba(99,102,241,0.2); + border-radius: 8px; + padding: 8px 10px; + font-size: 10px; + color: var(--text-secondary); + margin-top: 8px; +} + +/* ── Konfigurasi ── */ +.konfig-intro { + font-size: 11px; + color: var(--text-muted); + margin-bottom: 10px; + line-height: 1.5; +} + +.konfig-item { + margin-bottom: 10px; + padding-bottom: 10px; + border-bottom: 1px solid var(--border); +} + +.konfig-item:last-child { border-bottom: none; } + +.konfig-label { + display: block; + font-size: 11px; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 3px; +} + +.konfig-desc { + font-size: 10px; + color: var(--text-muted); + margin-bottom: 5px; + line-height: 1.4; +} + +/* ================================================================ + FITUR 3: KEMANDIRIAN +================================================================ */ +.kemandirian-siap-badge { + margin-left: auto; + background: rgba(16,185,129,0.12); + color: #10b981; + border-radius: 20px; + padding: 2px 8px; + font-size: 10px; + font-weight: 600; +} + +.kemandirian-item { + background: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: 10px; + padding: 10px; + margin-bottom: 8px; + transition: box-shadow 0.2s; +} + +.kemandirian-item:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.1); } + +.kemandirian-item-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 6px; + margin-bottom: 8px; +} + +.kemandirian-nama { + font-size: 12px; + font-weight: 600; + color: var(--text-primary); +} + +.kemandirian-sub { + font-size: 10px; + color: var(--text-muted); + margin-top: 1px; +} + +/* ── Gauge Grid ── */ +.kemandirian-gauges { + display: flex; + gap: 4px; + justify-content: space-around; + flex-wrap: wrap; + margin-bottom: 6px; + padding: 8px; + background: var(--bg-tertiary); + border-radius: 8px; +} + +.kemandirian-gauge { + display: flex; + flex-direction: column; + align-items: center; + gap: 0; + position: relative; +} + +.gauge-val { + font-size: 13px; + font-weight: 700; + line-height: 1; + margin-top: -4px; +} + +.gauge-label { + font-size: 9px; + color: var(--text-muted); + margin-top: 1px; +} + +.kemandirian-est { + display: flex; + align-items: center; + gap: 4px; + font-size: 10px; + color: var(--text-secondary); + margin-bottom: 4px; +} + +.kemandirian-rek { + background: var(--bg-tertiary); + border-radius: 6px; + padding: 6px 8px; + margin-top: 4px; +} + +.kemandirian-rek-item { + display: flex; + align-items: flex-start; + gap: 4px; + font-size: 10px; + color: var(--text-muted); + line-height: 1.4; + padding: 2px 0; +} + +/* ── Animation ── */ +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +/* ── Responsive fine-tuning ── */ +@media (max-width: 320px) { + .redistribusi-summary { grid-template-columns: repeat(3, 1fr); } + .redist-stat-row { grid-template-columns: repeat(2, 1fr); } + .kemandirian-gauges { gap: 2px; } +} + +/* ─── Side by Side Modals ─── */ +@media (min-width: 1100px) { + body.side-by-side-modals #modal-premium-verifikasi { + justify-content: center; + padding-right: 560px; + transition: all 0.3s ease; + } + body.side-by-side-modals #modal-warga { + justify-content: center; + padding-left: 540px; + background: transparent; + backdrop-filter: none; + pointer-events: none; + transition: all 0.3s ease; + } + body.side-by-side-modals #modal-warga .modal { + pointer-events: auto; + } +} + +/* ================================================================ + RBAC & LOGIN & DONASI FRONTEND STYLES +================================================================ */ +.user-profile-panel { + display: flex; + align-items: center; + gap: 12px; + padding: 12px; + background: var(--bg-secondary); + border-bottom: 1px solid var(--border); + transition: all 0.25s ease; +} + +.user-avatar-wrap { + position: relative; +} + +.user-avatar { + width: 36px; + height: 36px; + border-radius: 50%; + background: linear-gradient(135deg, #6366f1, #4338ca); + color: #ffffff; + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + font-size: 14px; + box-shadow: 0 2px 5px rgba(99, 102, 241, 0.3); +} + +.user-avatar.avatar-guest { + background: linear-gradient(135deg, #94a3b8, #64748b); + box-shadow: 0 2px 5px rgba(100, 116, 139, 0.3); +} + +.user-info { + flex: 1; + min-width: 0; +} + +.user-name { + font-size: 11px; + font-weight: 700; + color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.user-role-badge { + display: inline-block; + font-size: 9px; + font-weight: 600; + padding: 1px 6px; + border-radius: 4px; + margin-top: 2px; +} + +.badge-guest { background: rgba(148, 163, 184, 0.12); color: #64748b; } +.badge-admin { background: rgba(239, 68, 68, 0.12); color: #ef4444; } +.badge-petugas { background: rgba(245, 158, 11, 0.12); color: #d97706; } +.badge-koordinator { background: rgba(99, 102, 241, 0.12); color: #6366f1; } +.badge-kontributor { background: rgba(16, 185, 129, 0.12); color: #10b981; } + +.btn-auth-action { + font-size: 10px; + font-weight: 600; + padding: 5px 10px; + border-radius: 6px; + border: 1px solid var(--border); + background: var(--bg-primary); + color: var(--text-primary); + cursor: pointer; + transition: all 0.2s ease; +} + +.btn-auth-action:hover { + background: var(--bg-tertiary); + border-color: var(--text-muted); +} + +/* Kebutuhan & Donasi Cards */ +.kebutuhan-card { + background: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: 10px; + padding: 12px; + margin-bottom: 10px; + transition: all 0.2s ease; +} + +.kebutuhan-card:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); + border-color: var(--accent-light); +} + +.kebutuhan-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 8px; + margin-bottom: 8px; +} + +.kebutuhan-ibadah-nama { + font-size: 11px; + font-weight: 700; + color: var(--text-primary); +} + +.kebutuhan-bantuan-jenis { + font-size: 10px; + color: var(--text-muted); + margin-top: 1px; +} + +.kebutuhan-progress-section { + margin: 10px 0; +} + +.kebutuhan-progress-text { + display: flex; + justify-content: space-between; + font-size: 10px; + font-weight: 600; + color: var(--text-secondary); + margin-bottom: 4px; +} + +.kebutuhan-progress-bar { + height: 6px; + background: var(--bg-tertiary); + border-radius: 3px; + overflow: hidden; +} + +.kebutuhan-progress-fill { + height: 100%; + background: linear-gradient(90deg, #10b981, #34d399); + border-radius: 3px; + transition: width 0.4s ease; +} + +.kebutuhan-footer { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 8px; + gap: 8px; +} + +.kebutuhan-sisa { + font-size: 10px; + font-weight: 600; + color: #10b981; +} + +.kebutuhan-sisa.kebutuhan-penuh { + color: var(--text-muted); +} + +/* Riwayat Kontribusi Item */ +.kontribusi-item { + background: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: 10px; + padding: 10px; + margin-bottom: 8px; +} + +.kontribusi-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 6px; +} + +/* ================================================================ + PREMIUM DISTINCT LOGIN CARD REDESIGN (LIGHT CLASSIC PREMIUM) + ================================================================ */ +.login-premium-modal { + border-radius: 16px !important; + overflow: hidden !important; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.02), 0 0 0 1px rgba(99, 102, 241, 0.15) !important; + border: none !important; + background: #ffffff !important; + animation: modalSlideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1); +} + +@keyframes modalSlideUp { + from { transform: translateY(12px); opacity: 0; } + to { transform: translateY(0); opacity: 1; } +} + +.login-premium-header { + background: #ffffff !important; + padding: 26px 20px 18px 20px !important; + text-align: center; + position: relative; + border-bottom: 1px solid #f1f5f9 !important; +} + +.login-premium-header .modal-title { + color: #1e293b !important; + font-size: 16px !important; + font-weight: 800 !important; + text-transform: uppercase; + letter-spacing: 0.8px; + width: 100%; +} + +.login-premium-header .login-sub-title { + color: #64748b; + font-size: 11px; + margin-top: 5px; +} + +.login-premium-header .modal-close { + color: #64748b !important; + background: #f1f5f9 !important; + border-radius: 50% !important; + width: 26px !important; + height: 26px !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + transition: all 0.2s !important; + position: absolute !important; + top: 15px !important; + right: 15px !important; + border: none !important; +} + +.login-premium-header .modal-close:hover { + background: #e2e8f0 !important; + color: #0f172a !important; +} + +.login-premium-logo-wrap { + width: 50px; + height: 50px; + background: rgba(99, 102, 241, 0.08) !important; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto 12px auto; + border: 1px solid rgba(99, 102, 241, 0.15); + box-shadow: 0 4px 12px rgba(99, 102, 241, 0.08) !important; +} + +.login-premium-logo-wrap svg { + stroke: #4f46e5; + width: 24px; + height: 24px; +} + +.login-premium-body { + padding: 24px 22px !important; + background: #ffffff !important; +} + +.login-premium-input-group { + position: relative; + margin-bottom: 18px; +} + +.login-premium-input-group label { + display: block; + font-size: 10px; + font-weight: 700; + color: #475569; + text-transform: uppercase; + margin-bottom: 6px; + letter-spacing: 0.5px; +} + +.login-premium-input-wrapper { + position: relative; + display: flex; + align-items: center; +} + +.login-premium-input-wrapper svg { + position: absolute; + left: 12px; + color: #94a3b8; + pointer-events: none; + z-index: 10; + transition: color 0.2s; +} + +.login-premium-input-wrapper .form-control { + padding-left: 38px !important; + height: 42px !important; + border-radius: 8px !important; + border: 1px solid #cbd5e1 !important; + background: #f8fafc !important; + color: #0f172a !important; + font-size: 12px !important; + font-weight: 500 !important; + transition: all 0.2s ease-in-out !important; + width: 100% !important; +} + +.login-premium-input-wrapper .form-control::placeholder { + color: #94a3b8 !important; +} + +.login-premium-input-wrapper .form-control:focus { + border-color: #6366f1 !important; + box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15) !important; + background: #ffffff !important; +} + +.login-premium-input-wrapper .form-control:focus + svg { + color: #6366f1 !important; +} + +.login-premium-footer { + padding: 0 22px 26px 22px !important; + border-top: none !important; + display: flex; + gap: 12px; + background: #ffffff !important; +} + +.login-premium-footer .btn { + height: 42px !important; + border-radius: 8px !important; + font-size: 12px !important; + font-weight: 700 !important; + flex: 1; +} + +.login-premium-footer .btn-primary { + background: linear-gradient(135deg, #6366f1, #4f46e5) !important; + border: none !important; + box-shadow: 0 4px 12px rgba(79, 70, 229, 0.15) !important; + transition: all 0.2s; + color: #ffffff !important; +} + +.login-premium-footer .btn-primary:hover { + background: linear-gradient(135deg, #7073ff, #6366f1) !important; + box-shadow: 0 6px 16px rgba(79, 70, 229, 0.25) !important; + transform: translateY(-1.5px); +} + +.login-premium-footer .btn-ghost { + border: 1px solid #cbd5e1 !important; + background: #ffffff !important; + color: #475569 !important; +} + +.login-premium-footer .btn-ghost:hover { + background: #f1f5f9 !important; + border-color: #cbd5e1 !important; +} + +.login-hint-premium { + margin-top: 18px; + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 10px; + padding: 12px; +} + \ No newline at end of file diff --git a/04/assets/css/style.css b/04/assets/css/style.css new file mode 100644 index 0000000..d65d885 --- /dev/null +++ b/04/assets/css/style.css @@ -0,0 +1,847 @@ +/* assets/css/style.css */ +@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500;600;700&family=DM+Mono:wght@400;500&display=swap'); + +:root { + --bg-deep: #f8fafc; + --bg-panel: #ffffff; + --bg-card: #ffffff; + --bg-hover: #f1f5f9; + --border: #e2e8f0; + --border-light: #cbd5e1; + --border-subtle: #f1f5f9; + + --accent: #4f46e5; + --accent-light: #6366f1; + --accent-glow: rgba(99, 102, 241, 0.1); + --accent-2: #8b5cf6; + + --text-primary: #1e293b; + --text-secondary:#475569; + --text-muted: #64748b; + --text-hint: #94a3b8; + + --success: #10b981; + --success-bg: #ecfdf5; + --success-text: #065f46; + --warning: #f59e0b; + --warning-bg: #fffbeb; + --warning-text: #92400e; + --danger: #ef4444; + --danger-bg: #fef2f2; + --danger-text: #991b1b; + --info: #3b82f6; + --info-bg: #eff6ff; + --info-text: #1e40af; + + --icon-tint: #64748b; + --icon-active: #4f46e5; + + --radius-xs: 4px; + --radius-sm: 8px; + --radius: 12px; + --radius-lg: 16px; + --radius-xl: 20px; + + --shadow-sm: 0 1px 3px rgba(0,0,0,0.05); + --shadow: 0 4px 6px -1px rgba(0,0,0,0.05), 0 2px 4px -1px rgba(0,0,0,0.03); + --shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.08); + + --font: 'DM Sans', sans-serif; + --mono: 'DM Mono', monospace; + --sidebar-w: 300px; +} + +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } + +html, body { + height: 100%; + font-family: var(--font); + background: var(--bg-deep); + color: var(--text-primary); + overflow: hidden; + -webkit-font-smoothing: antialiased; +} + +/* ───────────────────────────────────────────── + LAYOUT +───────────────────────────────────────────── */ +#app { display: flex; height: 100vh; width: 100vw; } + +/* ───────────────────────────────────────────── + SIDEBAR +───────────────────────────────────────────── */ +#sidebar { + width: var(--sidebar-w); + min-width: var(--sidebar-w); + background: var(--bg-panel); + border-right: 1px solid var(--border); + display: flex; + flex-direction: column; + z-index: 100; + overflow: hidden; +} + +.sidebar-header { + padding: 18px 16px 14px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; + background: var(--bg-panel); +} + +.logo { display: flex; align-items: center; gap: 10px; margin-bottom: 3px; } + +.logo-icon { + width: 32px; height: 32px; + background: var(--accent); + border-radius: var(--radius-sm); + display: flex; align-items: center; justify-content: center; + flex-shrink: 0; +} +.logo-icon svg { width: 17px; height: 17px; } +.logo-text { font-size: 14px; font-weight: 700; letter-spacing: -0.2px; color: var(--text-primary); } +.logo-sub { font-size: 10px; color: var(--text-muted); font-weight: 400; letter-spacing: 0.2px; margin-left: 42px; line-height: 1.4; } + +/* Tabs */ +.sidebar-tabs { display: flex; border-bottom: 1px solid var(--border); flex-shrink: 0; padding: 0 4px; } + +.tab-btn { + flex: 1; padding: 10px 4px; + background: transparent; border: none; + border-bottom: 2px solid transparent; + color: var(--text-muted); + font-family: var(--font); font-size: 11px; font-weight: 600; + cursor: pointer; transition: all 0.2s; letter-spacing: 0.3px; + display: flex; flex-direction: column; align-items: center; gap: 5px; + margin-bottom: -1px; +} +.tab-btn svg { width: 16px; height: 16px; stroke: currentColor; fill: none; } +.tab-btn:hover { color: var(--text-secondary); } +.tab-btn.active { color: var(--accent-light); border-bottom-color: var(--accent-light); } + +.sidebar-content { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 14px 12px; } +.sidebar-content::-webkit-scrollbar { width: 3px; } +.sidebar-content::-webkit-scrollbar-track { background: transparent; } +.sidebar-content::-webkit-scrollbar-thumb { background: var(--border-light); border-radius: 2px; } + +.tab-panel { display: none; } +.tab-panel.active { display: block; } + +/* ───────────────────────────────────────────── + STATS +───────────────────────────────────────────── */ +.stats-grid { + display: grid; grid-template-columns: 1fr 1fr; + gap: 6px; margin-bottom: 14px; +} + +.stat-card { + background: var(--bg-card); border: 1px solid var(--border); + border-radius: var(--radius); padding: 12px 10px; + text-align: center; transition: border-color 0.2s; + position: relative; overflow: hidden; +} +.stat-card::before { + content: ''; position: absolute; top: 0; left: 0; right: 0; + height: 2px; background: var(--accent); opacity: 0.4; +} +.stat-card--rutin::before { background: var(--danger); opacity: 0.5; } +.stat-card--pelatihan::before { background: var(--success); opacity: 0.5; } +.stat-card:hover { border-color: var(--border-light); } + +.stat-number { + font-size: 24px; font-weight: 700; font-family: var(--font); + color: var(--accent-light); letter-spacing: -0.5px; line-height: 1.1; +} +.stat-card--rutin .stat-number { color: var(--danger); } +.stat-card--pelatihan .stat-number { color: var(--success); } +.stat-label { font-size: 9px; color: var(--text-muted); font-weight: 500; text-transform: uppercase; letter-spacing: 0.6px; margin-top: 3px; } + +/* ───────────────────────────────────────────── + EKONOMI GRID (stat avg penghasilan & anggota) +───────────────────────────────────────────── */ +.ekon-grid { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; } + +.ekon-card { + background: var(--bg-card); border: 1px solid var(--border); + border-radius: var(--radius); padding: 10px 12px; + display: flex; justify-content: space-between; align-items: center; +} +.ekon-label { font-size: 11px; color: var(--text-muted); font-weight: 500; } +.ekon-value { font-size: 13px; font-weight: 700; font-family: var(--font); color: var(--text-primary); } + +/* ───────────────────────────────────────────── + SECTION TITLE +───────────────────────────────────────────── */ +.section-title { + font-size: 9px; font-weight: 600; color: var(--text-muted); + text-transform: uppercase; letter-spacing: 1px; + margin: 14px 0 8px; + display: flex; align-items: center; gap: 8px; +} +.section-title svg { width: 12px; height: 12px; stroke: var(--text-muted); fill: none; flex-shrink: 0; } +.section-title::after { content: ''; flex: 1; height: 1px; background: var(--border); } + +/* ───────────────────────────────────────────── + LEGEND +───────────────────────────────────────────── */ +.legend-group-label { + font-size: 9px; color: var(--text-hint); font-weight: 600; + text-transform: uppercase; letter-spacing: 0.8px; + margin-bottom: 5px; padding-left: 2px; +} +.legend-item { + display: flex; align-items: center; gap: 8px; + padding: 5px 6px; border-radius: var(--radius-sm); margin-bottom: 2px; + font-size: 12px; font-weight: 400; color: var(--text-secondary); + transition: background 0.15s; cursor: default; +} +.legend-item:hover { background: var(--bg-card); } +.legend-icon { + width: 22px; height: 22px; border-radius: 6px; + background: var(--bg-card); border: 1px solid var(--border); + display: flex; align-items: center; justify-content: center; flex-shrink: 0; +} +.legend-icon svg { width: 13px; height: 13px; stroke: var(--icon-tint); fill: none; } +.legend-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; } + +/* ───────────────────────────────────────────── + RADIUS CONTROL +───────────────────────────────────────────── */ +.radius-control { + background: var(--bg-card); border: 1px solid var(--border); + border-radius: var(--radius); padding: 12px; margin-bottom: 10px; +} +.radius-label { + font-size: 11px; font-weight: 500; color: var(--text-secondary); + margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center; +} +.radius-value { font-family: var(--font); font-size: 13px; font-weight: 600; color: var(--accent-light); } +.radius-slider { + width: 100%; -webkit-appearance: none; appearance: none; + height: 3px; border-radius: 2px; + background: linear-gradient(to right, var(--accent) 0%, var(--accent) 50%, var(--border-light) 50%); + outline: none; cursor: pointer; margin-bottom: 10px; +} +.radius-slider::-webkit-slider-thumb { + -webkit-appearance: none; width: 15px; height: 15px; border-radius: 50%; + background: var(--accent-light); border: 2px solid var(--bg-panel); + cursor: pointer; transition: transform 0.15s; box-shadow: var(--shadow-sm); +} +.radius-slider::-webkit-slider-thumb:hover { transform: scale(1.15); } +.radius-presets { display: flex; gap: 5px; } +.preset-btn { + flex: 1; padding: 5px; background: var(--bg-deep); + border: 1px solid var(--border); border-radius: var(--radius-sm); + color: var(--text-muted); font-size: 10px; font-weight: 600; + font-family: var(--font); cursor: pointer; transition: all 0.15s; letter-spacing: 0.2px; +} +.preset-btn:hover { border-color: var(--accent); color: var(--accent-light); } +.preset-btn.active { background: var(--accent); border-color: var(--accent); color: #fff; } + +/* ───────────────────────────────────────────── + SEARCH & DATA LIST +───────────────────────────────────────────── */ +.search-box { position: relative; margin-bottom: 8px; } +.search-box input { + width: 100%; padding: 8px 10px 8px 32px; + background: var(--bg-card); border: 1px solid var(--border); + border-radius: var(--radius-sm); color: var(--text-primary); + font-family: var(--font); font-size: 12px; outline: none; transition: border-color 0.2s; +} +.search-box input::placeholder { color: var(--text-muted); } +.search-box input:focus { border-color: var(--accent); } +.search-icon { position: absolute; left: 9px; top: 50%; transform: translateY(-50%); pointer-events: none; } +.search-icon svg { width: 14px; height: 14px; stroke: var(--text-muted); fill: none; } + +.data-list { display: flex; flex-direction: column; gap: 4px; } + +.data-item { + background: var(--bg-card); border: 1px solid var(--border); + border-radius: var(--radius); padding: 10px; + cursor: pointer; transition: all 0.18s; + display: flex; gap: 10px; align-items: flex-start; +} +.data-item:hover { border-color: var(--accent); background: var(--bg-hover); } +.item-icon { + width: 28px; height: 28px; border-radius: 7px; + background: var(--bg-deep); border: 1px solid var(--border); + display: flex; align-items: center; justify-content: center; flex-shrink: 0; +} +.item-icon svg { width: 14px; height: 14px; stroke: var(--icon-tint); fill: none; } +.item-info { flex: 1; min-width: 0; } +.item-name { font-size: 12px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 2px; color: var(--text-primary); } +.item-sub { font-size: 10px; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.item-badge { font-size: 9px; font-weight: 600; padding: 2px 7px; border-radius: 20px; flex-shrink: 0; align-self: flex-start; margin-top: 2px; letter-spacing: 0.3px; } + +/* ───────────────────────────────────────────── + BUTTONS +───────────────────────────────────────────── */ +.btn { + display: inline-flex; align-items: center; justify-content: center; gap: 6px; + padding: 8px 14px; border: none; border-radius: var(--radius-sm); + font-family: var(--font); font-size: 12px; font-weight: 600; + cursor: pointer; transition: all 0.18s; text-decoration: none; +} +.btn svg { width: 14px; height: 14px; stroke: currentColor; fill: none; } +.btn-primary { background: var(--accent); color: #fff; } +.btn-primary:hover { background: #4338ca; } +.btn-success { background: #059669; color: #fff; } +.btn-success:hover { background: #047857; } +.btn-danger { background: #dc2626; color: #fff; } +.btn-danger:hover { background: #b91c1c; } +.btn-ghost { background: var(--bg-card); color: var(--text-secondary); border: 1px solid var(--border); } +.btn-ghost:hover { border-color: var(--border-light); color: var(--text-primary); } +.btn-sm { padding: 5px 10px; font-size: 11px; } +.btn-xs { padding: 3px 8px; font-size: 10px; } +.btn-block { width: 100%; } + +/* ───────────────────────────────────────────── + MAP AREA +───────────────────────────────────────────── */ +#map-container { flex: 1; position: relative; overflow: hidden; } +#map { width: 100%; height: 100%; } + +/* ───────────────────────────────────────────── + TOOLBAR +───────────────────────────────────────────── */ +#map-toolbar { + position: absolute; bottom: 20px; right: 16px; + z-index: 1000; display: flex; flex-direction: column; gap: 6px; +} +.tool-btn { + width: 38px; height: 38px; border-radius: var(--radius-sm); + background: var(--bg-panel); border: 1px solid var(--border); + color: var(--text-muted); cursor: pointer; + display: flex; align-items: center; justify-content: center; + transition: all 0.18s; box-shadow: var(--shadow-sm); +} +.tool-btn svg { width: 16px; height: 16px; stroke: currentColor; fill: none; } +.tool-btn:hover { background: var(--bg-hover); color: var(--text-primary); border-color: var(--accent); } + +/* ───────────────────────────────────────────── + INFO PANEL +───────────────────────────────────────────── */ +#info-panel { + position: absolute; top: 16px; right: 16px; + width: 280px; background: var(--bg-panel); + border: 1px solid var(--border); border-radius: var(--radius-lg); + z-index: 1000; display: none; box-shadow: var(--shadow-lg); overflow: hidden; +} +#info-panel.show { display: block; animation: slideIn 0.22s ease; } + +@keyframes slideIn { + from { opacity: 0; transform: translateY(-8px) scale(0.98); } + to { opacity: 1; transform: translateY(0) scale(1); } +} + +.info-header { + padding: 13px 14px 11px; border-bottom: 1px solid var(--border); + display: flex; justify-content: space-between; align-items: center; + background: var(--bg-card); +} +.info-title { font-size: 13px; font-weight: 700; display: flex; align-items: center; gap: 8px; color: var(--text-primary); } +.info-title-icon { + width: 26px; height: 26px; border-radius: 6px; + background: var(--bg-panel); border: 1px solid var(--border); + display: flex; align-items: center; justify-content: center; +} +.info-title-icon svg { width: 14px; height: 14px; stroke: var(--icon-tint); fill: none; } +.info-close { + width: 24px; height: 24px; border-radius: 6px; + background: transparent; border: 1px solid transparent; + color: var(--text-muted); font-size: 14px; cursor: pointer; + display: flex; align-items: center; justify-content: center; transition: all 0.18s; +} +.info-close:hover { background: var(--danger-bg); color: var(--danger); } +.info-body { padding: 12px 14px; max-height: calc(100vh - 200px); overflow-y: auto; } +.info-body::-webkit-scrollbar { width: 3px; } +.info-body::-webkit-scrollbar-thumb { background: var(--border-light); border-radius: 2px; } + +.info-row { display: flex; gap: 8px; margin-bottom: 7px; font-size: 12px; align-items: flex-start; } +.info-key { color: var(--text-muted); font-weight: 500; min-width: 82px; flex-shrink: 0; padding-top: 1px; } +.info-val { color: var(--text-secondary); word-break: break-word; line-height: 1.5; } + +.address-box { + background: var(--bg-card); border: 1px solid var(--border); + border-radius: var(--radius-sm); padding: 8px 10px; + font-size: 11px; color: var(--text-muted); line-height: 1.5; + margin: 0 0 10px; display: flex; gap: 6px; align-items: flex-start; +} +.addr-icon svg { width: 12px; height: 12px; stroke: var(--text-muted); fill: none; flex-shrink: 0; margin-top: 1px; } + +.info-coords { + font-family: var(--mono); font-size: 10px; color: var(--text-muted); + text-align: center; padding: 6px; + background: var(--bg-card); border-radius: var(--radius-sm); + border: 1px solid var(--border); margin-top: 8px; +} + +.info-section-title { + font-size: 9px; font-weight: 600; color: var(--text-muted); + text-transform: uppercase; letter-spacing: 0.8px; + margin: 12px 0 6px; display: flex; align-items: center; gap: 6px; +} +.info-section-title svg { width: 11px; height: 11px; stroke: currentColor; fill: none; } + +/* Anggota list in info panel */ +.anggota-list { display: flex; flex-direction: column; gap: 4px; } +.anggota-item { display: flex; gap: 8px; align-items: flex-start; padding: 6px 8px; background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-sm); } +.anggota-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; margin-top: 4px; } +.anggota-info { flex: 1; min-width: 0; } +.anggota-name { font-size: 11px; font-weight: 600; color: var(--text-primary); } +.anggota-meta { font-size: 10px; color: var(--text-muted); line-height: 1.5; margin-top: 1px; } + +/* Rekomendasi box in info panel */ +.rek-box { + border-radius: var(--radius-sm); padding: 8px 10px; margin: 8px 0; + border-left: 3px solid; +} +.rek-box.rutin { background: var(--danger-bg); border-color: var(--danger); } +.rek-box.pelatihan { background: var(--success-bg); border-color: var(--success); } +.rek-box.keduanya { background: var(--warning-bg); border-color: var(--warning); } +.rek-box.tidak-ada { background: var(--bg-hover); border-color: var(--border-light); } +.rek-box-title { font-size: 10px; font-weight: 700; color: var(--text-primary); margin-bottom: 3px; } +.rek-box-desc { font-size: 10px; color: var(--text-muted); line-height: 1.5; } + +/* ───────────────────────────────────────────── + BADGES +───────────────────────────────────────────── */ +.badge { display: inline-block; padding: 2px 8px; border-radius: 20px; font-size: 10px; font-weight: 600; letter-spacing: 0.2px; } +.badge-sangat-miskin { background: var(--danger-bg); color: var(--danger-text); } +.badge-miskin { background: var(--info-bg); color: var(--info-text); } +.badge-rentan-miskin { background: var(--warning-bg); color: var(--warning-text); } + +.badge-belum_terima { background: var(--danger-bg); color: var(--danger-text); } +.badge-sudah_terima { background: var(--success-bg); color: var(--success-text); } +.badge-sedang_proses { background: var(--warning-bg); color: var(--warning-text); } + +/* STATUS_CONFIG cls — harus cocok dengan app.js */ +.badge-belum-terima { background: var(--danger-bg); color: var(--danger-text); } +.badge-sudah-terima { background: var(--success-bg); color: var(--success-text); } +.badge-sedang-proses { background: var(--warning-bg); color: var(--warning-text); } + +/* REK_CONFIG cls */ +.badge-bantuan-rutin { background: var(--danger-bg); color: var(--danger-text); } +.badge-bantuan-pelatihan { background: var(--success-bg); color: var(--success-text); } +.badge-keduanya { background: var(--warning-bg); color: var(--warning-text); } +.badge-tidak-ada { background: var(--bg-hover); color: var(--text-muted); } + +/* ───────────────────────────────────────────── + MODAL +───────────────────────────────────────────── */ +.modal-overlay { + position: fixed; inset: 0; background: rgba(0,0,0,0.6); + z-index: 9000; display: none; align-items: center; justify-content: center; + backdrop-filter: blur(4px); +} +.modal-overlay.show { display: flex; } +#modal-warga, #modal-ibadah { z-index: 9100; } + +.modal { + background: var(--bg-panel); border: 1px solid var(--border); + border-radius: var(--radius-xl); width: 480px; + max-width: 95vw; max-height: 90vh; overflow-y: auto; + box-shadow: var(--shadow-lg); animation: modalIn 0.22s ease; +} +@keyframes modalIn { + from { opacity: 0; transform: scale(0.96) translateY(-16px); } + to { opacity: 1; transform: scale(1) translateY(0); } +} +.modal::-webkit-scrollbar { width: 3px; } +.modal::-webkit-scrollbar-thumb { background: var(--border-light); border-radius: 2px; } + +.modal-header { + padding: 16px 18px; border-bottom: 1px solid var(--border); + display: flex; justify-content: space-between; align-items: center; + position: sticky; top: 0; background: var(--bg-panel); z-index: 1; +} +.modal-title { font-size: 14px; font-weight: 700; color: var(--text-primary); } +.modal-close { + width: 26px; height: 26px; border-radius: var(--radius-sm); + background: var(--bg-card); border: 1px solid var(--border); + color: var(--text-muted); font-size: 13px; cursor: pointer; + display: flex; align-items: center; justify-content: center; transition: all 0.18s; +} +.modal-close:hover { background: var(--danger-bg); color: var(--danger); border-color: transparent; } + +/* Modal sub-tabs */ +.modal-tabs { + display: flex; border-bottom: 1px solid var(--border); + background: var(--bg-card); flex-shrink: 0; +} +.modal-tab-btn { + flex: 1; padding: 9px 6px; + background: transparent; border: none; + border-bottom: 2px solid transparent; + color: var(--text-muted); font-family: var(--font); + font-size: 11px; font-weight: 600; cursor: pointer; + transition: all 0.2s; margin-bottom: -1px; +} +.modal-tab-btn:hover { color: var(--text-secondary); } +.modal-tab-btn.active { color: var(--accent-light); border-bottom-color: var(--accent-light); } +.modal-tab-panel { display: none; } +.modal-tab-panel.active { display: block; } + +.modal-body { padding: 18px; } +.modal-footer { + padding: 12px 18px; border-top: 1px solid var(--border); + display: flex; gap: 8px; justify-content: flex-end; + position: sticky; bottom: 0; background: var(--bg-panel); z-index: 1; +} + +/* ───────────────────────────────────────────── + FORM +───────────────────────────────────────────── */ +.form-group { margin-bottom: 13px; } +.form-label { display: block; font-size: 10px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.6px; margin-bottom: 5px; } +.form-label span { color: var(--danger); } + +.form-control { + width: 100%; padding: 8px 11px; + background: var(--bg-card); border: 1px solid var(--border); + border-radius: var(--radius-sm); color: var(--text-primary); + font-family: var(--font); font-size: 13px; outline: none; + transition: border-color 0.18s, box-shadow 0.18s; +} +.form-control::placeholder { color: var(--text-muted); } +.form-control:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); } +select.form-control { + cursor: pointer; -webkit-appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + background-repeat: no-repeat; background-position: right 10px center; padding-right: 32px; +} +textarea.form-control { resize: vertical; min-height: 68px; } +.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } +.form-hint { font-size: 10px; color: var(--text-muted); margin-top: 4px; } + +.coord-display { + font-family: var(--mono); font-size: 11px; color: var(--accent-light); + background: var(--bg-card); border: 1px solid var(--border); + border-radius: var(--radius-sm); padding: 8px 10px; +} + +/* Kondisi rumah preview */ +.kondisi-rumah-preview { + background: var(--bg-card); border: 1px solid var(--border); + border-radius: var(--radius); padding: 12px; margin-top: 4px; +} +.kpr-title { font-size: 10px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.6px; margin-bottom: 10px; } +.kpr-grid { display: flex; gap: 6px; } +.kpr-item { + flex: 1; display: flex; flex-direction: column; align-items: center; gap: 5px; + background: var(--bg-deep); border: 1px solid var(--border); + border-radius: var(--radius-sm); padding: 8px 6px; + font-size: 10px; color: var(--text-secondary); text-align: center; +} +.kpr-item svg { color: var(--icon-tint); } + +/* Anggota form items */ +.anggota-form-item { + background: var(--bg-card); border: 1px solid var(--border); + border-radius: var(--radius); overflow: hidden; +} +.anggota-form-header { + display: flex; justify-content: space-between; align-items: center; + padding: 8px 12px; background: var(--bg-hover); + border-bottom: 1px solid var(--border); +} +.anggota-form-title { font-size: 11px; font-weight: 600; color: var(--text-secondary); } +.anggota-form-body { + padding: 12px; display: grid; grid-template-columns: 1fr 1fr; gap: 8px; +} +.anggota-form-body .form-group { margin-bottom: 0; } + +/* Rekomendasi preview box */ +.rek-preview { + background: var(--bg-card); border: 1px solid var(--border); + border-radius: var(--radius); padding: 10px 12px; +} + +/* ───────────────────────────────────────────── + TOAST +───────────────────────────────────────────── */ +#toast-container { position: fixed; bottom: 80px; right: 20px; z-index: 9999; display: flex; flex-direction: column; gap: 6px; } +.toast { + background: var(--bg-panel); border: 1px solid var(--border); + border-radius: var(--radius); padding: 10px 14px; + display: flex; align-items: center; gap: 10px; min-width: 240px; + box-shadow: var(--shadow-lg); animation: toastIn 0.25s ease; + font-size: 12px; color: var(--text-secondary); +} +@keyframes toastIn { from { opacity: 0; transform: translateX(16px); } to { opacity: 1; transform: translateX(0); } } +.toast.success { border-left: 3px solid var(--success); } +.toast.error { border-left: 3px solid var(--danger); } +.toast.info { border-left: 3px solid var(--accent-light); } +.toast-icon svg { width: 15px; height: 15px; stroke: currentColor; fill: none; } +.toast.success .toast-icon { color: var(--success); } +.toast.error .toast-icon { color: var(--danger); } +.toast.info .toast-icon { color: var(--accent-light); } + +/* ───────────────────────────────────────────── + RADIUS INFO BAR +───────────────────────────────────────────── */ +#radius-info { + position: absolute; bottom: 72px; left: 50%; transform: translateX(-50%); + z-index: 1000; background: var(--bg-panel); + border: 1px solid var(--border-light); border-radius: var(--radius); + padding: 9px 14px; display: none; gap: 10px; align-items: center; + box-shadow: var(--shadow); font-size: 12px; white-space: nowrap; +} +#radius-info.show { display: flex; } +.ri-label { color: var(--text-muted); } +.ri-val { color: var(--accent-light); font-weight: 600; font-family: var(--font); } +.ri-count { background: var(--danger-bg); color: var(--danger-text); padding: 2px 8px; border-radius: 20px; font-weight: 700; font-size: 11px; } +.ri-close { cursor: pointer; color: var(--text-muted); display: flex; align-items: center; justify-content: center; transition: color 0.18s; } +.ri-close:hover { color: var(--danger); } +.ri-close svg { width: 12px; height: 12px; stroke: currentColor; fill: none; } + +/* ───────────────────────────────────────────── + MODE INDICATOR +───────────────────────────────────────────── */ +#mode-indicator { + position: absolute; top: 16px; left: 16px; z-index: 1000; + background: var(--bg-panel); border: 1px solid var(--border); + border-radius: var(--radius); padding: 8px 12px; + display: none; align-items: center; gap: 8px; + font-size: 12px; font-weight: 500; color: var(--text-secondary); + box-shadow: var(--shadow-sm); +} +#mode-indicator.show { display: flex; } +.mode-dot { width: 7px; height: 7px; border-radius: 50%; animation: pulse 1.2s infinite; } +.mode-dot.ibadah { background: var(--success); } +.mode-dot.warga { background: var(--accent-light); } +@keyframes pulse { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.5); opacity: 0.5; } } + +/* ───────────────────────────────────────────── + LEAFLET OVERRIDES +───────────────────────────────────────────── */ +.leaflet-popup-content-wrapper { + background: var(--bg-panel) !important; border: 1px solid var(--border) !important; + border-radius: var(--radius) !important; box-shadow: var(--shadow-lg) !important; color: var(--text-primary) !important; +} +.leaflet-popup-content { color: var(--text-primary) !important; font-family: var(--font) !important; } +.leaflet-popup-tip { background: var(--bg-panel) !important; } +.leaflet-popup-close-button { color: var(--text-muted) !important; } +.leaflet-control-zoom a { + background: var(--bg-panel) !important; color: var(--text-secondary) !important; + border-color: var(--border) !important; font-family: var(--font) !important; +} +.leaflet-control-zoom a:hover { background: var(--bg-hover) !important; } +.leaflet-control-attribution { display: none !important; } + +/* ───────────────────────────────────────────── + SCROLLBAR +───────────────────────────────────────────── */ +::-webkit-scrollbar { width: 5px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; } + +/* ───────────────────────────────────────────── + LOADING / EMPTY +───────────────────────────────────────────── */ +.loading { display: flex; align-items: center; justify-content: center; padding: 28px; color: var(--text-muted); font-size: 12px; gap: 8px; } +.spinner { width: 16px; height: 16px; border: 2px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: spin 0.6s linear infinite; } +@keyframes spin { to { transform: rotate(360deg); } } +.empty-state { text-align: center; padding: 24px 16px; color: var(--text-muted); font-size: 12px; } +.empty-state svg { width: 28px; height: 28px; stroke: var(--text-hint); fill: none; margin: 0 auto 8px; display: block; } + +/* ───────────────────────────────────────────── + HOW TO USE BOX +───────────────────────────────────────────── */ +.howto-box { + font-size: 11px; color: var(--text-muted); line-height: 1.8; + background: var(--bg-card); border: 1px solid var(--border); + border-radius: var(--radius-sm); padding: 10px 12px; +} +.howto-box b { color: var(--text-secondary); font-weight: 600; } + +/* ───────────────────────────────────────────── + LAPORAN WARGA +───────────────────────────────────────────── */ +/* Tab badge counter */ +.tab-badge { + display: inline-flex; align-items: center; justify-content: center; + min-width: 16px; height: 16px; padding: 0 4px; + background: var(--danger); color: #fff; + border-radius: 10px; font-size: 9px; font-weight: 700; + margin-left: 3px; line-height: 1; +} + +/* Laporan card */ +.laporan-card { + background: var(--bg-card); border: 1px solid var(--border); + border-radius: var(--radius); padding: 11px 12px; + margin-bottom: 6px; transition: border-color 0.18s; + border-left: 3px solid var(--border); +} +.laporan-card:hover { border-color: var(--accent); } +.laporan-card.prioritas-tinggi { border-left-color: var(--danger); } +.laporan-card.prioritas-sedang { border-left-color: var(--warning); } +.laporan-card.prioritas-rendah { border-left-color: var(--success); } + +.laporan-card-header { + display: flex; justify-content: space-between; align-items: flex-start; + gap: 8px; margin-bottom: 6px; +} +.laporan-card-nama { + font-size: 12px; font-weight: 600; color: var(--text-primary); + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} +.laporan-card-meta { + font-size: 10px; color: var(--text-muted); margin-top: 2px; +} +.laporan-card-jenis { + font-size: 11px; color: var(--text-secondary); + margin-bottom: 5px; font-weight: 500; +} +.laporan-card-desc { + font-size: 11px; color: var(--text-muted); line-height: 1.5; + background: var(--bg-deep); border-radius: var(--radius-sm); + padding: 6px 8px; margin-bottom: 6px; + border-left: 2px solid var(--border-light); + display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; + overflow: hidden; +} +.laporan-card-footer { + display: flex; justify-content: space-between; align-items: center; + gap: 6px; flex-wrap: wrap; +} +.laporan-card-petugas { + font-size: 10px; color: var(--success); font-weight: 600; + display: flex; align-items: center; gap: 4px; +} +.laporan-catatan-admin { + font-size: 10px; color: var(--text-muted); + background: var(--info-bg); border: 1px solid rgba(59,130,246,0.15); + border-radius: var(--radius-sm); padding: 5px 8px; + margin-top: 5px; line-height: 1.5; +} + +/* Status badges laporan */ +.badge-laporan-pending { background: rgba(245,158,11,0.12); color: #92400e; } +.badge-laporan-ditinjau { background: rgba(59,130,246,0.12); color: #1e40af; } +.badge-laporan-diproses { background: rgba(139,92,246,0.12); color: #5b21b6; } +.badge-laporan-selesai { background: rgba(16,185,129,0.12); color: #065f46; } +.badge-laporan-ditolak { background: rgba(239,68,68,0.12); color: #991b1b; } + +/* Prioritas badges */ +.badge-prioritas-tinggi { background: rgba(239,68,68,0.12); color: var(--danger-text); } +.badge-prioritas-sedang { background: rgba(245,158,11,0.12); color: var(--warning-text); } +.badge-prioritas-rendah { background: rgba(16,185,129,0.12); color: var(--success-text); } + +/* Admin action area in laporan card */ +.laporan-admin-actions { + display: flex; gap: 5px; flex-wrap: wrap; margin-top: 8px; + padding-top: 8px; border-top: 1px dashed var(--border); +} +.laporan-admin-actions select { + flex: 1; min-width: 110px; font-size: 10px; padding: 4px 8px; + border-radius: var(--radius-sm); border: 1px solid var(--border); + background: var(--bg-deep); color: var(--text-secondary); + font-family: var(--font); cursor: pointer; +} +.laporan-admin-actions input[type="text"] { + flex: 1.5; min-width: 100px; font-size: 10px; padding: 4px 8px; + border-radius: var(--radius-sm); border: 1px solid var(--border); + background: var(--bg-deep); color: var(--text-secondary); + font-family: var(--font); +} + +/* Laporan filter tabs */ +.laporan-filter-tabs { + display: flex; gap: 4px; flex-wrap: wrap; + margin-bottom: 10px; +} +.laporan-filter-tab { + padding: 4px 10px; border-radius: 20px; font-size: 10px; font-weight: 600; + border: 1px solid var(--border); background: var(--bg-card); + color: var(--text-muted); cursor: pointer; transition: all 0.18s; + font-family: var(--font); +} +.laporan-filter-tab:hover { border-color: var(--accent); color: var(--accent-light); } +.laporan-filter-tab.active { background: var(--accent); border-color: var(--accent); color: #fff; } + +/* Laporkan button in info panel */ +.btn-laporkan { + display: flex; align-items: center; gap: 6px; + width: 100%; margin-top: 8px; padding: 7px 12px; + background: rgba(239,68,68,0.08); border: 1px solid rgba(239,68,68,0.25); + border-radius: var(--radius-sm); color: var(--danger); + font-family: var(--font); font-size: 11px; font-weight: 600; + cursor: pointer; transition: all 0.18s; +} +.btn-laporkan:hover { + background: rgba(239,68,68,0.15); border-color: var(--danger); +} +.btn-laporkan svg { width: 13px; height: 13px; stroke: currentColor; fill: none; flex-shrink: 0; } + +/* ───────────────────────────────────────────── + MOBILE STYLES & HAMBURGER MENU +───────────────────────────────────────────── */ +.mobile-menu-btn { + display: none; + position: absolute; + top: 16px; + left: 16px; + z-index: 2000; + width: 42px; + height: 42px; + border-radius: var(--radius-sm); + background: var(--bg-panel); + border: 1px solid var(--border); + color: var(--text-primary); + cursor: pointer; + align-items: center; + justify-content: center; + box-shadow: var(--shadow-sm); + transition: all 0.2s; +} +.mobile-menu-btn svg { width: 22px; height: 22px; } +.mobile-menu-btn:hover { background: var(--bg-hover); color: var(--accent); border-color: var(--accent); } + +/* Overlay for mobile sidebar */ +.sidebar-overlay { + display: none; + position: fixed; + inset: 0; + background: rgba(0,0,0,0.4); + z-index: 2500; + backdrop-filter: blur(2px); + opacity: 0; + transition: opacity 0.3s; +} +.sidebar-overlay.show { + display: block; + opacity: 1; +} + +@media (max-width: 768px) { + .mobile-menu-btn { + display: flex; + } + + #sidebar { + position: absolute; + top: 0; + left: -100%; + height: 100%; + z-index: 3000; + transition: left 0.3s ease; + box-shadow: var(--shadow-lg); + width: 85vw; /* Adjust width for mobile */ + max-width: 340px; + } + + #sidebar.show { + left: 0; + } + + #mode-indicator { + top: 16px; + left: 70px; /* Make space for hamburger button */ + } + + .radius-control { + padding: 10px; + } +} diff --git a/04/assets/js/app.js b/04/assets/js/app.js new file mode 100644 index 0000000..44f4f07 --- /dev/null +++ b/04/assets/js/app.js @@ -0,0 +1,2061 @@ +// assets/js/app.js — WebGIS Bansos (Versi Lengkap — Fixed) + +const App = (() => { + /* ══════════════════════════════════════════════════ + STATE + ══════════════════════════════════════════════════ */ + let map; + let activeRadiusCircle = null, activeRadiusIbadahId = null; + let radiusMeters = 500; + let addMode = null, tempMarker = null; + let ibadahMarkers = {}, wargaMarkers = {}; + let ibadahData = [], wargaData = []; + let ibadahLayerGroup, wargaLayerGroup; + let anggotaRows = []; + let currentUser = null; + + async function checkAuthStatus() { + try { + const res = await fetch('api/auth.php?action=status'); + const json = await res.json(); + if (json.success && json.logged_in) { + currentUser = json.user; + updateUserUI(); + + // Auto-switch to overview for admin/petugas if they are on default tab + const activeTab = document.querySelector('.tab-btn.active')?.dataset.tab; + if (['admin', 'petugas'].includes(currentUser.role) && activeTab === 'ibadah') { + const overviewBtn = document.querySelector('.tab-btn[data-tab="overview"]'); + if (overviewBtn) overviewBtn.click(); + } + } else { + currentUser = null; + updateUserUI(); + } + applyRoleUI(); + + if (window.FiturBaru && FiturBaru.Kebutuhan && typeof FiturBaru.Kebutuhan.load === 'function') { + FiturBaru.Kebutuhan.load(); + } + + if (window.FiturBaru && FiturBaru.Laporan && typeof FiturBaru.Laporan.loadList === 'function' && currentUser) { + FiturBaru.Laporan.loadList(); + } + + // Force refresh Kebutuhan tab if it's currently showing when auth status changes + if (window.FiturBaru && FiturBaru.Kebutuhan && typeof FiturBaru.Kebutuhan.refreshIfActive === 'function') { + FiturBaru.Kebutuhan.refreshIfActive(); + } + } catch (e) { + console.error('Error checking auth status:', e); + currentUser = null; + updateUserUI(); + applyRoleUI(); + } + } + + function updateUserUI() { + const avatar = document.getElementById('user-avatar'); + const nameEl = document.getElementById('user-display-name'); + const roleEl = document.getElementById('user-display-role'); + const btnToggle = document.getElementById('btn-auth-toggle'); + + if (currentUser) { + if (avatar) { + avatar.textContent = currentUser.username.charAt(0).toUpperCase(); + avatar.className = 'user-avatar'; + } + if (nameEl) nameEl.textContent = currentUser.username; + if (roleEl) { + roleEl.textContent = currentUser.role.toUpperCase(); + roleEl.className = 'user-role-badge badge-' + currentUser.role; + } + if (btnToggle) btnToggle.textContent = 'Logout'; + } else { + if (avatar) { + avatar.textContent = 'G'; + avatar.className = 'user-avatar avatar-guest'; + } + if (nameEl) nameEl.textContent = 'Guest Mode'; + if (roleEl) { + roleEl.textContent = 'Pengunjung'; + roleEl.className = 'user-role-badge badge-guest'; + } + if (btnToggle) btnToggle.textContent = 'Login'; + } + } + + function applyRoleUI() { + const role = currentUser ? currentUser.role : 'guest'; + + // Tab buttons + const btnOverview = document.querySelector('.tab-btn[data-tab="overview"]'); + const btnIbadah = document.getElementById('tab-btn-ibadah'); + const btnWarga = document.getElementById('tab-btn-warga'); + const btnKebutuhan = document.getElementById('tab-btn-kebutuhan'); + const btnLaporan = document.getElementById('tab-btn-laporan'); + + if (btnOverview) btnOverview.style.display = (role !== 'guest' && role !== 'kontributor') ? '' : 'none'; + if (btnIbadah) btnIbadah.style.display = ['admin', 'petugas', 'koordinator', 'kontributor', 'guest'].includes(role) ? '' : 'none'; + if (btnWarga) btnWarga.style.display = ['admin', 'petugas', 'koordinator'].includes(role) ? '' : 'none'; + if (btnLaporan) btnLaporan.style.display = role !== 'guest' ? '' : 'none'; + + // Kebutuhan tab: explicit rule so changes to guest handling don't remove kontributor access + const kebutuhanAllowed = ['admin', 'koordinator', 'kontributor', 'guest']; + if (btnKebutuhan) btnKebutuhan.style.display = kebutuhanAllowed.includes(role) ? '' : 'none'; + + // If currently selected tab is hidden, switch to a visible one + const activeTabBtn = document.querySelector('.tab-btn.active'); + if (activeTabBtn && activeTabBtn.style.display === 'none') { + const fallbackTab = (role === 'guest' || role === 'kontributor') ? 'ibadah' : 'overview'; + const fallbackBtn = document.querySelector(`.tab-btn[data-tab="${fallbackTab}"]`); + if (fallbackBtn) fallbackBtn.click(); + } + + // Add buttons + const btnAddIbadah = document.getElementById('btn-add-ibadah'); + const btnAddWarga = document.getElementById('btn-add-warga'); + + if (btnAddIbadah) btnAddIbadah.style.display = role === 'admin' ? '' : 'none'; + if (btnAddWarga) btnAddWarga.style.display = ['admin', 'koordinator'].includes(role) ? '' : 'none'; + + // Advanced Features inside Overview + const advancedCards = document.querySelectorAll('.ekon-card'); + advancedCards.forEach(card => { + const onclickAttr = card.getAttribute('onclick') || ''; + if (onclickAttr.includes('Redistribusi')) { + card.style.display = role === 'admin' ? 'flex' : 'none'; + } else if (onclickAttr.includes('Verifikasi')) { + card.style.display = ['admin', 'petugas'].includes(role) ? 'flex' : 'none'; + } else if (onclickAttr.includes('Kemandirian')) { + card.style.display = ['admin', 'petugas'].includes(role) ? 'flex' : 'none'; + } + }); + + // Hide admin-only Overview sections for koordinator & petugas (Ekonomi & Kependudukan, Kontrol Radius) + if (['koordinator', 'petugas'].includes(role)) { + document.querySelectorAll('.section-title').forEach(el => { + const txt = (el.textContent || '').trim(); + if (txt.includes('Ekonomi & Kependudukan') || txt.includes('Kontrol Radius')) { + el.style.display = 'none'; + const next = el.nextElementSibling; + if (next) next.style.display = 'none'; + } + }); + } else { + // ensure visibility for non-koordinator/petugas roles + document.querySelectorAll('.section-title').forEach(el => { + const txt = (el.textContent || '').trim(); + if (txt.includes('Ekonomi & Kependudukan') || txt.includes('Kontrol Radius')) { + el.style.display = ''; + const next = el.nextElementSibling; + if (next) next.style.display = ''; + } + }); + } + + // Hide Verifikasi Konfigurasi for non-admins + const verifTabKonfig = document.querySelector('[data-fbtab="konfig"]'); + if (verifTabKonfig) { + verifTabKonfig.style.display = role === 'admin' ? '' : 'none'; + if (role !== 'admin' && verifTabKonfig.classList.contains('active')) { + const verifTabList = document.querySelector('[data-fbtab="list"]'); + if (verifTabList) window.switchFbTab(verifTabList, 'verifikasi'); + } + } + } + + function showAuthModal(intendedAction) { + sessionStorage.setItem('intendedAction', JSON.stringify(intendedAction)); + document.getElementById('modal-login').classList.add('show'); + } + + async function handleRegister() { + const username = document.getElementById('register-username').value.trim(); + const email = document.getElementById('register-email').value.trim(); + const password = document.getElementById('register-password').value.trim(); + const phone = document.getElementById('register-phone')?.value.trim() || ''; + const role = document.getElementById('register-role').value; + const rumahIbadahSelect = document.getElementById('register-rumah-ibadah'); + const rumahIbadahId = rumahIbadahSelect && rumahIbadahSelect.value ? parseInt(rumahIbadahSelect.value) : null; + const errorEl = document.getElementById('register-error-msg'); + + if (!username || !email || !password || !phone || !role) { + if (errorEl) { errorEl.textContent = 'Semua field wajib diisi.'; errorEl.style.display = 'block'; } + return; + } + if (password.length < 6) { + if (errorEl) { errorEl.textContent = 'Password minimal 6 karakter.'; errorEl.style.display = 'block'; } + return; + } + + const payload = { username, email, password, no_telp: phone, role }; + if (role === 'koordinator' && rumahIbadahId) payload.rumah_ibadah_id = rumahIbadahId; + + try { + const res = await fetch('api/auth.php?action=register', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload) + }); + const json = await res.json(); + if (json.success) { + if (errorEl) errorEl.style.display = 'none'; + showToast(json.message, 'success'); + // Auto login after registration + document.getElementById('login-username').value = username; + document.getElementById('login-password').value = password; + await handleLogin(); + } else { + if (errorEl) { errorEl.textContent = json.message || 'Registrasi gagal.'; errorEl.style.display = 'block'; } + } + } catch (e) { + if (errorEl) { errorEl.textContent = 'Kesalahan jaringan.'; errorEl.style.display = 'block'; } + } + } + + // Populate rumah ibadah dropdown for koordinator registration + async function loadRumahIbadahOptions() { + const select = document.getElementById('register-rumah-ibadah'); + if (!select) return; + try { + const res = await fetch('api/rumah_ibadah.php'); + const json = await res.json(); + if (!json.success) return; + const data = json.data || []; + // Clear existing options except placeholder + select.innerHTML = ''; + data.forEach(item => { + const opt = document.createElement('option'); + opt.value = item.id; + opt.textContent = item.nama; + select.appendChild(opt); + }); + } catch (e) { console.error('Failed load rumah ibadah options', e); } + } + + // Toggle register/login forms + function toggleToRegister() { + document.getElementById('login-form-container').style.display = 'none'; + document.getElementById('register-form-container').style.display = 'block'; + document.getElementById('btn-login-submit').style.display = 'none'; + document.getElementById('btn-register-submit').style.display = 'inline-block'; + loadRumahIbadahOptions(); + } + function toggleToLogin() { + document.getElementById('login-form-container').style.display = 'block'; + document.getElementById('register-form-container').style.display = 'none'; + document.getElementById('btn-login-submit').style.display = 'inline-block'; + document.getElementById('btn-register-submit').style.display = 'none'; + } + + // Show/hide rumah ibadah selector based on role + function handleRoleChange() { + const role = document.getElementById('register-role').value; + const group = document.getElementById('register-rumah-ibadah-group'); + if (group) group.style.display = role === 'koordinator' ? 'block' : 'none'; + } + + // Event listeners registration + document.addEventListener('DOMContentLoaded', () => { + const linkReg = document.getElementById('link-show-register'); + if (linkReg) linkReg.addEventListener('click', e => { e.preventDefault(); toggleToRegister(); }); + const linkLogin = document.getElementById('link-show-login'); + if (linkLogin) linkLogin.addEventListener('click', e => { e.preventDefault(); toggleToLogin(); }); + const roleSelect = document.getElementById('register-role'); + if (roleSelect) roleSelect.addEventListener('change', handleRoleChange); + const btnReg = document.getElementById('btn-register-submit'); + if (btnReg) btnReg.addEventListener('click', e => { e.preventDefault(); handleRegister(); }); + }); + + function checkAuth(intendedAction, callback) { + if (currentUser) { + callback(); + } else { + showAuthModal(intendedAction); + } + } + + async function handleLogin() { + const userVal = document.getElementById('login-username').value.trim(); + const passVal = document.getElementById('login-password').value.trim(); + const errorEl = document.getElementById('login-error-msg'); + + if (!userVal || !passVal) { + if (errorEl) { + errorEl.textContent = 'Username dan password wajib diisi!'; + errorEl.style.display = 'block'; + } + return; + } + + try { + const res = await fetch('api/auth.php?action=login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username: userVal, password: passVal }) + }); + const json = await res.json(); + if (json.success) { + if (errorEl) errorEl.style.display = 'none'; + showToast(json.message, 'success'); + document.getElementById('modal-login').classList.remove('show'); + + document.getElementById('login-username').value = ''; + document.getElementById('login-password').value = ''; + + await checkAuthStatus(); + await loadAllData(); + + const intended = sessionStorage.getItem('intendedAction'); + if (intended) { + sessionStorage.removeItem('intendedAction'); + const action = JSON.parse(intended); + executeIntendedAction(action); + } + } else { + if (errorEl) { + errorEl.textContent = json.message || 'Login gagal!'; + errorEl.style.display = 'block'; + } + } + } catch (e) { + if (errorEl) { + errorEl.textContent = 'Terjadi kesalahan server saat login.'; + errorEl.style.display = 'block'; + } + } + } + + async function handleLogout() { + // Show custom logout confirmation modal (browser confirm() often gets blocked) + document.getElementById('modal-logout-confirm')?.classList.add('show'); + } + + async function doLogout() { + document.getElementById('modal-logout-confirm')?.classList.remove('show'); + try { + const res = await fetch('api/auth.php?action=logout', { method: 'POST' }); + const json = await res.json(); + if (json.success) { + showToast(json.message, 'info'); + currentUser = null; + updateUserUI(); + applyRoleUI(); + clearRadius(); // Bersihkan radius dan data highlight saat logout + await loadAllData(); + if (window.FiturBaru && FiturBaru.Kebutuhan && typeof FiturBaru.Kebutuhan.load === 'function') { + FiturBaru.Kebutuhan.load(); + } + // Force refresh Kebutuhan tab if it's currently showing + if (window.FiturBaru && FiturBaru.Kebutuhan && typeof FiturBaru.Kebutuhan.refreshIfActive === 'function') { + FiturBaru.Kebutuhan.refreshIfActive(); + } + // Switch to ibadah tab for guest + const overviewBtn = document.querySelector('.tab-btn[data-tab="overview"]'); + if (overviewBtn) overviewBtn.style.display = 'none'; + const wargaBtn = document.getElementById('tab-btn-warga'); + if (wargaBtn) wargaBtn.style.display = 'none'; + const ibadahBtn = document.querySelector('.tab-btn[data-tab="ibadah"]'); + if (ibadahBtn) ibadahBtn.click(); + } + } catch (e) { + showToast('Gagal logout', 'error'); + } + } + + function executeIntendedAction(action) { + if (!action) return; + if (action.type === 'add_ibadah') { + setAddMode('ibadah'); + showToast('Klik peta untuk menentukan lokasi Rumah Ibadah', 'info'); + } else if (action.type === 'add_warga') { + setAddMode('warga'); + showToast('Klik peta untuk menentukan lokasi Rumah Warga', 'info'); + } else if (action.type === 'edit_ibadah') { + editIbadah(action.id); + } else if (action.type === 'edit_warga') { + editWarga(action.id); + } else if (action.type === 'delete_ibadah') { + deleteIbadah(action.id); + } else if (action.type === 'delete_warga') { + deleteWarga(action.id); + } else if (action.type === 'donate') { + if (window.FiturBaru && FiturBaru.Kebutuhan && typeof FiturBaru.Kebutuhan.openDonationModal === 'function') { + FiturBaru.Kebutuhan.openDonationModal(action.kebutuhan_id, action.sisa, action.jenis, action.ibadah_nama); + } + } + } + + /* ══════════════════════════════════════════════════ + SVG LIBRARY + ══════════════════════════════════════════════════ */ + const SVG = { + mosque: ``, + church: ``, + temple: ``, + star: ``, + wheel: ``, + building: ``, + home: ``, + users: ``, + pin: ``, + check: ``, + x: ``, + info: ``, + edit: ``, + trash: ``, + gift: ``, + }; + + const JENIS_CONFIG = { + masjid: { svgKey: 'mosque', label: 'Masjid' }, + gereja: { svgKey: 'church', label: 'Gereja' }, + klenteng: { svgKey: 'temple', label: 'Klenteng' }, + pura: { svgKey: 'star', label: 'Pura' }, + vihara: { svgKey: 'wheel', label: 'Vihara' }, + lainnya: { svgKey: 'building', label: 'Lainnya' }, + }; + + const KATEGORI_CONFIG = { + sangat_miskin: { label: 'Sangat Kurang Mampu', color: '#f87171', badgeCls: 'badge-sangat-miskin' }, + miskin: { label: 'Kurang Mampu', color: '#6366f1', badgeCls: 'badge-miskin' }, + rentan_miskin: { label: 'Rentan Kurang Mampu', color: '#fbbf24', badgeCls: 'badge-rentan-miskin' }, + }; + + const STATUS_CONFIG = { + belum_terima: { label: 'Belum Terima', cls: 'badge-belum-terima' }, + sudah_terima: { label: 'Sudah Terima', cls: 'badge-sudah-terima' }, + sedang_proses: { label: 'Sedang Proses', cls: 'badge-sedang-proses' }, + }; + + const REK_CONFIG = { + bantuan_rutin: { label: 'Bantuan Rutin', cls: 'badge-bantuan-rutin', boxCls: 'rutin', desc: 'Keluarga ini memerlukan bantuan sosial rutin (sembako, tunai, dll) karena tidak memiliki anggota produktif yang mampu bekerja.' }, + bantuan_pelatihan: { label: 'Bantuan Pelatihan', cls: 'badge-bantuan-pelatihan', boxCls: 'pelatihan', desc: 'Terdapat anggota keluarga usia produktif yang belum bekerja tetap. Direkomendasikan pelatihan keterampilan atau program pemberdayaan.' }, + keduanya: { label: 'Rutin + Pelatihan', cls: 'badge-keduanya', boxCls: 'keduanya', desc: 'Keluarga ini memerlukan bantuan rutin sekaligus ada potensi pemberdayaan melalui pelatihan kerja.' }, + tidak_ada: { label: 'Tidak Diperlukan', cls: 'badge-tidak-ada', boxCls: 'tidak-ada', desc: 'Kondisi keluarga sudah cukup mandiri. Tidak ada rekomendasi bantuan khusus saat ini.' }, + }; + + /* ══════════════════════════════════════════════════ + HELPER FUNCTIONS + ══════════════════════════════════════════════════ */ + function panToPoint(lat, lng, minZoom = 15) { + const cur = map.getZoom(); + const z = Math.max(cur, minZoom); + if (cur === z) map.panTo([lat, lng], { animate: true, duration: 0.5 }); + else map.flyTo([lat, lng], z, { duration: 0.6 }); + } + + function haversine(lat1, lng1, lat2, lng2) { + const R = 6371000; + const dLat = (lat2 - lat1) * Math.PI / 180; + const dLng = (lng2 - lng1) * Math.PI / 180; + const a = Math.sin(dLat / 2) ** 2 + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLng / 2) ** 2; + return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + } + + function hitungUsia(tglLahir) { + if (!tglLahir) return null; + const lahir = new Date(tglLahir); + const today = new Date(); + let usia = today.getFullYear() - lahir.getFullYear(); + const m = today.getMonth() - lahir.getMonth(); + if (m < 0 || (m === 0 && today.getDate() < lahir.getDate())) usia--; + return usia; + } + + function rekomendasiLocal(anggotaList, penghasilan, statusPekerjaan) { + let adaProd = false, adaProdBisa = false, semuaProdTakBisa = true; + anggotaList.forEach(a => { + const usia = hitungUsia(a.tanggal_lahir); + if (usia !== null && usia >= 18 && usia <= 55) { + adaProd = true; + const bisaKerja = parseInt(a.dapat_bekerja) === 1 && ['sehat', 'sakit_ringan'].includes(a.kondisi_kesehatan); + if (bisaKerja) semuaProdTakBisa = false; + if (bisaKerja && a.status_bekerja !== 'bekerja') adaProdBisa = true; + } + }); + const perluRutin = !adaProd || (adaProd && semuaProdTakBisa) || parseFloat(penghasilan) < 500000 || statusPekerjaan === 'tidak_bekerja'; + const perluPelatihan = adaProdBisa; + if (perluRutin && perluPelatihan) return 'keduanya'; + if (perluRutin) return 'bantuan_rutin'; + if (perluPelatihan) return 'bantuan_pelatihan'; + return 'tidak_ada'; + } + + function formatRupiah(n) { + return 'Rp ' + parseInt(n || 0).toLocaleString('id-ID'); + } + + function svgIcon(key, w = 14, h = 14) { + return SVG[key]?.replace(' { const el = document.getElementById(id); if (el) el.textContent = val; }; + + set('stat-ibadah', d.total_ibadah || 0); + set('stat-warga', d.total_warga || 0); + set('stat-jiwa', d.total_jiwa || 0); + set('stat-bansos', d.status_bansos?.sudah_terima || 0); + + const rutin = (d.rekomendasi_bantuan?.bantuan_rutin || 0) + (d.rekomendasi_bantuan?.keduanya || 0); + const pelatihan = (d.rekomendasi_bantuan?.bantuan_pelatihan || 0) + (d.rekomendasi_bantuan?.keduanya || 0); + set('stat-rutin', rutin); + set('stat-pelatihan', pelatihan); + + // Field baru dari statistik.php + const avgPh = parseInt(d.rata_penghasilan || 0); + set('stat-avg-penghasilan', 'Rp ' + avgPh.toLocaleString('id-ID')); + set('stat-anggota-db', (d.total_anggota_db || 0) + ' jiwa'); + + } catch { /* silent */ } + } + + /* ══════════════════════════════════════════════════ + MARKERS IBADAH + ══════════════════════════════════════════════════ */ + function renderIbadahMarkers() { + ibadahLayerGroup.clearLayers(); + ibadahMarkers = {}; + ibadahData.forEach(d => { + const cfg = JENIS_CONFIG[d.jenis] || JENIS_CONFIG.lainnya; + const icon = L.divIcon({ + className: '', + html: `
${svgIcon(cfg.svgKey, 18, 18)}
`, + iconSize: [38, 38], iconAnchor: [19, 19], + }); + + const marker = L.marker([d.latitude, d.longitude], { icon, draggable: true }) + .on('click', () => { if (!addMode) onIbadahClick(d, marker); }) + .on('dragend', async e => { + const { lat, lng } = e.target.getLatLng(); + d.latitude = lat; d.longitude = lng; + const res = await fetch('api/rumah_ibadah.php', { + method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ + id: d.id, nama: d.nama, jenis: d.jenis, + penanggung_jawab: d.penanggung_jawab, kontak: d.kontak, + alamat: d.alamat, kelurahan: d.kelurahan, kecamatan: d.kecamatan, kota: d.kota, + keterangan: d.keterangan, + kapasitas_kk: d.kapasitas_kk ?? 20, + radius_primer: d.radius_primer ?? 500, + anggaran_bulanan: d.anggaran_bulanan ?? 0, + latitude: lat, longitude: lng + }) + }); + const json = await res.json(); + if (json.success) { + showToast('Koordinat diperbarui', 'success'); + await loadIbadah(); + } else { + showToast('Gagal menyimpan koordinat', 'error'); + } + if (activeRadiusIbadahId == d.id) { activeRadiusCircle.setLatLng([lat, lng]); refreshRadiusIfActive(); } + }); + + ibadahLayerGroup.addLayer(marker); + ibadahMarkers[d.id] = marker; + }); + } + + /* ══════════════════════════════════════════════════ + MARKERS WARGA + ══════════════════════════════════════════════════ */ + function renderWargaMarkers(highlightIds = new Set()) { + wargaLayerGroup.clearLayers(); + wargaMarkers = {}; + const isKontributor = currentUser && currentUser.role === 'kontributor'; + const canDrag = currentUser && ['admin', 'petugas'].includes(currentUser.role); + const visibleWarga = getWargaForRole(); + visibleWarga.forEach(d => { + const inRadius = highlightIds.has(parseInt(d.id)); + const cfg = KATEGORI_CONFIG[d.kategori_kemiskinan] || KATEGORI_CONFIG.miskin; + let markerColor; + if (inRadius) { + markerColor = (d.status_bantuan === 'sudah_terima') ? '#10b981' : '#ef4444'; + } else { + markerColor = cfg.color; + } + + let htmlContent = ''; + let iconSize = [13, 13]; + let iconAnchor = [6.5, 6.5]; + + if (inRadius) { + iconSize = [28, 28]; + iconAnchor = [14, 28]; + const pulse = isKontributor ? `
` : ''; + htmlContent = ` +
+ ${pulse} + + + + +
+ `; + } else { + const size = 13; + const shadow = '0 2px 6px rgba(0,0,0,0.2)'; + const pulse = isKontributor ? `
` : ''; + htmlContent = ` +
+ ${pulse} +
+
+ `; + } + + const icon = L.divIcon({ + className: '', + html: htmlContent, + iconSize: iconSize, + iconAnchor: iconAnchor, + }); + + const marker = L.marker([d.latitude, d.longitude], { icon, draggable: canDrag }) + .on('click', () => { if (!addMode) onWargaClick(d); }); + + // Only attach drag handler for roles that can edit + if (canDrag) { + marker.on('dragend', async e => { + const { lat, lng } = e.target.getLatLng(); + d.latitude = lat; d.longitude = lng; + const resW = await fetch('api/rumah_warga.php', { + method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ + id: d.id, no_kk: d.no_kk, nama_kepala_keluarga: d.nama_kepala_keluarga, nik: d.nik, + alamat: d.alamat, kelurahan: d.kelurahan, kecamatan: d.kecamatan, kota: d.kota, + jumlah_tanggungan: d.jumlah_tanggungan, penghasilan_bulanan: d.penghasilan_bulanan, + pekerjaan: d.pekerjaan, status_pekerjaan: d.status_pekerjaan, + luas_rumah: d.luas_rumah, jenis_lantai: d.jenis_lantai, jenis_dinding: d.jenis_dinding, + sumber_air: d.sumber_air, status_tempat_tinggal: d.status_tempat_tinggal, + kategori_kemiskinan: d.is_kategori_manual ? d.kategori_kemiskinan : 'sistem', + status_bantuan: d.status_bantuan, + rekomendasi_bantuan: d.is_rekomendasi_manual ? d.rekomendasi_bantuan : 'sistem', + keterangan: d.keterangan, + latitude: lat, longitude: lng + }) + }); + const jsonW = await resW.json(); + if (jsonW.success) { + showToast('Koordinat diperbarui', 'success'); + await loadWarga(); + } else { + showToast('Gagal menyimpan koordinat', 'error'); + } + refreshRadiusIfActive(); + }); + } + + // Tooltip for kontributor: location indicator only + if (isKontributor) { + marker.bindTooltip('📍 Lokasi Penerima Manfaat', { + direction: 'top', offset: [0, -8], opacity: 0.9, + className: 'kontributor-tooltip' + }); + } + + wargaLayerGroup.addLayer(marker); + wargaMarkers[d.id] = marker; + }); + } + + /* ══════════════════════════════════════════════════ + CLICK HANDLERS + ══════════════════════════════════════════════════ */ + function onIbadahClick(d, marker) { + if (addMode) return; + drawRadius(d, marker); + showInfoIbadah(d); + panToPoint(d.latitude, d.longitude, 15); + } + + async function onWargaClick(d) { + if (addMode) return; + await showInfoWarga(d); + panToPoint(d.latitude, d.longitude, 16); + } + + /* ══════════════════════════════════════════════════ + RADIUS + ══════════════════════════════════════════════════ */ + function drawRadius(ibadah, marker) { + if (activeRadiusCircle) map.removeLayer(activeRadiusCircle); + activeRadiusCircle = L.circle([ibadah.latitude, ibadah.longitude], { + radius: radiusMeters, color: '#6366f1', fillColor: '#6366f1', + fillOpacity: 0.07, weight: 1.5, dashArray: '6 4', + }).addTo(map); + activeRadiusIbadahId = ibadah.id; + refreshRadiusData(ibadah); + } + + function getWargaInRadius(ibadahId, lat, lng, r) { + return getWargaForRole().filter(w => { + const kelolaId = w.dikelola_oleh_ibadah_id ? parseInt(w.dikelola_oleh_ibadah_id) : null; + if (kelolaId) { + // Jika ada penugasan eksplisit, tampilkan jika dia milik ibadah ini + return kelolaId === parseInt(ibadahId); + } else { + // Jika belum ada penugasan, cek radius Haversine + return haversine(lat, lng, parseFloat(w.latitude), parseFloat(w.longitude)) <= r; + } + }); + } + + function getWargaForRole() { + if (currentUser?.role === 'koordinator' && currentUser.rumah_ibadah_id) { + const myId = parseInt(currentUser.rumah_ibadah_id); + const myIbadah = ibadahData.find(x => parseInt(x.id) === myId); + if (myIbadah) { + return wargaData.filter(w => { + const kelolaId = w.dikelola_oleh_ibadah_id ? parseInt(w.dikelola_oleh_ibadah_id) : null; + if (kelolaId) { + return kelolaId === myId; + } else { + return haversine(parseFloat(myIbadah.latitude), parseFloat(myIbadah.longitude), parseFloat(w.latitude), parseFloat(w.longitude)) <= radiusMeters; + } + }); + } + return []; + } + return wargaData; + } + + function refreshRadiusData(ibadah) { + const inRadius = getWargaInRadius(ibadah.id, ibadah.latitude, ibadah.longitude, radiusMeters); + const ids = new Set(inRadius.map(m => parseInt(m.id))); + renderWargaMarkers(ids); + showRadiusInfo(ibadah.nama, radiusMeters, inRadius.length); + if (currentUser?.role === 'koordinator') { + renderWargaList(); + } + } + + function refreshRadiusIfActive() { + if (currentUser?.role === 'koordinator' && currentUser.rumah_ibadah_id) { + if (activeRadiusCircle) { + activeRadiusCircle.setRadius(radiusMeters); + } + const ibadah = ibadahData.find(d => parseInt(d.id) === parseInt(currentUser.rumah_ibadah_id)); + if (ibadah) { + refreshRadiusData(ibadah); + } + return; + } + + if (!activeRadiusCircle || !activeRadiusIbadahId) return; + const ibadah = ibadahData.find(d => parseInt(d.id) === parseInt(activeRadiusIbadahId)); + if (!ibadah) return; + activeRadiusCircle.setRadius(radiusMeters); + refreshRadiusData(ibadah); + } + + function clearRadius() { + if (activeRadiusCircle) { map.removeLayer(activeRadiusCircle); activeRadiusCircle = null; } + activeRadiusIbadahId = null; + renderWargaMarkers(new Set()); + hideRadiusInfo(); + } + + function showRadiusInfo(nama, r, count) { + const bar = document.getElementById('radius-info'); + bar.querySelector('.ri-val').textContent = r >= 1000 ? (r / 1000).toFixed(1) + ' km' : r + ' m'; + bar.querySelector('.ri-count').textContent = count + ' rumah'; + bar.classList.add('show'); + } + function hideRadiusInfo() { document.getElementById('radius-info').classList.remove('show'); } + + /* ══════════════════════════════════════════════════ + RADIUS CONTROL + ══════════════════════════════════════════════════ */ + function initRadiusControl() { + const slider = document.getElementById('radius-slider'); + const display = document.getElementById('radius-display'); + const input = document.getElementById('radius-input'); + + const updateGrad = () => { + const pct = (radiusMeters - slider.min) / (slider.max - slider.min) * 100; + slider.style.background = `linear-gradient(to right,#6366f1 0%,#6366f1 ${pct}%,#e2e8f0 ${pct}%)`; + }; + + slider.addEventListener('input', () => { + radiusMeters = parseInt(slider.value); + display.textContent = radiusMeters >= 1000 ? (radiusMeters / 1000).toFixed(1) + ' km' : radiusMeters + ' m'; + input.value = radiusMeters; + updateGrad(); + refreshRadiusIfActive(); + }); + + input.addEventListener('change', () => { + radiusMeters = Math.max(50, Math.min(5000, parseInt(input.value) || 500)); + slider.value = radiusMeters; + slider.dispatchEvent(new Event('input')); + }); + + document.querySelectorAll('.preset-btn').forEach(btn => { + btn.addEventListener('click', () => { + document.querySelectorAll('.preset-btn').forEach(b => b.classList.remove('active')); + btn.classList.add('active'); + radiusMeters = parseInt(btn.dataset.r); + slider.value = radiusMeters; + slider.dispatchEvent(new Event('input')); + }); + }); + + updateGrad(); + } + + /* ══════════════════════════════════════════════════ + INFO PANEL + ══════════════════════════════════════════════════ */ + function showInfoIbadah(d) { + const cfg = JENIS_CONFIG[d.jenis] || JENIS_CONFIG.lainnya; + const panel = document.getElementById('info-panel'); + const header = document.getElementById('info-panel-title'); + const body = document.getElementById('info-panel-body'); + + header.innerHTML = `
${svgIcon(cfg.svgKey, 14, 14)}
${d.nama}`; + body.innerHTML = ` +
${svgIcon('pin', 11, 11)}Memuat alamat...
+
Jenis${cfg.label}
+ ${d.penanggung_jawab ? `
Penanggung Jwb${d.penanggung_jawab}
` : ''} + ${d.kontak ? `
Kontak${d.kontak}
` : ''} + ${d.kelurahan ? `
Kelurahan${d.kelurahan}
` : ''} + ${d.kecamatan ? `
Kecamatan${d.kecamatan}
` : ''} + ${d.kota ? `
Kota${d.kota}
` : ''} + ${d.keterangan ? `
Ket.${d.keterangan}
` : ''} +
${parseFloat(d.latitude).toFixed(6)}, ${parseFloat(d.longitude).toFixed(6)}
+ ${(currentUser && currentUser.role === 'admin') ? ` +
+ + +
` : ''}`; + + panel.classList.add('show'); + reverseGeocode(d.latitude, d.longitude, addr => { const el = document.getElementById('info-address'); if (el) el.textContent = addr; }); + } + + async function showInfoWarga(d) { + const isKoor = currentUser && currentUser.role === 'koordinator'; + const isKontributor = currentUser && currentUser.role === 'kontributor'; + const isGuest = !currentUser; + const cat = d.kategori_kemiskinan ? (KATEGORI_CONFIG[d.kategori_kemiskinan] || KATEGORI_CONFIG.miskin) : null; + const sts = STATUS_CONFIG[d.status_bantuan] || STATUS_CONFIG.belum_terima; + const rek = REK_CONFIG[d.rekomendasi_bantuan] || REK_CONFIG.tidak_ada; + const panel = document.getElementById('info-panel'); + const header = document.getElementById('info-panel-title'); + const body = document.getElementById('info-panel-body'); + + if (isKontributor || isGuest) { + const title = isGuest ? 'Lokasi Penerima Manfaat' : 'Rumah Penerima Manfaat'; + header.innerHTML = `
${svgIcon('home', 14, 14)}
${title}`; + + let htmlContent = ` +
${svgIcon('pin', 11, 11)}Memuat alamat...
+
Kategori${cat ? cat.label : 'Kurang Mampu'}
+
Status Bansos${sts.label}
+
${parseFloat(d.latitude).toFixed(6)}, ${parseFloat(d.longitude).toFixed(6)}
+ +
+
+ 🔒 Privasi Terjaga +
+
+ Detail informasi pribadi warga disembunyikan untuk menjaga privasi penerima manfaat. +
+
+ `; + + if (isKontributor) { + htmlContent += ` + + `; + } + body.innerHTML = htmlContent; + panel.classList.add('show'); + reverseGeocode(d.latitude, d.longitude, addr => { const el = document.getElementById('info-address'); if (el) el.textContent = addr; }); + return; + } + + header.innerHTML = `
${svgIcon('home', 14, 14)}
${d.nama_kepala_keluarga}`; + + let htmlContent = ` +
${svgIcon('pin', 11, 11)}Memuat alamat...
+ `; + + if (!isKoor) { + htmlContent += ` + ${d.no_kk ? `
No. KK${d.no_kk}
` : ''} + ${d.nik ? `
NIK${d.nik}
` : ''} +
Tanggungan${d.jumlah_tanggungan} jiwa
+
Penghasilan${formatRupiah(d.penghasilan_bulanan)}/bln
+ ${d.pekerjaan ? `
Pekerjaan${d.pekerjaan}
` : ''} +
Kategori${cat.label}
+ `; + } + + htmlContent += ` +
Bansos${sts.label}
+
Rekomendasi${rek.label}
+ ${d.dikelola_oleh_ibadah_id ? (() => { + const ibadah = ibadahData.find(i => parseInt(i.id) === parseInt(d.dikelola_oleh_ibadah_id)); + return ibadah ? `
Penanggung Bantuan:
${ibadah.nama}
` : ''; + })() : ''} + ${!isKoor ? `
${rek.label}
${rek.desc}
` : ''} + ${d.kelurahan ? `
Kelurahan${d.kelurahan}
` : ''} + ${d.kecamatan ? `
Kecamatan${d.kecamatan}
` : ''} + ${d.kota ? `
Kota${d.kota}
` : ''} + ${!isKoor && d.keterangan ? `
Ket.${d.keterangan}
` : ''} +
${parseFloat(d.latitude).toFixed(6)}, ${parseFloat(d.longitude).toFixed(6)}
+ `; + + if (!isKoor) { + htmlContent += ` +
${svgIcon('users', 11, 11)} Anggota Keluarga
+
+ `; + } + + if (currentUser && (currentUser.role === 'admin' || currentUser.role === 'petugas' || currentUser.role === 'koordinator')) { + htmlContent += ` +
+ + ${currentUser.role === 'admin' ? `` : ''} +
+ `; + } + + if (currentUser && (currentUser.role === 'kontributor' || currentUser.role === 'koordinator')) { + htmlContent += ` + + `; + } + + body.innerHTML = htmlContent; + + panel.classList.add('show'); + reverseGeocode(d.latitude, d.longitude, addr => { const el = document.getElementById('info-address'); if (el) el.textContent = addr; }); + + if (!isKoor) { + try { + const res = await fetch(`api/anggota_keluarga.php?rumah_warga_id=${d.id}`); + const json = await res.json(); + const container = document.getElementById('anggota-info-list'); + if (!container) return; + if (!json.data?.length) { container.innerHTML = `
Belum ada data anggota
`; return; } + const PEND = { tidak_sekolah: 'Tdk Sekolah', sd: 'SD', smp: 'SMP', sma: 'SMA', d3: 'D3', s1: 'S1', s2: 'S2', s3: 'S3' }; + container.innerHTML = `
${json.data.map(a => { + const usia = a.usia !== null ? `${a.usia} th` : '–'; + const prod = a.usia >= 18 && a.usia <= 55; + const bisaKerja = parseInt(a.dapat_bekerja) === 1 && ['sehat', 'sakit_ringan'].includes(a.kondisi_kesehatan); + const dotColor = prod && bisaKerja ? '#10b981' : (prod ? '#fbbf24' : '#94a3b8'); + return `
+
+
+
${a.nama} (${usia})
+
+ ${a.jenis_kelamin === 'laki_laki' ? '♂' : '♀'} · ${PEND[a.pendidikan] || a.pendidikan} + ${a.pekerjaan ? ' · ' + a.pekerjaan : ''} + ${a.kondisi_kesehatan !== 'sehat' ? ` · ${a.kondisi_kesehatan.replace(/_/g, ' ')}` : ''} + ${parseInt(a.dapat_bekerja) ? '' : ' · Tdk bisa kerja'} +
+
+
`; + }).join('')}
`; + } catch { /* silent */ } + } + } + + /* ══════════════════════════════════════════════════ + REVERSE GEOCODING + ══════════════════════════════════════════════════ */ + async function reverseGeocode(lat, lng, cb) { + try { + const res = await fetch(`https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lng}&format=json&accept-language=id`); + const json = await res.json(); + cb(json.display_name || `${lat}, ${lng}`, json.address || {}); + } catch { cb(`${lat}, ${lng}`, {}); } + } + + /* ══════════════════════════════════════════════════ + ADD MODE + ══════════════════════════════════════════════════ */ + function setAddMode(mode) { + addMode = mode; + if (tempMarker) { map.removeLayer(tempMarker); tempMarker = null; } + const ind = document.getElementById('mode-indicator'); + if (mode === 'ibadah') { + ind.innerHTML = `
Klik peta untuk menentukan lokasi Rumah Ibadah`; + ind.classList.add('show'); + map.getContainer().style.cursor = 'crosshair'; + } else if (mode === 'warga') { + ind.innerHTML = `
Klik peta untuk menentukan lokasi Rumah Warga`; + ind.classList.add('show'); + map.getContainer().style.cursor = 'crosshair'; + } else { + ind.classList.remove('show'); + map.getContainer().style.cursor = ''; + } + } + + function cancelAddMode() { + setAddMode(null); + if (tempMarker) { map.removeLayer(tempMarker); tempMarker = null; } + } + + function onMapClick(e) { + if (!addMode) return; + const { lat, lng } = e.latlng; + if (tempMarker) map.removeLayer(tempMarker); + + if (addMode === 'ibadah') { + document.getElementById('ibadah-lat').value = lat; + document.getElementById('ibadah-lng').value = lng; + updateCoordDisplay('ibadah', lat, lng); + tempMarker = L.marker([lat, lng], { icon: L.divIcon({ className: '', html: `
${svgIcon('mosque', 18, 18)}
`, iconSize: [38, 38], iconAnchor: [19, 19] }) }).addTo(map); + // reverseGeocode is called inside openIbadahModal — no need to call again here + + // Setelah memilih titik untuk rumah ibadah, buka modal pre-filled dan keluar dari add mode + openIbadahModal(null, lat, lng); + cancelAddMode(); + } else if (addMode === 'warga') { + document.getElementById('warga-lat').value = lat; + document.getElementById('warga-lng').value = lng; + updateCoordDisplay('warga', lat, lng); + tempMarker = L.marker([lat, lng], { icon: L.divIcon({ className: '', html: `
`, iconSize: [14, 14], iconAnchor: [7, 7] }) }).addTo(map); + // reverseGeocode is called inside openWargaModal — no need to call again here + + // Setelah memilih titik untuk rumah warga, buka modal pre-filled dan keluar dari add mode + openWargaModal(null, lat, lng); + cancelAddMode(); + } + } + + function updateCoordDisplay(type, lat, lng) { + const el = document.getElementById(`${type}-coord-display`); + if (el) el.textContent = lat && lng ? `${parseFloat(lat).toFixed(6)}, ${parseFloat(lng).toFixed(6)}` : 'Belum dipilih — klik di peta'; + } + + /* ══════════════════════════════════════════════════ + MODAL IBADAH + ══════════════════════════════════════════════════ */ + function openIbadahModal(data = null, lat = null, lng = null) { + document.getElementById('modal-ibadah-title').textContent = data ? 'Edit Rumah Ibadah' : 'Tambah Rumah Ibadah'; + document.getElementById('ibadah-id').value = data?.id || ''; + document.getElementById('ibadah-nama').value = data?.nama || ''; + document.getElementById('ibadah-jenis').value = data?.jenis || 'masjid'; + document.getElementById('ibadah-pj').value = data?.penanggung_jawab || ''; + document.getElementById('ibadah-kontak').value = data?.kontak || ''; + document.getElementById('ibadah-alamat').value = data?.alamat || ''; + document.getElementById('ibadah-kelurahan').value = data?.kelurahan || ''; + document.getElementById('ibadah-kecamatan').value = data?.kecamatan || ''; + document.getElementById('ibadah-kota').value = data?.kota || ''; + document.getElementById('ibadah-ket').value = data?.keterangan || ''; + document.getElementById('ibadah-kapasitas').value = data?.kapasitas_kk || 20; + document.getElementById('ibadah-radius').value = data?.radius_primer || 500; + document.getElementById('ibadah-anggaran').value = data?.anggaran_bulanan || 0; + const latVal = data ? data.latitude : (lat || ''); + const lngVal = data ? data.longitude : (lng || ''); + document.getElementById('ibadah-lat').value = latVal; + document.getElementById('ibadah-lng').value = lngVal; + updateCoordDisplay('ibadah', latVal, lngVal); + if (latVal && lngVal) reverseGeocode(latVal, lngVal, (addr, details) => { + const h = document.getElementById('ibadah-addr-hint'); + if (h) h.textContent = addr; + + if (!data) { + if (details && Object.keys(details).length > 0) { + const kel = details.village || details.suburb || details.neighbourhood || ''; + const kec = details.county || details.city_district || details.town || ''; + const kot = details.city || details.municipality || details.regency || details.county || ''; + + if (kel && !document.getElementById('ibadah-kelurahan').value) document.getElementById('ibadah-kelurahan').value = kel; + if (kec && !document.getElementById('ibadah-kecamatan').value) document.getElementById('ibadah-kecamatan').value = kec; + if (kot && !document.getElementById('ibadah-kota').value) document.getElementById('ibadah-kota').value = kot; + } else { + if (h) h.innerHTML = addr + ' (Alamat detail tidak didapatkan, silahkan isi manual)'; + } + } + }); + document.getElementById('modal-ibadah').classList.add('show'); + } + + async function saveIbadah() { + const id = document.getElementById('ibadah-id').value; + const nama = document.getElementById('ibadah-nama').value.trim(); + const lat = document.getElementById('ibadah-lat').value; + const lng = document.getElementById('ibadah-lng').value; + if (!nama) { showToast('Nama wajib diisi', 'error'); return; } + if (!lat || !lng) { showToast('Pilih lokasi di peta terlebih dahulu', 'error'); return; } + + const payload = { + id, nama, + jenis: document.getElementById('ibadah-jenis').value, + penanggung_jawab: document.getElementById('ibadah-pj').value, + kontak: document.getElementById('ibadah-kontak').value, + alamat: document.getElementById('ibadah-alamat').value, + kelurahan: document.getElementById('ibadah-kelurahan').value, + kecamatan: document.getElementById('ibadah-kecamatan').value, + kota: document.getElementById('ibadah-kota').value, + keterangan: document.getElementById('ibadah-ket').value, + kapasitas_kk: document.getElementById('ibadah-kapasitas').value, + radius_primer: document.getElementById('ibadah-radius').value, + anggaran_bulanan: document.getElementById('ibadah-anggaran').value, + latitude: lat, longitude: lng, + }; + + try { + const res = await fetch('api/rumah_ibadah.php', { method: id ? 'PUT' : 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); + const json = await res.json(); + if (json.success) { showToast(json.message, 'success'); closeModal('modal-ibadah'); cancelAddMode(); await loadIbadah(); loadStats(); refreshRadiusIfActive(); } + else showToast(json.message, 'error'); + } catch { showToast('Terjadi kesalahan', 'error'); } + } + + /* ══════════════════════════════════════════════════ + MODAL WARGA — ANGGOTA ROWS + ══════════════════════════════════════════════════ */ + function buildAnggotaRow(data = {}, index = 0) { + return `
+
+
Anggota ${typeof index === 'number' ? index + 1 : ''}
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
`; + } + + function collectAnggotaRows() { + const container = document.getElementById('anggota-form-container'); + if (!container) return []; + const items = container.querySelectorAll('[data-arow]'); + const result = [], seen = new Set(); + items.forEach(el => { + const idx = el.dataset.arow; + if (seen.has(idx)) return; + seen.add(idx); + const row = { dapat_bekerja: 1 }; + const inputs = container.querySelectorAll(`[name][data-arow="${idx}"]`); + inputs.forEach(i => { row[i.name] = i.tagName === 'SELECT' ? i.value : i.value.trim(); }); + if (row.nama) result.push(row); + }); + return result; + } + + function kategoriLocal(penghasilan, tanggungan, tinggal, air) { + let p = tanggungan > 0 ? penghasilan / tanggungan : penghasilan; + let skor = 0; + if (p < 300000) skor += 40; + else if (p < 600000) skor += 20; + + if (tinggal === 'numpang' || tinggal === 'sewa') skor += 20; + if (air === 'sungai' || air === 'hujan') skor += 10; + + if (skor >= 50) return 'sangat_miskin'; + if (skor >= 20) return 'miskin'; + return 'rentan_miskin'; + } + + function previewRek() { + const anggota = collectAnggotaRows(); + const penghasilan = parseFloat(document.getElementById('warga-penghasilan')?.value || 0); + const tanggungan = parseInt(document.getElementById('warga-tanggungan')?.value || 1); + const tinggal = document.getElementById('warga-tempat')?.value || 'milik_sendiri'; + const air = document.getElementById('warga-air')?.value || 'sumur'; + const stsPekerjaan = document.getElementById('warga-stspekerjaan')?.value || 'tidak_bekerja'; + const isKatSistem = document.getElementById('warga-kategori')?.value === 'sistem'; + + const rek = rekomendasiLocal(anggota, penghasilan, stsPekerjaan); + const cfg = REK_CONFIG[rek] || REK_CONFIG.tidak_ada; + + const box = document.getElementById('rek-preview-box'); + if (box) { + let html = `
Prediksi Bantuan: ${cfg.label}
${cfg.desc}
`; + if (isKatSistem) { + const kat = kategoriLocal(penghasilan, tanggungan, tinggal, air); + let katLabel = ''; + if (kat === 'sangat_miskin') katLabel = 'Sangat Kurang Mampu'; + else if (kat === 'miskin') katLabel = 'Kurang Mampu'; + else if (kat === 'rentan_miskin') katLabel = 'Rentan Kurang Mampu'; + + let katCls = 'badge-miskin'; + if (kat === 'sangat_miskin') katCls = 'badge-sangat-miskin'; + else if (kat === 'rentan_miskin') katCls = 'badge-rentan-miskin'; + + html = `
Prediksi Kategori: ${katLabel}
` + html; + } + box.innerHTML = html; + } + } + + /* ══════════════════════════════════════════════════ + MODAL WARGA + FIX: openWargaModal sekarang reset sub-tab dengan + cara yang aman (tidak bergantung pada querySelector + global yang bisa bentrok dengan elemen lain) + ══════════════════════════════════════════════════ */ + async function openWargaModal(data = null, lat = null, lng = null) { + const isKoor = currentUser && currentUser.role === 'koordinator'; + document.getElementById('modal-warga-title').textContent = data ? 'Edit Rumah Warga' : 'Tambah Rumah Warga'; + + // Reset sub-tab modal warga — pakai scope modal biar aman + const modalEl = document.getElementById('modal-warga'); + modalEl.querySelectorAll('.modal-tab-btn').forEach(b => b.classList.remove('active')); + modalEl.querySelectorAll('.modal-tab-panel').forEach(p => p.classList.remove('active')); + const firstTabBtn = modalEl.querySelector('.modal-tab-btn[data-mtab="data-pokok"]'); + const firstTabPanel = document.getElementById('mtab-data-pokok'); + if (firstTabBtn) firstTabBtn.classList.add('active'); + if (firstTabPanel) firstTabPanel.classList.add('active'); + + // Hide tabs if coordinator + const tabRumahBtn = modalEl.querySelector('.modal-tab-btn[data-mtab="rumah"]'); + const tabAnggotaBtn = modalEl.querySelector('.modal-tab-btn[data-mtab="anggota"]'); + if (tabRumahBtn) tabRumahBtn.style.display = isKoor ? 'none' : ''; + if (tabAnggotaBtn) tabAnggotaBtn.style.display = isKoor ? 'none' : ''; + + const toggleField = (id, show) => { + const el = document.getElementById(id); + if (el) { + const fg = el.closest('.form-group'); + if (fg) fg.style.display = show ? '' : 'none'; + } + }; + const disableField = (id, disable) => { + const el = document.getElementById(id); + if (el) el.disabled = disable; + }; + + const fieldsToToggle = [ + 'warga-nokk', 'warga-nik', 'warga-tanggungan', 'warga-penghasilan', + 'warga-pekerjaan', 'warga-stspekerjaan', 'warga-kategori', + 'warga-rekomendasi', 'warga-ket', 'warga-coord-display' + ]; + fieldsToToggle.forEach(fId => toggleField(fId, !isKoor)); + + const fieldsToDisable = [ + 'warga-nama', 'warga-alamat', 'warga-kelurahan', 'warga-kecamatan', 'warga-kota' + ]; + fieldsToDisable.forEach(fId => disableField(fId, isKoor)); + + const isAdmin = currentUser && currentUser.role === 'admin'; + disableField('warga-dikelola-oleh', !isAdmin); + + // Also hide prediction/preview box if coordinator + const previewBox = document.getElementById('rek-preview-box'); + if (previewBox) previewBox.style.display = isKoor ? 'none' : ''; + + // Hide empty form-rows (rows where all child form-groups are hidden) for clean layout + if (isKoor) { + const dataPokokPanel = document.getElementById('mtab-data-pokok'); + if (dataPokokPanel) { + dataPokokPanel.querySelectorAll('.form-row').forEach(row => { + const groups = row.querySelectorAll('.form-group'); + const allHidden = Array.from(groups).every(g => g.style.display === 'none'); + row.style.display = allHidden ? 'none' : ''; + }); + } + } else { + // Restore all form-rows for non-coordinator + modalEl.querySelectorAll('.form-row').forEach(row => { row.style.display = ''; }); + } + + document.getElementById('warga-id').value = data?.id || ''; + document.getElementById('warga-nokk').value = data?.no_kk || ''; + document.getElementById('warga-nama').value = data?.nama_kepala_keluarga || ''; + document.getElementById('warga-nik').value = data?.nik || ''; + document.getElementById('warga-tanggungan').value = data?.jumlah_tanggungan || 1; + document.getElementById('warga-penghasilan').value = data?.penghasilan_bulanan || 0; + document.getElementById('warga-pekerjaan').value = data?.pekerjaan || ''; + document.getElementById('warga-stspekerjaan').value = data?.status_pekerjaan || 'tidak_bekerja'; + document.getElementById('warga-alamat').value = data?.alamat || ''; + document.getElementById('warga-kelurahan').value = data?.kelurahan || ''; + document.getElementById('warga-kecamatan').value = data?.kecamatan || ''; + document.getElementById('warga-kota').value = data?.kota || ''; + // Set manual kategori if active + if (data && parseInt(data.is_kategori_manual) === 1) { + document.getElementById('warga-kategori').value = data.kategori_kemiskinan || 'miskin'; + } else { + document.getElementById('warga-kategori').value = 'sistem'; + } + + document.getElementById('warga-stsbantuan').value = data?.status_bantuan || 'belum_terima'; + + // Set manual recommendation if active + if (data && parseInt(data.is_rekomendasi_manual) === 1) { + document.getElementById('warga-rekomendasi').value = data.rekomendasi_bantuan || 'sistem'; + } else { + document.getElementById('warga-rekomendasi').value = 'sistem'; + } + + document.getElementById('warga-luas').value = data?.luas_rumah || ''; + document.getElementById('warga-lantai').value = data?.jenis_lantai || 'semen'; + document.getElementById('warga-dinding').value = data?.jenis_dinding || 'tembok'; + document.getElementById('warga-air').value = data?.sumber_air || 'sumur'; + document.getElementById('warga-tempat').value = data?.status_tempat_tinggal || 'milik_sendiri'; + document.getElementById('warga-ket').value = data?.keterangan || ''; + + const latVal = data ? data.latitude : (lat || ''); + const lngVal = data ? data.longitude : (lng || ''); + document.getElementById('warga-lat').value = latVal; + document.getElementById('warga-lng').value = lngVal; + updateCoordDisplay('warga', latVal, lngVal); + + // Populate dropdown Penanggung Bantuan + const selectPj = document.getElementById('warga-dikelola-oleh'); + if (selectPj) { + let optionsHtml = ''; + let closestIbadahId = null; + let minDistance = Infinity; + + if (typeof ibadahData !== 'undefined' && Array.isArray(ibadahData)) { + ibadahData.forEach(ib => { + optionsHtml += ``; + if (!data && latVal && lngVal && typeof haversine === 'function') { + const dist = haversine(parseFloat(latVal), parseFloat(lngVal), parseFloat(ib.latitude), parseFloat(ib.longitude)); + if (dist < minDistance) { minDistance = dist; closestIbadahId = ib.id; } + } + }); + } + selectPj.innerHTML = optionsHtml; + + if (data && data.dikelola_oleh_ibadah_id) { + selectPj.value = data.dikelola_oleh_ibadah_id; + } else if (!data && closestIbadahId) { + selectPj.value = closestIbadahId; + } else { + selectPj.value = ''; + } + } + + if (latVal && lngVal) reverseGeocode(latVal, lngVal, (addr, details) => { + const h = document.getElementById('warga-addr-hint'); + if (h) h.textContent = addr; + + if (!data) { + if (details && Object.keys(details).length > 0) { + const kel = details.village || details.suburb || details.neighbourhood || ''; + const kec = details.county || details.city_district || details.town || ''; + const kot = details.city || details.municipality || details.regency || details.county || ''; + + if (kel && !document.getElementById('warga-kelurahan').value) document.getElementById('warga-kelurahan').value = kel; + if (kec && !document.getElementById('warga-kecamatan').value) document.getElementById('warga-kecamatan').value = kec; + if (kot && !document.getElementById('warga-kota').value) document.getElementById('warga-kota').value = kot; + } else { + if (h) h.innerHTML = addr + ' (Alamat detail tidak didapatkan, silahkan isi manual)'; + } + } + }); + + // Load anggota existing + anggotaRows = []; + const container = document.getElementById('anggota-form-container'); + container.innerHTML = ''; + if (data?.id && !isKoor) { + try { + const res = await fetch(`api/anggota_keluarga.php?rumah_warga_id=${data.id}`); + const json = await res.json(); + if (json.data?.length) { + json.data.forEach((a, i) => { anggotaRows.push(i); container.insertAdjacentHTML('beforeend', buildAnggotaRow(a, i)); }); + } + } catch { /* silent */ } + } + previewRek(); + + // Adjust footer buttons based on mode (Add vs Edit) + const btnVerifyDirect = document.getElementById('btn-verify-warga-direct'); + const btnSave = document.getElementById('btn-save-warga'); + if (data?.id && !isKoor) { + if (btnVerifyDirect) btnVerifyDirect.style.display = 'inline-flex'; + if (btnSave) btnSave.innerHTML = ' Simpan & Verifikasi'; + } else { + if (btnVerifyDirect) btnVerifyDirect.style.display = 'none'; + if (btnSave) { + if (isKoor) { + btnSave.innerHTML = 'Simpan'; + } else { + btnSave.innerHTML = 'Simpan'; + } + } + } + + // Buka modal setelah semua data terisi + modalEl.classList.add('show'); + if (document.getElementById('modal-premium-verifikasi')?.classList.contains('show')) { + document.body.classList.add('side-by-side-modals'); + } + } + + async function saveWarga() { + const isKoor = currentUser && currentUser.role === 'koordinator'; + const id = document.getElementById('warga-id').value; + const nokk = document.getElementById('warga-nokk').value.trim(); + const nama = document.getElementById('warga-nama').value.trim(); + const lat = document.getElementById('warga-lat').value; + const lng = document.getElementById('warga-lng').value; + + if (!isKoor) { + if (!nama) { showToast('Nama Kepala Keluarga wajib diisi', 'error'); return; } + if (!lat || !lng) { showToast('Pilih lokasi di peta terlebih dahulu', 'error'); return; } + if (!nokk || !/^\d{16}$/.test(nokk)) { showToast('No. KK harus 16 digit angka', 'error'); return; } + } else { + if (!id) { showToast('ID Warga wajib terisi', 'error'); return; } + } + + const payload = isKoor ? { + id, + status_bantuan: document.getElementById('warga-stsbantuan').value + } : { + id, no_kk: nokk, nama_kepala_keluarga: nama, + nik: document.getElementById('warga-nik').value, + jumlah_tanggungan: document.getElementById('warga-tanggungan').value, + penghasilan_bulanan: document.getElementById('warga-penghasilan').value, + pekerjaan: document.getElementById('warga-pekerjaan').value, + status_pekerjaan: document.getElementById('warga-stspekerjaan').value, + alamat: document.getElementById('warga-alamat').value, + kelurahan: document.getElementById('warga-kelurahan').value, + kecamatan: document.getElementById('warga-kecamatan').value, + kota: document.getElementById('warga-kota').value, + kategori_kemiskinan: document.getElementById('warga-kategori').value, + status_bantuan: document.getElementById('warga-stsbantuan').value, + rekomendasi_bantuan: document.getElementById('warga-rekomendasi').value, + luas_rumah: document.getElementById('warga-luas').value, + jenis_lantai: document.getElementById('warga-lantai').value, + jenis_dinding: document.getElementById('warga-dinding').value, + sumber_air: document.getElementById('warga-air').value, + status_tempat_tinggal: document.getElementById('warga-tempat').value, + keterangan: document.getElementById('warga-ket').value, + dikelola_oleh_ibadah_id: document.getElementById('warga-dikelola-oleh') ? document.getElementById('warga-dikelola-oleh').value : '', + latitude: lat, longitude: lng, + anggota: collectAnggotaRows(), + }; + + try { + const res = await fetch('api/rumah_warga.php', { method: id ? 'PUT' : 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); + const json = await res.json(); + if (json.success) { + const rekLabel = json.rekomendasi ? (REK_CONFIG[json.rekomendasi]?.label || json.rekomendasi) : ''; + + // Auto-verify pasca-simpan (baru maupun edit) + const targetId = id || json.id; + if (targetId) { + try { + await fetch('api/verifikasi.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + warga_id: parseInt(targetId), + petugas: currentUser ? (currentUser.role === 'koordinator' ? `Koordinator (${currentUser.username})` : currentUser.username) : 'Sistem', + tanggal: new Date().toLocaleDateString('en-CA'), + catatan: id ? 'Verifikasi otomatis pasca-edit data warga' : 'Verifikasi otomatis pendaftaran warga baru' + }) + }); + } catch (e) { console.error('Auto-verify error:', e); } + } + + showToast(`${json.message}${rekLabel ? ' · Rek: ' + rekLabel : ''}`, 'success'); + try { closeModal('modal-warga'); } catch (e) { } + try { cancelAddMode(); } catch (e) { } + try { await loadWarga(); } catch (e) { } + try { loadStats(); } catch (e) { } + try { refreshRadiusIfActive(); } catch (e) { } + + if (window.FiturBaru && FiturBaru.Verifikasi) { + try { FiturBaru.Verifikasi.load(document.getElementById('filter-verif-status')?.value || ''); } catch (e) { } + } + } else { + showToast(json.message, 'error'); + } + } catch (err) { + console.error('SaveWarga Error:', err); + showToast('Terjadi kesalahan', 'error'); + } + } + + /* ══════════════════════════════════════════════════ + EDIT / DELETE + ══════════════════════════════════════════════════ */ + function editIbadah(id) { + checkAuth({ type: 'edit_ibadah', id }, () => { + if (currentUser.role !== 'admin') { + showToast('Hanya Admin yang berhak merubah data rumah ibadah', 'error'); + return; + } + const d = ibadahData.find(x => parseInt(x.id) === parseInt(id)); + if (d) { document.getElementById('info-panel').classList.remove('show'); openIbadahModal(d); } + }); + } + + async function editWarga(id) { + checkAuth({ type: 'edit_warga', id }, async () => { + if (!['admin', 'petugas', 'koordinator'].includes(currentUser.role)) { + showToast('Anda tidak memiliki izin untuk mengedit data warga', 'error'); + return; + } + const d = wargaData.find(x => parseInt(x.id) === parseInt(id)); + if (d) { document.getElementById('info-panel').classList.remove('show'); await openWargaModal(d); } + }); + } + + async function deleteIbadah(id) { + checkAuth({ type: 'delete_ibadah', id }, async () => { + if (currentUser.role !== 'admin') { + showToast('Hanya Admin yang berhak menghapus data rumah ibadah', 'error'); + return; + } + if (!confirm('Hapus data rumah ibadah ini?')) return; + try { + const res = await fetch(`api/rumah_ibadah.php?id=${id}`, { method: 'DELETE' }); + const json = await res.json(); + if (json.success) { showToast(json.message, 'success'); document.getElementById('info-panel').classList.remove('show'); if (parseInt(activeRadiusIbadahId) === parseInt(id)) clearRadius(); await loadIbadah(); loadStats(); } + else showToast(json.message, 'error'); + } catch { showToast('Terjadi kesalahan', 'error'); } + }); + } + + async function deleteWarga(id) { + checkAuth({ type: 'delete_warga', id }, async () => { + if (currentUser.role !== 'admin') { + showToast('Hanya Admin yang berhak menghapus data warga', 'error'); + return; + } + if (!confirm('Hapus data rumah warga ini?')) return; + try { + const res = await fetch(`api/rumah_warga.php?id=${id}`, { method: 'DELETE' }); + const json = await res.json(); + if (json.success) { showToast(json.message, 'success'); document.getElementById('info-panel').classList.remove('show'); await loadWarga(); loadStats(); refreshRadiusIfActive(); } + else showToast(json.message, 'error'); + } catch { showToast('Terjadi kesalahan', 'error'); } + }); + } + + /* ══════════════════════════════════════════════════ + DATA LISTS + ══════════════════════════════════════════════════ */ + function renderIbadahList() { + const search = document.getElementById('search-ibadah')?.value || ''; + const jenis = document.getElementById('filter-ibadah-jenis')?.value || ''; + const container = document.getElementById('ibadah-list'); + if (!container) return; + const filtered = ibadahData.filter(d => { + const matchSearch = !search || d.nama.toLowerCase().includes(search.toLowerCase()); + const matchJenis = !jenis || d.jenis === jenis; + return matchSearch && matchJenis; + }); + + const isKoor = currentUser?.role === 'koordinator'; + let html = ''; + + if (isKoor && currentUser.rumah_ibadah_id) { + const myIbadah = ibadahData.find(x => parseInt(x.id) === parseInt(currentUser.rumah_ibadah_id)); + if (myIbadah) { + const cfg = JENIS_CONFIG[myIbadah.jenis] || JENIS_CONFIG.lainnya; + html += `
+
Rumah Ibadah Saya
+
+
${svgIcon(cfg.svgKey, 13, 13)}
+
+
${myIbadah.nama}
+
${myIbadah.kelurahan || '–'} ${myIbadah.kecamatan ? '· ' + myIbadah.kecamatan : ''}
+
+ ${cfg.label} +
+
+
Semua Rumah Ibadah
`; + } + } + + if (!filtered.length) { + container.innerHTML = html + `
${svgIcon('building', 26, 26)}
Tidak ada data
`; + return; + } + + const listHTML = filtered.map(d => { + const cfg = JENIS_CONFIG[d.jenis] || JENIS_CONFIG.lainnya; + const isMyIbadah = isKoor && parseInt(d.id) === parseInt(currentUser.rumah_ibadah_id); + const styleAttr = isMyIbadah ? ` style="border:1.5px solid #6366f1;background:rgba(99,102,241,0.06)"` : ''; + return `
+
${svgIcon(cfg.svgKey, 13, 13)}
+
+
${d.nama}
+
${d.kelurahan || '–'} ${d.kecamatan ? '· ' + d.kecamatan : ''}
+
+ ${cfg.label} +
`; + }).join(''); + + container.innerHTML = html + listHTML; + } + + function renderWargaList() { + const search = document.getElementById('search-warga')?.value || ''; + const kategori = document.getElementById('filter-warga-kategori')?.value || ''; + const status = document.getElementById('filter-warga-status')?.value || ''; + const rekomendasi = document.getElementById('filter-warga-rekomendasi')?.value || ''; + const container = document.getElementById('warga-list'); + if (!container) return; + const visibleWarga = getWargaForRole(); + const filtered = visibleWarga.filter(d => { + const matchSearch = !search || d.nama_kepala_keluarga.toLowerCase().includes(search.toLowerCase()) || (d.no_kk || '').includes(search) || (d.nik || '').includes(search); + const matchKat = !kategori || (d.kategori_kemiskinan && d.kategori_kemiskinan === kategori); + const matchSts = !status || (d.status_bantuan && d.status_bantuan === status); + const matchRek = !rekomendasi || (d.rekomendasi_bantuan && d.rekomendasi_bantuan === rekomendasi); + return matchSearch && matchKat && matchSts && matchRek; + }); + if (!filtered.length) { container.innerHTML = `
${svgIcon('home', 26, 26)}
Tidak ada data
`; return; } + container.innerHTML = filtered.map(d => { + const rek = REK_CONFIG[d.rekomendasi_bantuan] || REK_CONFIG.tidak_ada; + const cat = d.kategori_kemiskinan ? (KATEGORI_CONFIG[d.kategori_kemiskinan] || KATEGORI_CONFIG.miskin) : null; + const sts = STATUS_CONFIG[d.status_bantuan] || STATUS_CONFIG.belum_terima; + const subInfo = cat + ? `${cat.label}${d.jumlah_tanggungan != null ? ' · ' + d.jumlah_tanggungan + ' jiwa' : ''}` + : `${sts.label}`; + return `
+
${svgIcon('home', 13, 13)}
+
+
${d.nama_kepala_keluarga}
+
${subInfo}
+
+ ${rek.label} +
`; + }).join(''); + } + + function flyToIbadah(id) { + const d = ibadahData.find(x => parseInt(x.id) === parseInt(id)); + if (!d) return; + panToPoint(d.latitude, d.longitude, 16); + setTimeout(() => { + const m = ibadahMarkers[id]; + if (m) { drawRadius(d, m); showInfoIbadah(d); } + }, 600); + } + + function flyToWarga(id) { + const d = wargaData.find(x => parseInt(x.id) === parseInt(id)); + if (!d) return; + panToPoint(d.latitude, d.longitude, 17); + setTimeout(() => showInfoWarga(d), 600); + } + + /* ══════════════════════════════════════════════════ + MODAL HELPERS + ══════════════════════════════════════════════════ */ + function closeModal(id) { + document.getElementById(id).classList.remove('show'); + if (tempMarker) { map.removeLayer(tempMarker); tempMarker = null; } + if (addMode) cancelAddMode(); + if (id === 'modal-warga') { + document.body.classList.remove('side-by-side-modals'); + } + } + + /* ══════════════════════════════════════════════════ + TOAST + ══════════════════════════════════════════════════ */ + function showToast(msg, type = 'info', duration = 3500) { + const container = document.getElementById('toast-container'); + const toast = document.createElement('div'); + toast.className = `toast ${type}`; + const icons = { success: svgIcon('check', 14, 14), error: svgIcon('x', 14, 14), info: svgIcon('info', 14, 14) }; + toast.innerHTML = `${icons[type] || icons.info}${msg}`; + container.appendChild(toast); + setTimeout(() => { toast.style.cssText += ';opacity:0;transform:translateX(16px);transition:all 0.25s'; setTimeout(() => toast.remove(), 250); }, duration); + } + + /* ══════════════════════════════════════════════════ + TABS + ══════════════════════════════════════════════════ */ + function initTabs() { + // Sidebar tabs + document.querySelectorAll('.tab-btn').forEach(btn => { + btn.addEventListener('click', () => { + document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); + document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active')); + btn.classList.add('active'); + document.getElementById('tab-' + btn.dataset.tab).classList.add('active'); + + if (btn.dataset.tab === 'laporan' && window.FiturBaru && FiturBaru.Laporan) { + FiturBaru.Laporan.loadList(); + } + }); + }); + + // Modal sub-tabs — scope ke dalam masing-masing modal + document.querySelectorAll('.modal-overlay').forEach(modalOverlay => { + modalOverlay.querySelectorAll('.modal-tab-btn').forEach(btn => { + btn.addEventListener('click', () => { + modalOverlay.querySelectorAll('.modal-tab-btn').forEach(b => b.classList.remove('active')); + modalOverlay.querySelectorAll('.modal-tab-panel').forEach(p => p.classList.remove('active')); + btn.classList.add('active'); + const panelId = 'mtab-' + btn.dataset.mtab; + const panel = document.getElementById(panelId); + if (panel) panel.classList.add('active'); + }); + }); + }); + } + + /* ══════════════════════════════════════════════════ + SEARCH & FILTER + ══════════════════════════════════════════════════ */ + function initSearchFilter() { + document.getElementById('search-ibadah')?.addEventListener('input', renderIbadahList); + document.getElementById('filter-ibadah-jenis')?.addEventListener('change', renderIbadahList); + document.getElementById('filter-ibadah-reset')?.addEventListener('click', () => { + document.getElementById('search-ibadah').value = ''; + document.getElementById('filter-ibadah-jenis').value = ''; + renderIbadahList(); + }); + + document.getElementById('search-warga')?.addEventListener('input', renderWargaList); + document.getElementById('filter-warga-kategori')?.addEventListener('change', renderWargaList); + document.getElementById('filter-warga-status')?.addEventListener('change', renderWargaList); + document.getElementById('filter-warga-rekomendasi')?.addEventListener('change', renderWargaList); + document.getElementById('filter-warga-reset')?.addEventListener('click', () => { + document.getElementById('search-warga').value = ''; + document.getElementById('filter-warga-kategori').value = ''; + document.getElementById('filter-warga-status').value = ''; + document.getElementById('filter-warga-rekomendasi').value = ''; + renderWargaList(); + }); + + document.getElementById('warga-penghasilan')?.addEventListener('input', previewRek); + document.getElementById('warga-stspekerjaan')?.addEventListener('change', previewRek); + } + + /* ══════════════════════════════════════════════════ + ANGGOTA ROWS + ══════════════════════════════════════════════════ */ + function addAnggotaRow() { + const container = document.getElementById('anggota-form-container'); + const idx = Date.now(); + anggotaRows.push(idx); + container.insertAdjacentHTML('beforeend', buildAnggotaRow({}, idx)); + previewRek(); + } + + function removeAnggotaRow(idx) { + const el = document.getElementById(`arow-${idx}`); + if (el) el.remove(); + anggotaRows = anggotaRows.filter(r => r !== idx); + previewRek(); + } + + async function verifyWargaNoChanges() { + const wargaId = document.getElementById('warga-id').value; + if (!wargaId) return; + try { + const today = new Date().toLocaleDateString('en-CA'); + const res = await fetch('api/verifikasi.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + warga_id: parseInt(wargaId), + petugas: 'Admin', + tanggal: today, + catatan: 'Verifikasi langsung (data sesuai, tidak ada perubahan)' + }) + }); + const json = await res.json(); + if (json.success) { + showToast('Verifikasi berhasil dicatat (Data Sesuai)', 'success'); + closeModal('modal-warga'); + await loadWarga(); + loadStats(); + if (window.FiturBaru && FiturBaru.Verifikasi) { + FiturBaru.Verifikasi.load(document.getElementById('filter-verif-status')?.value || ''); + } + } else { + showToast(json.message || 'Gagal verifikasi', 'error'); + } + } catch (e) { + showToast('Gagal memproses verifikasi', 'error'); + } + } + + /* ══════════════════════════════════════════════════ + PUBLIC API + ══════════════════════════════════════════════════ */ + return { + init() { + initMap(); + initTabs(); + initRadiusControl(); + initSearchFilter(); + + checkAuthStatus(); + + // Bind login submit button + document.getElementById('btn-login-submit')?.addEventListener('click', handleLogin); + + // Support enter key on login modal + document.getElementById('login-password')?.addEventListener('keydown', e => { + if (e.key === 'Enter') handleLogin(); + }); + + // Bind login/logout toggle button + document.getElementById('btn-auth-toggle')?.addEventListener('click', () => { + if (currentUser) { + handleLogout(); + } else { + showAuthModal({ type: 'login' }); + } + }); + + // Bind logout confirmation button + document.getElementById('btn-confirm-logout')?.addEventListener('click', doLogout); + + // Tombol tambah + document.getElementById('btn-add-ibadah')?.addEventListener('click', () => { + checkAuth({ type: 'add_ibadah' }, () => { + setAddMode('ibadah'); + showToast('Klik peta untuk menentukan lokasi Rumah Ibadah', 'info'); + }); + }); + document.getElementById('btn-add-warga')?.addEventListener('click', () => { + checkAuth({ type: 'add_warga' }, () => { + setAddMode('warga'); + showToast('Klik peta untuk menentukan lokasi Rumah Warga', 'info'); + }); + }); + + document.getElementById('btn-add-anggota-row')?.addEventListener('click', addAnggotaRow); + document.getElementById('btn-save-ibadah')?.addEventListener('click', saveIbadah); + document.getElementById('btn-save-warga')?.addEventListener('click', saveWarga); + + document.getElementById('tool-fit')?.addEventListener('click', () => { + const all = [...ibadahData, ...wargaData]; + if (!all.length) return; + map.fitBounds(L.latLngBounds(all.map(d => [d.latitude, d.longitude])), { padding: [40, 40] }); + }); + document.getElementById('tool-clear-radius')?.addEventListener('click', clearRadius); + document.getElementById('info-close')?.addEventListener('click', () => document.getElementById('info-panel').classList.remove('show')); + document.getElementById('radius-info-close')?.addEventListener('click', clearRadius); + + document.querySelectorAll('[data-modal-close]').forEach(btn => { + btn.addEventListener('click', () => closeModal(btn.dataset.modalClose)); + }); + + document.addEventListener('keydown', e => { + if (e.key === 'Escape') { + cancelAddMode(); + document.getElementById('info-panel').classList.remove('show'); + document.querySelectorAll('.modal-overlay').forEach(m => m.classList.remove('show')); + } + }); + }, + + // Exposed untuk onclick di HTML + editIbadah, deleteIbadah, + editWarga, deleteWarga, + flyToIbadah, flyToWarga, + removeAnggotaRow, + previewRek, + verifyWargaNoChanges, + + // Exposed helper functions + getCurrentUser() { return currentUser; }, + checkAuth, + showAuthModal, + doLogout, + handleAuthToggle() { + if (currentUser) { + handleLogout(); + } else { + showAuthModal({ type: 'login' }); + } + }, + getIbadahData() { return ibadahData; }, + getWargaData() { return wargaData; }, + loadWarga, + haversine + }; +})(); + +// Expose App globally +window.App = App; + +document.addEventListener('DOMContentLoaded', () => { + App.init(); + + // Mobile menu toggle + const mobileMenuBtn = document.getElementById('mobile-menu-btn'); + const sidebar = document.getElementById('sidebar'); + let overlay = document.querySelector('.sidebar-overlay'); + + if (!overlay) { + overlay = document.createElement('div'); + overlay.className = 'sidebar-overlay'; + document.body.appendChild(overlay); + } + + if (mobileMenuBtn && sidebar) { + mobileMenuBtn.addEventListener('click', () => { + sidebar.classList.toggle('show'); + overlay.classList.toggle('show'); + }); + + overlay.addEventListener('click', () => { + sidebar.classList.remove('show'); + overlay.classList.remove('show'); + }); + + // Close sidebar on tab click in mobile + document.querySelectorAll('.tab-btn').forEach(btn => { + btn.addEventListener('click', () => { + if (window.innerWidth <= 768) { + sidebar.classList.remove('show'); + overlay.classList.remove('show'); + } + }); + }); + } +}); \ No newline at end of file diff --git a/04/assets/js/fitur_baru.js b/04/assets/js/fitur_baru.js new file mode 100644 index 0000000..1d6888c --- /dev/null +++ b/04/assets/js/fitur_baru.js @@ -0,0 +1,1997 @@ +// assets/js/fitur_baru.js +// Modul 3 Fitur Baru WebGIS Bansos +// Integrasi dengan App (app.js) yang sudah ada. +// Cara pakai: SETELAH app.js + +const FiturBaru = (() => { + + /* ══════════════════════════════════════════════════ + SHARED HELPERS + ══════════════════════════════════════════════════ */ + function svgIcon(paths, w = 14, h = 14) { + return `${paths}`; + } + + const ICONS = { + swap: ``, + shield: ``, + trend: ``, + clock: ``, + check2: ``, + alert: ``, + gear: ``, + exit: ``, + chart: ``, + refresh: ``, + }; + + function formatRp(n) { + return 'Rp ' + parseInt(n || 0).toLocaleString('id-ID'); + } + + function toast(msg, type = 'info') { + // reuse toast dari app.js jika ada + if (window.App && typeof App.showToast === 'function') { + App.showToast(msg, type); + } else { + const c = document.getElementById('toast-container'); + if (!c) return; + const t = document.createElement('div'); + t.className = `toast ${type}`; + t.textContent = msg; + c.appendChild(t); + setTimeout(() => { t.style.opacity = '0'; setTimeout(() => t.remove(), 250); }, 3500); + } + } + + function showModal(id) { document.getElementById(id)?.classList.add('show'); } + function closeModal(id) { document.getElementById(id)?.classList.remove('show'); } + + /* ══════════════════════════════════════════════════ + ░░ FITUR 1: REDISTRIBUSI BANTUAN ░░ + ══════════════════════════════════════════════════ */ + const Redistribusi = (() => { + let analisisData = null; + let saranData = []; + + const BEBAN_CONFIG = { + kosong: { label: 'Kosong', color: '#94a3b8', bg: 'rgba(148,163,184,0.12)' }, + ringan: { label: 'Ringan', color: '#10b981', bg: 'rgba(16,185,129,0.12)' }, + normal: { label: 'Normal', color: '#6366f1', bg: 'rgba(99,102,241,0.12)' }, + padat: { label: 'Padat', color: '#f59e0b', bg: 'rgba(245,158,11,0.12)' }, + overload:{ label: 'Overload', color: '#ef4444', bg: 'rgba(239,68,68,0.12)' }, + }; + + const PRIORITAS_CONFIG = { + tinggi: { label: 'Prioritas Tinggi', cls: 'badge-sangat-miskin' }, + sedang: { label: 'Prioritas Sedang', cls: 'badge-miskin' }, + rendah: { label: 'Prioritas Rendah', cls: 'badge-rentan-miskin' }, + }; + + async function loadAnalisis() { + const container = document.getElementById('redistribusi-analisis'); + if (!container) return; + container.innerHTML = `
${svgIcon(ICONS.refresh, 18, 18)}Menganalisis beban distribusi...
`; + const summaryContainer = document.getElementById('redistribusi-summary-container'); + if (summaryContainer) summaryContainer.innerHTML = ''; + + try { + const res = await fetch('api/redistribusi.php?action=analisis'); + const json = await res.json(); + if (!json.success) throw new Error(json.message); + + analisisData = json.data; + saranData = json.saran; + renderAnalisis(json.data, json.summary); + renderSaran(json.saran); + renderRiwayat(); + } catch (e) { + container.innerHTML = `
${svgIcon(ICONS.alert,16,16)} Gagal memuat: ${e.message}
`; + } + } + + let cachedSummaryHtml = ''; + + function renderAnalisis(data, summary) { + const container = document.getElementById('redistribusi-analisis'); + if (!container) return; + + if (summary) { + const summaryContainer = document.getElementById('redistribusi-summary-container'); + if (summaryContainer) { + summaryContainer.innerHTML = ` +
+ ${Object.entries(summary).filter(([k]) => k !== 'total_ibadah').map(([k, v]) => { + const cfg = BEBAN_CONFIG[k] || { color: '#64748b', bg: 'rgba(100,116,139,0.1)' }; + return `
+ ${v} + ${cfg.label || k} +
`; + }).join('')} +
`; + } + } + + // Filter locally based on search + const searchVal = document.getElementById('redistribusi-search')?.value.toLowerCase() || ''; + const filtered = data ? data.filter(d => { + return !searchVal || + d.ibadah.nama.toLowerCase().includes(searchVal) || + (d.ibadah.kelurahan || '').toLowerCase().includes(searchVal) || + (d.ibadah.kecamatan || '').toLowerCase().includes(searchVal); + }) : []; + + const cardsHtml = filtered.map(d => { + const cfg = BEBAN_CONFIG[d.status_beban] || BEBAN_CONFIG.normal; + const pct = Math.min(d.pct_beban, 100); + return `
+
+
+
${d.ibadah.nama}
+
${d.ibadah.kelurahan || ''} ${d.ibadah.kecamatan ? '· ' + d.ibadah.kecamatan : ''}
+
+ ${cfg.label} +
+
+
+
+
+ ${d.pct_beban}% +
+
+
+ ${d.sangat_miskin} + Sangat Miskin +
+
+ ${d.miskin} + Miskin +
+
+ ${d.rentan_miskin} + Rentan +
+
+ ${d.total_warga}/${d.kapasitas} + KK/Kap. +
+
+
+ Rasio kemiskinan: ${d.rasio_miskin}% + ${d.surplus_kapasitas > 0 ? `· Surplus: +${d.surplus_kapasitas} KK` : ''} + ${d.defisit_kapasitas > 0 ? `· Defisit: -${d.defisit_kapasitas} KK` : ''} +
+
`; + }).join(''); + + container.innerHTML = cardsHtml; + } + + function renderSearch() { + renderAnalisis(analisisData, null); + } + + function renderSaran(saran) { + const container = document.getElementById('redistribusi-saran'); + if (!container) return; + + if (!saran.length) { + container.innerHTML = `
${svgIcon(ICONS.check2,20,20)}
Distribusi sudah merata. Tidak ada saran redistribusi saat ini.
`; + return; + } + + container.innerHTML = saran.map((s, i) => { + const priCfg = PRIORITAS_CONFIG[s.prioritas] || PRIORITAS_CONFIG.rendah; + return `
+
+ ${priCfg.label} + ${s.jarak_km} km +
+
+
${svgIcon(ICONS.swap,12,12)} ${s.pengirim_nama}
+
→ ${s.jumlah_kk} KK →
+
${s.penerima_nama} ${svgIcon(ICONS.alert,12,12)}
+
+
${s.alasan}
+
Est. dana: ${formatRp(s.estimasi_dana)}/bulan
+
+ + +
+
`; + }).join(''); + } + + async function renderRiwayat() { + const container = document.getElementById('redistribusi-riwayat'); + if (!container) return; + try { + const res = await fetch('api/redistribusi.php?action=riwayat&limit=10'); + const json = await res.json(); + if (!json.data.length) { + container.innerHTML = `
Belum ada riwayat redistribusi
`; + return; + } + const STATUS_LABEL = { saran_sistem:'Saran Sistem', disetujui:'Disetujui', ditolak:'Ditolak', selesai:'Selesai' }; + const STATUS_CLS = { saran_sistem:'badge-belum-terima', disetujui:'badge-sudah-terima', ditolak:'badge-sangat-miskin', selesai:'badge-bantuan-rutin' }; + container.innerHTML = json.data.map(r => ` +
+
+
+
${r.pengirim_nama} → ${r.penerima_nama}
+
${r.jumlah_kk} KK · ${formatRp(r.estimasi_dana)}/bln · ${r.periode_bulan || ''}
+
+ ${STATUS_LABEL[r.status] || r.status} +
+ ${r.catatan_admin ? `
${r.catatan_admin}
` : ''} +
`).join(''); + } catch { container.innerHTML = `
Gagal memuat riwayat
`; } + } + + async function setujuiSaran(idx) { + const s = saranData[idx]; + if (!s) return; + + document.getElementById('sr-penerima-nama').textContent = s.penerima_nama; + document.getElementById('sr-penerima-id').value = s.penerima_id; + document.getElementById('sr-catatan-sistem').value = s.alasan; + + // Hitung rasio estimasi dana per KK dari saran (jika ada) + const danaPerKK = s.jumlah_kk > 0 ? (s.estimasi_dana / s.jumlah_kk) : 200000; + document.getElementById('sr-estimasi-dana-per-kk').value = danaPerKK; + + // Populate select dengan rumah ibadah yang surplus (kosong/ringan) + const select = document.getElementById('sr-pengirim-id'); + const surplusList = analisisData.filter(d => d.status_beban === 'kosong' || d.status_beban === 'ringan'); + + select.innerHTML = surplusList.map(d => { + const selected = d.ibadah.id == s.pengirim_id ? 'selected' : ''; + return ``; + }).join(''); + + // Jika saran pengirim tidak ada di list surplus (mungkin list kosong), tambahkan manual + if (select.innerHTML === '') { + select.innerHTML = ``; + } + + // Get eligible warga: dikelola_oleh_ibadah_id == penerima_id or (null and in radius) + const container = document.getElementById('sr-warga-list-container'); + container.innerHTML = '
Memuat warga...
'; + + const ibadahData = window.App ? window.App.getIbadahData() : []; + const wargaData = window.App ? window.App.getWargaData() : []; + const haversine = window.App ? window.App.haversine : null; + + const penerimaNode = ibadahData.find(i => parseInt(i.id) === parseInt(s.penerima_id)); + const pLat = parseFloat(penerimaNode?.latitude || 0); + const pLng = parseFloat(penerimaNode?.longitude || 0); + const pRad = parseFloat(penerimaNode?.radius_primer || 500); + + let eligibleWarga = wargaData.filter(w => { + if (!['sangat_miskin', 'miskin', 'rentan_miskin'].includes(w.kategori_kemiskinan) || w.status_kemandirian === 'mandiri') return false; + + if (w.dikelola_oleh_ibadah_id && parseInt(w.dikelola_oleh_ibadah_id) === parseInt(s.penerima_id)) return true; + if (!w.dikelola_oleh_ibadah_id && typeof haversine === 'function') { + const dist = haversine(parseFloat(w.latitude), parseFloat(w.longitude), pLat, pLng); + if (dist <= pRad) return true; + } + return false; + }); + + if (eligibleWarga.length === 0) { + container.innerHTML = '
Tidak ada warga eligible yang dapat dipindahkan.
'; + } else { + // Render checkboxes + let html = ''; + eligibleWarga.forEach((w, idx) => { + const checked = idx < s.jumlah_kk ? 'checked' : ''; + html += ` +
+ + +
+ `; + }); + container.innerHTML = html; + + // Add event listeners to update count + const cbs = container.querySelectorAll('.sr-warga-cb'); + cbs.forEach(cb => { + cb.addEventListener('change', () => { + const count = container.querySelectorAll('.sr-warga-cb:checked').length; + const inputJml = document.getElementById('sr-jumlah-kk'); + inputJml.value = count; + const total = count * danaPerKK; + document.getElementById('sr-estimasi-dana').value = total; + document.getElementById('sr-estimasi-dana-display').value = formatRp(total); + }); + }); + } + + const inputJml = document.getElementById('sr-jumlah-kk'); + const initialChecked = container.querySelectorAll('.sr-warga-cb:checked').length; + inputJml.value = initialChecked; + + const total = initialChecked * danaPerKK; + document.getElementById('sr-estimasi-dana').value = total; + document.getElementById('sr-estimasi-dana-display').value = formatRp(total); + + document.getElementById('sr-catatan').value = ''; + showModal('modal-setujui-redistribusi'); + } + + async function submitSetujui() { + const pengirimId = document.getElementById('sr-pengirim-id').value; + const penerimaId = document.getElementById('sr-penerima-id').value; + const jumlahKK = document.getElementById('sr-jumlah-kk').value; + const estimasi = document.getElementById('sr-estimasi-dana').value; + const catatan = document.getElementById('sr-catatan').value.trim(); + const alasan = document.getElementById('sr-catatan-sistem').value; + + if (!pengirimId || !jumlahKK || parseInt(jumlahKK) < 1) { + toast('Lengkapi data redistribusi', 'error'); + return; + } + + // Kumpulkan warga yang dipilih (sebelum disable button) + const cbWarga = document.querySelectorAll('.sr-warga-cb:checked'); + const wargaIds = Array.from(cbWarga).map(cb => cb.value); + + const btn = document.querySelector('#modal-setujui-redistribusi .btn-primary'); + const oldHtml = btn.innerHTML; + btn.innerHTML = '
Menyimpan...'; + btn.disabled = true; + + try { + const res = await fetch('api/redistribusi.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + pengirim_id: pengirimId, + penerima_id: penerimaId, + jumlah_kk: jumlahKK, + estimasi_dana: estimasi, + status: 'disetujui', + catatan_sistem: alasan, + catatan_admin: catatan, + periode_bulan: new Date().toLocaleDateString('en-CA').slice(0, 7), + warga_ids: wargaIds, + }), + }); + + let json; + try { json = await res.json(); } catch(e) {} + + if (!res.ok || (json && !json.success)) { + throw new Error(json?.message || 'Gagal menyimpan redistribusi'); + } + toast('Redistribusi disetujui dan disimpan', 'success'); + closeModal('modal-setujui-redistribusi'); + loadAnalisis(); + if (window.App && window.App.loadWarga) window.App.loadWarga(); + } catch (err) { + toast(err.message || 'Gagal menyimpan keputusan', 'error'); + } finally { + btn.innerHTML = oldHtml; + btn.disabled = false; + } + } + + async function tolakSaran(idx) { + const s = saranData[idx]; + if (!s) return; + const catatan = prompt('Alasan penolakan:') ?? 'Ditolak admin'; + try { + await fetch('api/redistribusi.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + pengirim_id: s.pengirim_id, + penerima_id: s.penerima_id, + jumlah_kk: s.jumlah_kk, + estimasi_dana: s.estimasi_dana, + status: 'ditolak', + catatan_sistem:s.alasan, + catatan_admin: catatan, + periode_bulan: new Date().toLocaleDateString('en-CA').slice(0, 7), + }), + }); + toast('Saran ditolak dan dicatat', 'info'); + loadAnalisis(); + } catch { toast('Gagal menyimpan', 'error'); } + } + + return { load: loadAnalisis, renderSearch, setujuiSaran, submitSetujui, tolakSaran, open: () => { showModal('modal-premium-redistribusi'); loadAnalisis(); } }; + })(); + + /* ══════════════════════════════════════════════════ + ░░ FITUR 2: KETERBARUAN DATA ░░ + ══════════════════════════════════════════════════ */ + const Verifikasi = (() => { + let konfig = {}; + + const STATUS_CFG = { + terverifikasi: { label: 'Terverifikasi', color: '#10b981', bg: 'rgba(16,185,129,0.12)' }, + perlu_update: { label: 'Perlu Update', color: '#f59e0b', bg: 'rgba(245,158,11,0.12)' }, + kadaluarsa: { label: 'Kadaluarsa', color: '#ef4444', bg: 'rgba(239,68,68,0.12)' }, + }; + + function skorBar(skor) { + const color = skor >= 80 ? '#10b981' : skor >= 40 ? '#f59e0b' : '#ef4444'; + return `
+
+ ${skor}% +
`; + } + + let allData = []; + + async function load(statusFilter = '') { + const container = document.getElementById('verifikasi-list'); + if (!container) return; + container.innerHTML = `
${svgIcon(ICONS.clock,18,18)}Memuat data verifikasi...
`; + + // Clear search input on load/change + const searchBox = document.getElementById('verifikasi-search'); + if (searchBox) searchBox.value = ''; + + const url = `api/verifikasi.php?action=list${statusFilter ? '&status=' + statusFilter : ''}`; + try { + const res = await fetch(url); + const json = await res.json(); + if (!json.success) throw new Error(json.message); + + // Update summary count + const s = json.summary || {}; + document.getElementById('verif-count-ok')?.setAttribute('data-val', s.terverifikasi || 0); + document.getElementById('verif-count-warn')?.setAttribute('data-val', s.perlu_update || 0); + document.getElementById('verif-count-err')?.setAttribute('data-val', s.kadaluarsa || 0); + ['ok','warn','err'].forEach(k => { + const el = document.getElementById(`verif-count-${k}`); + if (el) el.textContent = el.getAttribute('data-val'); + }); + + allData = json.data || []; + renderList(); + } catch (e) { + container.innerHTML = `
${svgIcon(ICONS.alert,16,16)} ${e.message}
`; + } + } + + function renderList() { + const container = document.getElementById('verifikasi-list'); + if (!container) return; + + const searchVal = document.getElementById('verifikasi-search')?.value.toLowerCase() || ''; + const filtered = allData.filter(w => { + return !searchVal || + w.nama_kepala_keluarga.toLowerCase().includes(searchVal) || + w.no_kk.includes(searchVal) || + (w.kelurahan || '').toLowerCase().includes(searchVal); + }); + + if (!filtered.length) { + container.innerHTML = `
${svgIcon(ICONS.check2,20,20)}
Tidak ada data warga ditemukan
`; + return; + } + + container.innerHTML = filtered.map(w => { + const stsCfg = STATUS_CFG[w.status_verifikasi] || STATUS_CFG.terverifikasi; + const det = w.detail_skor || {}; + return `
+
+
+
${w.nama_kepala_keluarga}
+
${w.kelurahan || ''} · No.KK: ${w.no_kk}
+
+ ${stsCfg.label} +
+ ${skorBar(w.skor_kepercayaan)} +
+ ${svgIcon(ICONS.clock,10,10)} Verif: ${w.tanggal_verifikasi ? new Date(w.tanggal_verifikasi).toLocaleDateString('id-ID') : 'Belum pernah'} + ${det.hari_sejak_verifikasi != null ? `${det.hari_sejak_verifikasi} hari lalu` : ''} + ${det.tanggal_kadaluarsa ? `Exp: ${new Date(det.tanggal_kadaluarsa).toLocaleDateString('id-ID')}` : ''} +
+ +
`; + }).join(''); + } + + function openVerifModal(wargaId, namWarga) { + document.getElementById('verif-modal-nama').textContent = namWarga; + document.getElementById('verif-modal-id').value = wargaId; + document.getElementById('verif-modal-tanggal').value = new Date().toLocaleDateString('en-CA'); + document.getElementById('verif-modal-petugas').value = ''; + document.getElementById('verif-modal-catatan').value = ''; + showModal('modal-verifikasi'); + } + + async function simpanVerifikasi() { + const wargaId = document.getElementById('verif-modal-id').value; + const petugas = document.getElementById('verif-modal-petugas').value.trim(); + const tanggal = document.getElementById('verif-modal-tanggal').value; + const catatan = document.getElementById('verif-modal-catatan').value; + + try { + const res = await fetch('api/verifikasi.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ warga_id: parseInt(wargaId), petugas, tanggal, catatan }), + }); + const json = await res.json(); + if (json.success) { + toast(json.message, 'success'); + closeModal('modal-verifikasi'); + load(document.getElementById('filter-verif-status')?.value || ''); + } else toast(json.message, 'error'); + } catch { toast('Gagal menyimpan verifikasi', 'error'); } + } + + async function loadKonfig() { + try { + const res = await fetch('api/verifikasi.php?action=config'); + const json = await res.json(); + if (!json.success) return; + const container = document.getElementById('konfig-list'); + if (!container) return; + konfig = {}; + json.data.forEach(k => { konfig[k.kunci] = k; }); + container.innerHTML = json.data.map(k => ` +
+ + ${k.deskripsi ? `
${k.deskripsi}
` : ''} + +
`).join(''); + } catch { /* silent */ } + } + + async function simpanKonfig() { + const inputs = document.querySelectorAll('.konfig-input'); + const payload = {}; + inputs.forEach(el => { payload[el.dataset.kunci] = el.value; }); + try { + const res = await fetch('api/verifikasi.php', { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }); + const json = await res.json(); + toast(json.message, json.success ? 'success' : 'error'); + if (json.success) load(); + } catch { toast('Gagal menyimpan konfigurasi', 'error'); } + } + + return { load, renderSearch: renderList, openVerifModal, simpanVerifikasi, loadKonfig, simpanKonfig, open: () => { showModal('modal-premium-verifikasi'); load(); loadKonfig(); } }; + })(); + + /* ══════════════════════════════════════════════════ + ░░ FITUR 3: POTENSI KEMANDIRIAN ░░ + ══════════════════════════════════════════════════ */ + const Kemandirian = (() => { + const STATUS_CFG = { + sangat_bergantung: { label: 'Sangat Bergantung', color: '#ef4444', bg: 'rgba(239,68,68,0.12)', icon: '🔴' }, + bergantung: { label: 'Bergantung', color: '#f87171', bg: 'rgba(248,113,113,0.12)', icon: '🟠' }, + berkembang: { label: 'Berkembang', color: '#f59e0b', bg: 'rgba(245,158,11,0.12)', icon: '🟡' }, + berpotensi_mandiri: { label: 'Berpotensi Mandiri', color: '#6366f1', bg: 'rgba(99,102,241,0.12)', icon: '🔵' }, + mandiri: { label: 'Mandiri / Siap Exit',color: '#10b981', bg: 'rgba(16,185,129,0.12)', icon: '🟢' }, + }; + + function skorGauge(skor, label) { + const deg = Math.round(skor / 100 * 180); + const color = skor >= 80 ? '#10b981' : skor >= 61 ? '#6366f1' : skor >= 41 ? '#f59e0b' : skor >= 21 ? '#f87171' : '#ef4444'; + return `
+ + + + +
${skor}
+
${label}
+
`; + } + + let allData = []; + + async function load(statusFilter = '') { + const container = document.getElementById('kemandirian-list'); + if (!container) return; + container.innerHTML = `
${svgIcon(ICONS.trend,18,18)}Menghitung skor kemandirian...
`; + + // Clear search input on load/change + const searchBox = document.getElementById('kemandirian-search'); + if (searchBox) searchBox.value = ''; + + const url = `api/kemandirian.php?action=list${statusFilter ? '&status=' + statusFilter : ''}`; + try { + const res = await fetch(url); + const json = await res.json(); + if (!json.success) throw new Error(json.message); + + // Update stat cards + const summaryMap = {}; + json.summary.forEach(s => { summaryMap[s.status] = s; }); + document.getElementById('kemandirian-siap-exit').textContent = json.siap_exit || 0; + + allData = json.data || []; + renderList(); + return; + + container.innerHTML = json.data.map(w => { + const stsCfg = STATUS_CFG[w.status_kemandirian] || STATUS_CFG.bergantung; + const det = w.detail_skor || {}; + const estBulan = det.estimasi_bulan_mandiri; + + return `
+
+
+
${w.nama_kepala_keluarga}
+
${formatRp(w.penghasilan_bulanan)}/bln · ${w.jumlah_tanggungan} tanggungan
+
+ ${stsCfg.icon} ${stsCfg.label} +
+ +
+ ${skorGauge(w.skor_kemandirian, 'Total')} + ${det.skor_penghasilan != null ? skorGauge(det.skor_penghasilan, 'Penghasilan') : ''} + ${det.skor_status_kerja != null ? skorGauge(det.skor_status_kerja, 'Kerja') : ''} + ${det.skor_tanggungan != null ? skorGauge(det.skor_tanggungan, 'Tanggungan') : ''} +
+ + ${estBulan > 0 ? `
+ ${svgIcon(ICONS.clock,11,11)} Est. mandiri: ~${estBulan} bulan +
` : w.status_kemandirian === 'mandiri' ? `
+ ${svgIcon(ICONS.check2,11,11)} Siap keluar dari daftar bantuan +
` : ''} + + ${det.rekomendasi?.length ? `
+ ${det.rekomendasi.map(r => `
${svgIcon(ICONS.trend,10,10)} ${r}
`).join('')} +
` : ''} + + ${w.tanggal_exit_plan ? `
+ ${svgIcon(ICONS.exit,10,10)} Target exit: ${new Date(w.tanggal_exit_plan).toLocaleDateString('id-ID')} +
` : ''} + + +
`; + }).join(''); + } catch (e) { + container.innerHTML = `
${svgIcon(ICONS.alert,16,16)} ${e.message}
`; + } + } + + function renderList() { + const container = document.getElementById('kemandirian-list'); + if (!container) return; + + const searchVal = document.getElementById('kemandirian-search')?.value.toLowerCase() || ''; + const filtered = allData.filter(w => { + return !searchVal || + w.nama_kepala_keluarga.toLowerCase().includes(searchVal) || + (w.pekerjaan || '').toLowerCase().includes(searchVal); + }); + + if (!filtered.length) { + container.innerHTML = `
${svgIcon(ICONS.chart,20,20)}
Tidak ada data warga ditemukan
`; + return; + } + + container.innerHTML = filtered.map(w => { + const stsCfg = STATUS_CFG[w.status_kemandirian] || STATUS_CFG.bergantung; + const det = w.detail_skor || {}; + const estBulan = det.estimasi_bulan_mandiri; + + return `
+
+
+
${w.nama_kepala_keluarga}
+
${formatRp(w.penghasilan_bulanan)}/bln · ${w.jumlah_tanggungan} tanggungan
+
+ ${stsCfg.icon} ${stsCfg.label} +
+ +
+ ${skorGauge(w.skor_kemandirian, 'Total')} + ${det.skor_penghasilan != null ? skorGauge(det.skor_penghasilan, 'Penghasilan') : ''} + ${det.skor_status_kerja != null ? skorGauge(det.skor_status_kerja, 'Kerja') : ''} + ${det.skor_tanggungan != null ? skorGauge(det.skor_tanggungan, 'Tanggungan') : ''} +
+ + ${estBulan > 0 ? `
+ ${svgIcon(ICONS.clock,11,11)} Est. mandiri: ~${estBulan} bulan +
` : w.status_kemandirian === 'mandiri' ? `
+ ${svgIcon(ICONS.check2,11,11)} Siap keluar dari daftar bantuan +
` : ''} + + ${det.rekomendasi?.length ? `
+ ${det.rekomendasi.map(r => `
${svgIcon(ICONS.trend,10,10)} ${r}
`).join('')} +
` : ''} + + ${w.tanggal_exit_plan ? `
+ ${svgIcon(ICONS.exit,10,10)} Target exit: ${new Date(w.tanggal_exit_plan).toLocaleDateString('id-ID')} +
` : ''} + + +
`; + }).join(''); + } + + function openCatatanModal(wargaId, nama, exitPlan, catatan) { + document.getElementById('kemandirian-modal-nama').textContent = nama; + document.getElementById('kemandirian-modal-id').value = wargaId; + document.getElementById('kemandirian-modal-exit').value = exitPlan; + document.getElementById('kemandirian-modal-catatan').value = catatan; + showModal('modal-kemandirian'); + } + + async function simpanCatatan() { + const wargaId = document.getElementById('kemandirian-modal-id').value; + const catatan = document.getElementById('kemandirian-modal-catatan').value; + const exitPlan = document.getElementById('kemandirian-modal-exit').value; + + try { + const res = await fetch('api/kemandirian.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + warga_id: parseInt(wargaId), + catatan, + tanggal_exit_plan: exitPlan || null, + }), + }); + const json = await res.json(); + if (json.success) { + toast(json.message, 'success'); + closeModal('modal-kemandirian'); + load(document.getElementById('filter-kemandirian-status')?.value || ''); + } else toast(json.message, 'error'); + } catch { toast('Gagal menyimpan', 'error'); } + } + + return { load, renderSearch: renderList, openCatatanModal, simpanCatatan, open: () => { showModal('modal-premium-kemandirian'); load(); } }; + })(); + + /* ══════════════════════════════════════════════════ + ░░ FITUR 4: KEBUTUHAN & KONTRIBUSI ░░ + ══════════════════════════════════════════════════ */ + const Kebutuhan = (() => { + let kebutuhanData = []; + let activeSubTab = 'daftar'; // 'daftar', 'riwayat', or 'konfirmasi' + + async function load() { + const container = document.getElementById('kebutuhan-panel-content'); + if (!container) return; + + const user = App.getCurrentUser(); + const role = user ? user.role : 'guest'; + + // Enforce default tab based on role transitions + if (activeSubTab === 'riwayat' && role !== 'kontributor') { + activeSubTab = 'daftar'; + } + if (activeSubTab === 'konfirmasi' && !['admin', 'koordinator'].includes(role)) { + activeSubTab = 'daftar'; + } + if (role === 'guest') { + activeSubTab = 'daftar'; + } + + let subTabsHtml = ''; + if (role === 'kontributor') { + subTabsHtml = ` +
+ + +
+ `; + } else if (role === 'koordinator' || role === 'admin') { + subTabsHtml = ` +
+ + +
+ `; + } else if (role === 'guest') { + subTabsHtml = ''; + } + + container.innerHTML = ` + ${subTabsHtml} +
+
Memuat data...
+
+ `; + + if (activeSubTab === 'daftar') { + await loadKebutuhanList(); + } else if (activeSubTab === 'riwayat') { + await loadRiwayatDonasi(); + } else if (activeSubTab === 'konfirmasi') { + await loadVerifikasiDonasi(); + } + } + + // Force refresh Kebutuhan tab if it's currently active + function refreshIfActive() { + const kebutuhanTab = document.getElementById('tab-btn-kebutuhan'); + if (kebutuhanTab && kebutuhanTab.classList.contains('active')) { + load(); + } + } + + async function loadKebutuhanList() { + const subContainer = document.getElementById('kebutuhan-sub-panel-content'); + if (!subContainer) return; + + try { + const res = await fetch('api/kebutuhan.php'); + const json = await res.json(); + if (!json.success) throw new Error(json.message); + + kebutuhanData = json.data || []; + const user = App.getCurrentUser(); + const role = user ? user.role : 'guest'; + + let addBtnHtml = ''; + if (role === 'admin' || role === 'koordinator') { + addBtnHtml = ` + + `; + } + + if (kebutuhanData.length === 0) { + subContainer.innerHTML = ` + ${addBtnHtml} +
Belum ada kebutuhan bantuan terdaftar.
+ `; + return; + } + + const cardsHtml = kebutuhanData.map(k => { + const gathered = parseFloat(k.jumlah_terkumpul || 0); + const required = parseFloat(k.jumlah_dibutuhkan || 0); + const progress = Math.min(100, Math.round((gathered / required) * 100)) || 0; + const sisa = Math.max(0, required - gathered); + const ibadahNama = k.nama_rumah_ibadah || k.nama_ibadah || 'Rumah Ibadah'; + + let actionButtons = ''; + if (role === 'admin' || (role === 'koordinator' && user.rumah_ibadah_id == k.rumah_ibadah_id)) { + actionButtons = ` +
+ + +
+ `; + } else { + if (sisa > 0) { + actionButtons = ` + + `; + } else { + actionButtons = `${svgIcon(ICONS.check2, 10, 10)} Terpenuhi`; + } + } + + const categoryIcons = { + barang: '📦', + uang: '💰', + jasa: '🤝' + }; + const catIcon = categoryIcons[k.kategori] || '📦'; + + return ` +
+
+
+
${ibadahNama}
+
${catIcon} ${k.jenis_bantuan} (${k.kategori})
+
+
+ +
+
+ Terkumpul: ${gathered} / ${required} ${k.satuan ? (k.satuan === 'nominal' ? '(Rp)' : k.satuan) : ''} + ${progress}% +
+
+
+
+
+ + +
+ `; + }).join(''); + + subContainer.innerHTML = addBtnHtml + cardsHtml; + } catch (e) { + subContainer.innerHTML = `
${e.message}
`; + } + } + + async function loadRiwayatDonasi() { + const subContainer = document.getElementById('kebutuhan-sub-panel-content'); + if (!subContainer) return; + + try { + const res = await fetch('api/kontribusi.php'); + const json = await res.json(); + if (!json.success) throw new Error(json.message); + + const list = json.data || []; + if (list.length === 0) { + subContainer.innerHTML = `
Anda belum pernah mengirimkan donasi.
`; + return; + } + + subContainer.innerHTML = list.map(c => { + const statusCls = c.status === 'confirmed' ? 'badge-sudah-terima' : c.status === 'rejected' ? 'badge-sangat-miskin' : 'badge-belum-terima'; + const statusLabel = c.status === 'confirmed' ? 'Disetujui' : c.status === 'rejected' ? 'Ditolak' : 'Menunggu Konfirmasi'; + + return ` +
+
+ ${c.nama_ibadah} + ${statusLabel} +
+
+ Bantuan: ${c.jenis_bantuan} +
+
+ Jumlah: ${c.jumlah} (${c.tipe_kontribusi}) + ${new Date(c.created_at).toLocaleDateString('id-ID')} +
+ ${c.catatan ? `
Catatan: ${c.catatan}
` : ''} +
+ `; + }).join(''); + } catch (e) { + subContainer.innerHTML = `
${e.message}
`; + } + } + + async function loadVerifikasiDonasi() { + const subContainer = document.getElementById('kebutuhan-sub-panel-content'); + if (!subContainer) return; + + try { + const res = await fetch('api/kontribusi.php'); + const json = await res.json(); + if (!json.success) throw new Error(json.message); + + const list = json.data || []; + if (list.length === 0) { + subContainer.innerHTML = `
Tidak ada donasi masuk untuk rumah ibadah Anda.
`; + return; + } + + subContainer.innerHTML = list.map(c => { + const isPending = c.status === 'pending'; + const statusCls = c.status === 'confirmed' ? 'badge-sudah-terima' : c.status === 'rejected' ? 'badge-sangat-miskin' : 'badge-belum-terima'; + const statusLabel = c.status === 'confirmed' ? 'Disetujui' : c.status === 'rejected' ? 'Ditolak' : 'Menunggu Konfirmasi'; + + return ` +
+
+ ${c.nama_kontributor} + ${statusLabel} +
+
+ Ibadah: ${c.nama_ibadah} · Bantuan: ${c.jenis_bantuan} +
+
+ Jumlah: ${c.jumlah} (${c.tipe_kontribusi}) + Contact: ${c.kontak_kontributor || '–'} +
+ ${c.catatan ? `
"${c.catatan}"
` : ''} + ${isPending ? ` +
+ + +
+ ` : ''} +
+ `; + }).join(''); + } catch (e) { + subContainer.innerHTML = `
${e.message}
`; + } + } + + function switchTab(tab) { + activeSubTab = tab; + load(); + } + + function openFormModal(id = null) { + const modal = document.getElementById('modal-kebutuhan-form'); + if (!modal) return; + + // Prevent non-authorized users from opening the form (defense-in-depth) + const currentUser = App.getCurrentUser(); + const role = currentUser ? currentUser.role : 'guest'; + if (!['admin', 'koordinator'].includes(role)) { + toast('Hanya Admin atau Koordinator yang dapat menambah / mengedit kebutuhan.', 'error'); + return; + } + + const titleEl = document.getElementById('modal-kebutuhan-title'); + const idInput = document.getElementById('kebutuhan-id'); + const ibadahSelect = document.getElementById('kebutuhan-ibadah-id'); + const katSelect = document.getElementById('kebutuhan-kategori'); + const jenisInput = document.getElementById('kebutuhan-jenis-bantuan'); + const jumlahInput = document.getElementById('kebutuhan-jumlah-dibutuhkan'); + const ibadahContainer = document.getElementById('kebutuhan-ibadah-select-container'); + + if (ibadahSelect) { + const list = App.getIbadahData() || []; + ibadahSelect.innerHTML = list.map(i => ``).join(''); + } + + const user = App.getCurrentUser(); + if (user && user.role === 'koordinator') { + if (ibadahContainer) ibadahContainer.style.display = 'none'; + if (ibadahSelect) ibadahSelect.value = user.rumah_ibadah_id; + } else { + if (ibadahContainer) ibadahContainer.style.display = 'block'; + } + + if (id) { + titleEl.textContent = 'Edit Kebutuhan Bantuan'; + const d = kebutuhanData.find(x => x.id == id); + if (d) { + idInput.value = d.id; + if (ibadahSelect) ibadahSelect.value = d.rumah_ibadah_id; + katSelect.value = d.kategori; + jenisInput.value = d.jenis_bantuan; + jumlahInput.value = d.jumlah_dibutuhkan; + const satuanSelect = document.getElementById('kebutuhan-satuan'); + if (satuanSelect) satuanSelect.value = d.satuan || (d.kategori === 'uang' ? 'nominal' : (d.kategori === 'jasa' ? 'orang' : 'unit')); + } + } else { + titleEl.textContent = 'Tambah Kebutuhan Bantuan'; + idInput.value = ''; + jenisInput.value = ''; + jumlahInput.value = ''; + const satuanSelect = document.getElementById('kebutuhan-satuan'); + if (satuanSelect) { + // default based on kategori + const defKat = katSelect ? katSelect.value : 'barang'; + satuanSelect.value = defKat === 'uang' ? 'nominal' : (defKat === 'jasa' ? 'orang' : 'unit'); + } + } + + // Ensure kategori change updates jumlah label/hint + const katSelectEl = document.getElementById('kebutuhan-kategori'); + const jumlahGroup = document.getElementById('kebutuhan-jumlah-group'); + const jumlahLabel = document.getElementById('kebutuhan-jumlah-label'); + const jumlahHint = document.getElementById('kebutuhan-jumlah-hint'); + + const satuanSelectEl = document.getElementById('kebutuhan-satuan'); + + function updateJumlahVisibility() { + const val = katSelectEl ? katSelectEl.value : 'barang'; + if (val === 'uang') { + // For monetary needs, show amount and hint + if (jumlahGroup) jumlahGroup.style.display = ''; + if (jumlahLabel) jumlahLabel.textContent = 'Jumlah Dibutuhkan (nominal dalam satuan lokal) *'; + if (jumlahHint) jumlahHint.textContent = 'Masukkan jumlah dana yang dibutuhkan (angka, tanpa pemisah).'; + if (satuanSelectEl) satuanSelectEl.value = 'nominal'; + } else if (val === 'jasa') { + // For jasa, show count of tenaga/relawan needed + if (jumlahGroup) jumlahGroup.style.display = ''; + if (jumlahLabel) jumlahLabel.textContent = 'Jumlah Tenaga / Relawan Dibutuhkan *'; + if (jumlahHint) jumlahHint.textContent = 'Masukkan jumlah tenaga/relawan yang diperlukan.'; + if (satuanSelectEl) satuanSelectEl.value = 'orang'; + } else { + // barang + if (jumlahGroup) jumlahGroup.style.display = ''; + if (jumlahLabel) jumlahLabel.textContent = 'Jumlah Dibutuhkan *'; + if (jumlahHint) jumlahHint.textContent = 'Untuk barang: jumlah unit yang dibutuhkan.'; + if (satuanSelectEl) satuanSelectEl.value = 'unit'; + } + } + + if (katSelectEl) { + katSelectEl.removeEventListener('change', updateJumlahVisibility); + katSelectEl.addEventListener('change', updateJumlahVisibility); + } + updateJumlahVisibility(); + + modal.classList.add('show'); + } + + async function saveKebutuhan() { + const id = document.getElementById('kebutuhan-id').value; + const ibadahId = document.getElementById('kebutuhan-ibadah-id').value; + const kategori = document.getElementById('kebutuhan-kategori').value; + const jenis = document.getElementById('kebutuhan-jenis-bantuan').value.trim(); + const jumlah = document.getElementById('kebutuhan-jumlah-dibutuhkan').value; + const satuan = document.getElementById('kebutuhan-satuan') ? document.getElementById('kebutuhan-satuan').value : 'unit'; + + if (!jenis || jumlah === '' || jumlah === null) { + toast('Semua input wajib diisi!', 'error'); + return; + } + + // Normalize kategori to allowed DB values + const allowed = ['barang', 'uang', 'jasa']; + const kategoriNorm = allowed.includes(kategori) ? kategori : 'barang'; + + const method = id ? 'PUT' : 'POST'; + const body = { rumah_ibadah_id: parseInt(ibadahId), kategori: kategoriNorm, jenis_bantuan: jenis, jumlah_dibutuhkan: parseFloat(jumlah), satuan }; + if (id) body.id = parseInt(id); + + try { + const res = await fetch('api/kebutuhan.php', { + method, + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + const json = await res.json(); + if (json.success) { + toast(json.message, 'success'); + closeModal('modal-kebutuhan-form'); + load(); + } else { + toast(json.message, 'error'); + } + } catch (e) { + toast('Gagal menyimpan kebutuhan', 'error'); + } + } + + async function deleteKebutuhan(id) { + if (!confirm('Apakah Anda yakin ingin menghapus kebutuhan ini?')) return; + + try { + const res = await fetch(`api/kebutuhan.php?id=${id}`, { method: 'DELETE' }); + const json = await res.json(); + if (json.success) { + toast(json.message, 'success'); + load(); + } else { + toast(json.message, 'error'); + } + } catch (e) { + toast('Gagal menghapus kebutuhan', 'error'); + } + } + + function triggerDonation(kebutuhanId, sisa, jenis, ibadahNama) { + App.checkAuth({ type: 'donate', kebutuhan_id: kebutuhanId, sisa, jenis, ibadah_nama: ibadahNama }, () => { + openDonationModal(kebutuhanId, sisa, jenis, ibadahNama); + }); + } + + function openDonationModal(kebutuhanId, sisa, jenis, ibadahNama) { + const modal = document.getElementById('modal-kontribusi-form'); + if (!modal) return; + + document.getElementById('kontribusi-kebutuhan-id').value = kebutuhanId; + document.getElementById('kontribusi-target-ibadah').textContent = ibadahNama; + document.getElementById('kontribusi-target-detail').textContent = `Bantuan: ${jenis} (Sisa: ${sisa})`; + + const countInput = document.getElementById('kontribusi-jumlah'); + countInput.value = ''; + countInput.max = sisa; + + const maxHint = document.getElementById('kontribusi-max-hint'); + if (maxHint) maxHint.textContent = `Batas Maksimal: ${sisa}`; + + const nameInput = document.getElementById('kontribusi-nama'); + const contactInput = document.getElementById('kontribusi-kontak'); + const typeSelect = document.getElementById('kontribusi-tipe'); + + const user = App.getCurrentUser(); + if (user) { + nameInput.value = user.username || ''; + nameInput.readOnly = true; + contactInput.value = user.no_telp || ''; + contactInput.readOnly = true; + } else { + nameInput.value = ''; + nameInput.readOnly = false; + contactInput.value = ''; + contactInput.readOnly = false; + } + + // Auto-map contribution type based on need category + const d = kebutuhanData.find(x => x.id == kebutuhanId); + let mappedType = 'barang'; // default + if (d) { + if (d.kategori === 'uang') { + mappedType = 'uang'; + } else if (['jasa', 'tenaga', 'relawan'].includes(d.kategori) || d.jenis_bantuan.toLowerCase().includes('relawan') || d.jenis_bantuan.toLowerCase().includes('tenaga') || d.jenis_bantuan.toLowerCase().includes('jasa')) { + mappedType = 'tenaga'; + } + } + if (typeSelect) { + typeSelect.value = mappedType; + typeSelect.disabled = true; // Lock it to prevent manual modification + } + + document.getElementById('kontribusi-catatan').value = ''; + + modal.classList.add('show'); + } + + async function saveDonation() { + const kebutuhanId = document.getElementById('kontribusi-kebutuhan-id').value; + const nama = document.getElementById('kontribusi-nama').value.trim(); + const kontak = document.getElementById('kontribusi-kontak').value.trim(); + const tipe = document.getElementById('kontribusi-tipe').value; + const jumlah = parseFloat(document.getElementById('kontribusi-jumlah').value); + const catatan = document.getElementById('kontribusi-catatan').value.trim(); + + if (!nama || !kontak || isNaN(jumlah) || jumlah <= 0) { + toast('Nama, Kontak, dan Jumlah Donasi wajib diisi!', 'error'); + return; + } + + const maxLimit = parseFloat(document.getElementById('kontribusi-jumlah').max); + if (jumlah > maxLimit) { + toast(`Jumlah kontribusi tidak boleh melebihi sisa kebutuhan (${maxLimit})!`, 'error'); + return; + } + + try { + const res = await fetch('api/kontribusi.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + kebutuhan_id: parseInt(kebutuhanId), + nama_kontributor: nama, + kontak_kontributor: kontak, + tipe_kontribusi: tipe, + jumlah, + catatan + }) + }); + const json = await res.json(); + if (json.success) { + toast(json.message, 'success'); + closeModal('modal-kontribusi-form'); + load(); + } else { + toast(json.message, 'error'); + } + } catch (e) { + toast('Gagal mengirimkan donasi', 'error'); + } + } + + async function verifyDonasi(id, status) { + if (!confirm(`Apakah Anda yakin ingin ${status === 'confirmed' ? 'menyetujui' : 'menolak'} donasi ini?`)) return; + + try { + const res = await fetch('api/kontribusi.php', { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ id: parseInt(id), status }) + }); + const json = await res.json(); + if (json.success) { + toast(json.message, 'success'); + load(); + } else { + toast(json.message, 'error'); + } + } catch (e) { + toast('Gagal memverifikasi donasi', 'error'); + } + } + + return { + load, + switchTab, + openFormModal, + saveKebutuhan, + deleteKebutuhan, + triggerDonation, + openDonationModal, + saveDonation, + verifyDonasi, + refreshIfActive + }; + })(); + + /* ══════════════════════════════════════════════════ + INISIALISASI TAB & EVENT LISTENER + ══════════════════════════════════════════════════ */ + function init() { + // Tambah tab baru ke sidebar jika belum ada + injectSidebarTabs(); + injectModals(); + bindEvents(); + Kebutuhan.load(); + } + + function injectSidebarTabs() { + // 1. Inject the three feature modals directly to body + document.body.insertAdjacentHTML('beforeend', buildRedistribusiPanel()); + document.body.insertAdjacentHTML('beforeend', buildVerifikasiPanel()); + document.body.insertAdjacentHTML('beforeend', buildKemandirianPanel()); + + // 2. Inject the advanced features section inside Overview tab + const overviewTab = document.getElementById('tab-overview'); + if (overviewTab) { + const fiturHtml = ` + +
+ ${svgIcon(ICONS.gear, 12, 12)} + Fitur Lanjutan +
+
+
+
+
+ ${svgIcon(ICONS.swap, 12, 12)} + Redistribusi Bantuan +
+
+ Analisis beban distribusi & rekomendasi transfer bantuan antar rumah ibadah. +
+
+
+ +
+
+
+ ${svgIcon(ICONS.shield, 12, 12)} + Keterbaruan & Verifikasi Data +
+
+ Pantau waktu verifikasi, skor kepercayaan data, dan rekam kunjungan lapangan. +
+
+
+ +
+
+
+ ${svgIcon(ICONS.trend, 12, 12)} + Potensi Kemandirian Warga +
+
+ Skor kemandirian warga, grafik gauge interaktif, dan penentuan exit plan. +
+
+
+
+ `; + const legendaTitle = document.getElementById('section-legenda'); + if (legendaTitle) { + legendaTitle.insertAdjacentHTML('beforebegin', fiturHtml); + } else { + overviewTab.insertAdjacentHTML('beforeend', fiturHtml); + } + } + } + + /* ─── Premium Overlay Modals HTML Builders ─── */ + function buildRedistribusiPanel() { + return ``; + } + + function buildVerifikasiPanel() { + return ``; + } + + function buildKemandirianPanel() { + return ``; + } + + /* ─── Modal HTML ─── */ + function injectModals() { + document.body.insertAdjacentHTML('beforeend', ` + + + + + + `); + + // Modal Setujui Redistribusi + document.body.insertAdjacentHTML('beforeend', ` + + `); + } + + function bindEvents() { + // Keyboard close + document.addEventListener('keydown', e => { + if (e.key === 'Escape') { + closeModal('modal-verifikasi'); + closeModal('modal-kemandirian'); + closeModal('modal-setujui-redistribusi'); + closeModal('modal-premium-redistribusi'); + closeModal('modal-premium-verifikasi'); + closeModal('modal-premium-kemandirian'); + closeModal('modal-kebutuhan-form'); + closeModal('modal-kontribusi-form'); + closeModal('modal-laporan-form'); + document.body.classList.remove('side-by-side-modals'); + } + }); + + document.getElementById('btn-kebutuhan-save')?.addEventListener('click', () => Kebutuhan.saveKebutuhan()); + document.getElementById('btn-kontribusi-save')?.addEventListener('click', () => Kebutuhan.saveDonation()); + } + + /* ─── Sub-tab switcher (global) ─── */ + window.switchFbTab = function(btn, prefix) { + const tabId = btn.dataset.fbtab; + const scope = document.getElementById(`modal-premium-${prefix}`) || document.getElementById(`tab-${prefix}`); + if (!scope) return; + scope.querySelectorAll('.fb-sub-tab').forEach(b => b.classList.toggle('active', b.dataset.fbtab === tabId)); + scope.querySelectorAll('.fb-sub-panel').forEach(p => { + p.classList.toggle('active', p.id === `${prefix}-${tabId}-wrap`); + }); + }; + + /* ══════════════════════════════════════════════════ + MODUL LAPORAN WARGA + ══════════════════════════════════════════════════ */ + const Laporan = { + async loadList() { + const currentUser = App.getCurrentUser(); + if (!currentUser || currentUser.role === 'guest') return; + + const panel = document.getElementById('laporan-panel-content'); + if (!panel) return; + + const isAdminOrPetugas = currentUser.role === 'admin' || currentUser.role === 'petugas'; + + try { + const res = await fetch('api/laporan.php?_t=' + Date.now()); + const json = await res.json(); + + if (!json.success) throw new Error(json.message); + + let petugasList = []; + if (isAdminOrPetugas && json.petugas) { + petugasList = json.petugas; + } + + let html = ''; + + if (isAdminOrPetugas) { + html += ` +
+ Daftar Laporan Warga Masuk (Total: ${json.total}) + +
+ `; + } else { + html += ` +
+ Riwayat Laporan Saya +
+ `; + } + + if (!json.data || json.data.length === 0) { + html += `
${svgIcon(ICONS.info, 28, 28)}
Belum ada laporan.
`; + panel.innerHTML = html; + return; + } + + let listHtml = '
'; + json.data.forEach(l => { + const jenisLabel = { + 'warga_mampu': 'Warga Mampu', + 'data_tidak_sesuai': 'Data Tidak Sesuai', + 'warga_pindah': 'Warga Pindah', + 'warga_meninggal': 'Warga Meninggal', + 'data_ganda': 'Data Ganda', + 'lainnya': 'Lainnya' + }[l.jenis_laporan] || l.jenis_laporan; + + const st = l.status; + const statusBadge = `${st.replace('_', ' ').toUpperCase()}`; + const pr = l.prioritas; + const prioBadge = `PRIORITAS ${pr.toUpperCase()}`; + + const date = new Date(l.created_at).toLocaleDateString('id-ID', {day:'numeric', month:'short', year:'numeric', hour:'2-digit', minute:'2-digit'}); + + let petugasOpts = ''; + petugasList.forEach(p => { + petugasOpts += ``; + }); + + listHtml += ` +
+
+
+
${l.nama_kepala_keluarga}
+
${l.kelurahan || '-'}, ${l.kecamatan || '-'}
+
+ ${statusBadge} +
+
${jenisLabel}
+
" ${l.deskripsi} "
+ + `; + + if (l.dokumentasi) { + listHtml += `
Link Dokumentasi Lapangan: ${l.dokumentasi}
`; + } + + if (isAdminOrPetugas) { + listHtml += ` +
+ + ${currentUser.role === 'admin' ? `` : ''} +
+ + `; + } + + listHtml += `
`; + }); + listHtml += '
'; + + html += listHtml; + panel.innerHTML = html; + + } catch (err) { + panel.innerHTML = `
${svgIcon(ICONS.info, 28, 28)}
Gagal memuat laporan.
`; + } + }, + + filterUI(status) { + const cards = document.querySelectorAll('#laporan-data-list .laporan-card'); + cards.forEach(c => { + if (status === 'all') c.style.display = 'block'; + else if (c.dataset.status === status) c.style.display = 'block'; + else c.style.display = 'none'; + }); + }, + + bukaModalLaporan(wargaId, namaWarga) { + document.getElementById('laporan-warga-id').value = wargaId; + document.getElementById('laporan-warga-nama-display').textContent = 'Melaporkan Data: ' + namaWarga; + document.getElementById('laporan-jenis').value = 'data_tidak_sesuai'; + document.getElementById('laporan-prioritas').value = 'sedang'; + document.getElementById('laporan-deskripsi').value = ''; + document.getElementById('modal-laporan-form').classList.add('show'); + }, + + async submitLaporan() { + const wargaId = document.getElementById('laporan-warga-id').value; + const jenis = document.getElementById('laporan-jenis').value; + const prioritas = document.getElementById('laporan-prioritas').value; + const deskripsi = document.getElementById('laporan-deskripsi').value.trim(); + + if (!deskripsi || deskripsi.length < 20) { + showToast('Deskripsi wajib diisi minimal 20 karakter', 'error'); + return; + } + + const btn = document.getElementById('btn-laporan-submit'); + btn.disabled = true; + btn.innerHTML = 'Mengirim...'; + + try { + const res = await fetch('api/laporan.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + rumah_warga_id: wargaId, + jenis_laporan: jenis, + prioritas: prioritas, + deskripsi: deskripsi + }) + }); + const json = await res.json(); + + if (json.success) { + showToast(json.message, 'success'); + closeModal('modal-laporan-form'); + // Reload list jika sedang membuka tab laporan + if (document.getElementById('tab-laporan')?.classList.contains('active')) { + this.loadList(); + } + } else { + showToast(json.message || 'Gagal mengirim laporan', 'error'); + } + } catch (err) { + showToast('Terjadi kesalahan koneksi', 'error'); + } finally { + btn.disabled = false; + btn.innerHTML = ` Kirim Laporan`; + } + }, + + async updateStatus(id) { + const btn = event.currentTarget || event.target; + const originalText = btn.innerHTML; + btn.innerHTML = 'Menyimpan...'; + btn.disabled = true; + + const status = document.getElementById(`lap-status-${id}`).value; + const catatanEl = document.getElementById(`lap-catatan-${id}`); + const catatan = catatanEl ? catatanEl.value.trim() : ''; + + const fileInput = document.getElementById(`lap-dokumentasi-${id}`); + + let ditugaskan_ke = ''; + const petugasSelect = document.getElementById(`lap-petugas-${id}`); + if (petugasSelect && status === 'diproses') { + ditugaskan_ke = petugasSelect.value; + } + + const formData = new FormData(); + formData.append('_method', 'PUT'); + formData.append('id', id); + formData.append('status', status); + formData.append('catatan_admin', catatan); + formData.append('ditugaskan_ke', ditugaskan_ke); + + if (fileInput && fileInput.files.length > 0) { + formData.append('dokumentasi_file', fileInput.files[0]); + } + + try { + const res = await fetch('api/laporan.php', { + method: 'POST', + body: formData + }); + const json = await res.json(); + + if (json.success) { + if (typeof showToast === 'function') showToast('Data berhasil diperbarui!', 'success'); + else alert('Data berhasil diperbarui!'); + this.loadList(); + } else { + if (typeof showToast === 'function') showToast(json.message || 'Gagal memperbarui', 'error'); + else alert(json.message || 'Gagal memperbarui'); + } + } catch (err) { + if (typeof showToast === 'function') showToast('Terjadi kesalahan saat mengupdate', 'error'); + else alert('Terjadi kesalahan saat mengupdate'); + } finally { + if (btn) { + btn.innerHTML = originalText; + btn.disabled = false; + } + } + }, + + async hapus(id) { + if (!confirm('Apakah Anda yakin ingin menghapus laporan ini?')) return; + try { + const res = await fetch('api/laporan.php', { + method: 'DELETE', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ id }) + }); + const json = await res.json(); + if (json.success) { + showToast('Laporan dihapus', 'success'); + this.loadList(); + } else { + showToast(json.message || 'Gagal menghapus', 'error'); + } + } catch (err) { + showToast('Terjadi kesalahan', 'error'); + } + } + }; + + /* ══════════════════════════════════════════════════ + PUBLIC API + ══════════════════════════════════════════════════ */ + return { init, Redistribusi, Verifikasi, Kemandirian, Kebutuhan, Laporan }; +})(); + +// Expose FiturBaru globally +window.FiturBaru = FiturBaru; + +document.addEventListener('DOMContentLoaded', () => FiturBaru.init()); \ No newline at end of file diff --git a/04/config/db.php b/04/config/db.php new file mode 100644 index 0000000..e22d5ee --- /dev/null +++ b/04/config/db.php @@ -0,0 +1,74 @@ +set_charset('utf8mb4'); + if ($conn->connect_error) { + throw new Exception($conn->connect_error); + } + // Self-healing migration for manual recommendation column + try { + $checkCol = $conn->query("SHOW COLUMNS FROM `rumah_warga` LIKE 'is_rekomendasi_manual'"); + if ($checkCol && $checkCol->num_rows === 0) { + $conn->query("ALTER TABLE `rumah_warga` ADD COLUMN `is_rekomendasi_manual` TINYINT(1) NOT NULL DEFAULT 0 AFTER `rekomendasi_bantuan`"); + } + + $checkColKat = $conn->query("SHOW COLUMNS FROM `rumah_warga` LIKE 'is_kategori_manual'"); + if ($checkColKat && $checkColKat->num_rows === 0) { + $conn->query("ALTER TABLE `rumah_warga` ADD COLUMN `is_kategori_manual` TINYINT(1) NOT NULL DEFAULT 0 AFTER `kategori_kemiskinan`"); + } + + $checkColDikelola = $conn->query("SHOW COLUMNS FROM `rumah_warga` LIKE 'dikelola_oleh_ibadah_id'"); + if ($checkColDikelola && $checkColDikelola->num_rows === 0) { + $conn->query("ALTER TABLE `rumah_warga` ADD COLUMN `dikelola_oleh_ibadah_id` INT NULL DEFAULT NULL AFTER `keterangan`"); + } + } catch (Exception $ex) {} + + // Self-healing migration for no_telp column in users table + try { + $checkColUsers = $conn->query("SHOW COLUMNS FROM `users` LIKE 'no_telp'"); + if ($checkColUsers && $checkColUsers->num_rows === 0) { + $conn->query("ALTER TABLE `users` ADD COLUMN `no_telp` VARCHAR(20) NULL DEFAULT NULL AFTER `email`"); + } + } catch (Exception $ex) {} + + // Self-healing migration for RBAC & Kebutuhan tables + try { + $checkUsers = $conn->query("SHOW TABLES LIKE 'users'"); + if ($checkUsers && $checkUsers->num_rows === 0) { + $sqlFile = dirname(__DIR__) . '/database/migrations.sql'; + if (file_exists($sqlFile)) { + $sqlContent = file_get_contents($sqlFile); + if ($conn->multi_query($sqlContent)) { + do { + if ($result = $conn->store_result()) { + $result->free(); + } + } while ($conn->more_results() && $conn->next_result()); + } + } + } + } catch (Exception $ex) {} +} catch (Exception $e) { + http_response_code(500); + die(json_encode([ + 'success' => false, + 'message' => 'Koneksi database MySQLi gagal: ' . $e->getMessage() . '. Pastikan XAMPP Apache dan MySQL Anda aktif, serta database "bansos_gis" telah dibuat di phpMyAdmin.' + ])); +} + +// New PDO connection used by new APIs (redistribusi, etc.) +try { + $pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $user, $pass, [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC + ]); +} catch (PDOException $e) { + // Set to null — APIs that require PDO must check and throw themselves + $pdo = null; +} \ No newline at end of file diff --git a/04/database/bansos_gis.sql b/04/database/bansos_gis.sql new file mode 100644 index 0000000..c1a20b8 --- /dev/null +++ b/04/database/bansos_gis.sql @@ -0,0 +1,364 @@ +-- phpMyAdmin SQL Dump +-- version 5.2.1 +-- https://www.phpmyadmin.net/ +-- +-- Host: 127.0.0.1 +-- Generation Time: May 17, 2026 at 01:04 PM +-- Server version: 10.4.32-MariaDB +-- PHP Version: 8.2.12 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +START TRANSACTION; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `bansos_gis` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `anggota_keluarga` +-- + +CREATE TABLE `anggota_keluarga` ( + `id` int(11) NOT NULL, + `rumah_warga_id` int(11) NOT NULL, + `nama` varchar(200) NOT NULL, + `nik` varchar(16) DEFAULT NULL, + `tanggal_lahir` date DEFAULT NULL, + `jenis_kelamin` enum('laki_laki','perempuan') DEFAULT 'laki_laki', + `pendidikan` enum('tidak_sekolah','sd','smp','sma','d3','s1','s2','s3') DEFAULT 'sd', + `pekerjaan` varchar(100) DEFAULT NULL, + `status_bekerja` enum('bekerja','tidak_bekerja','pelajar','pensiunan') DEFAULT 'tidak_bekerja', + `kondisi_kesehatan` enum('sehat','sakit_ringan','sakit_berat','disabilitas') DEFAULT 'sehat', + `dapat_bekerja` tinyint(1) DEFAULT 1, + `created_at` timestamp NOT NULL DEFAULT current_timestamp(), + `updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp() +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- +-- Dumping data for table `anggota_keluarga` +-- + +INSERT INTO `anggota_keluarga` (`id`, `rumah_warga_id`, `nama`, `nik`, `tanggal_lahir`, `jenis_kelamin`, `pendidikan`, `pekerjaan`, `status_bekerja`, `kondisi_kesehatan`, `dapat_bekerja`, `created_at`, `updated_at`) VALUES +(1, 1, 'Budi Santoso', '1234567890123456', '1985-06-15', 'laki_laki', 'smp', 'Buruh Harian', 'bekerja', 'sehat', 1, '2026-05-17 06:06:37', '2026-05-17 06:06:37'), +(2, 1, 'Rina Santoso', '1234567890123457', '1988-03-20', 'perempuan', 'sd', 'Ibu Rumah Tangga', 'tidak_bekerja', 'sehat', 1, '2026-05-17 06:06:37', '2026-05-17 06:06:37'), +(3, 1, 'Doni Santoso', '1234567890123458', '2008-11-10', 'laki_laki', 'smp', NULL, 'pelajar', 'sehat', 0, '2026-05-17 06:06:37', '2026-05-17 06:06:37'), +(4, 1, 'Dewi Santoso', '1234567890123459', '2012-07-25', 'perempuan', 'sd', NULL, 'pelajar', 'sehat', 0, '2026-05-17 06:06:37', '2026-05-17 06:06:37'), +(5, 2, 'Siti Aminah', '9876543210987654', '1970-01-12', 'perempuan', 'sd', NULL, 'tidak_bekerja', 'sakit_ringan', 0, '2026-05-17 06:06:37', '2026-05-17 06:06:37'), +(6, 2, 'Ahmad Firdaus', '9876543210987655', '2005-09-30', 'laki_laki', 'sma', NULL, 'pelajar', 'sehat', 1, '2026-05-17 06:06:37', '2026-05-17 06:06:37'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `konfigurasi_sistem` +-- + +CREATE TABLE `konfigurasi_sistem` ( + `id` int(11) NOT NULL, + `kunci` varchar(100) NOT NULL COMMENT 'key konfigurasi', + `nilai` text NOT NULL COMMENT 'value konfigurasi', + `label` varchar(200) DEFAULT NULL COMMENT 'label tampil di UI', + `deskripsi` text DEFAULT NULL, + `tipe` enum('integer','decimal','string','boolean','json') DEFAULT 'string', + `updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp() +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- +-- Dumping data for table `konfigurasi_sistem` +-- + +INSERT INTO `konfigurasi_sistem` (`id`, `kunci`, `nilai`, `label`, `deskripsi`, `tipe`, `updated_at`) VALUES +(1, 'interval_verifikasi_bulan', '6', 'Interval Verifikasi Data (Bulan)', 'Berapa bulan sekali data rumah warga harus diverifikasi ulang', 'integer', '2026-05-17 10:41:31'), +(2, 'skor_data_kadaluarsa', '40', 'Skor Minimum Data Valid (%)', 'Data dengan skor kepercayaan di bawah nilai ini dianggap perlu reverifikasi', 'integer', '2026-05-17 10:41:31'), +(3, 'threshold_penghasilan_mandiri', '3500000', 'Threshold Penghasilan Mandiri (Rp)', 'Penghasilan bulanan minimum agar warga dianggap berpotensi mandiri (default = UMR)', 'decimal', '2026-05-17 10:41:31'), +(4, 'bobot_penghasilan', '40', 'Bobot Skor: Penghasilan (%)', 'Kontribusi penghasilan terhadap skor kemandirian (total 3 bobot harus = 100)', 'integer', '2026-05-17 10:41:31'), +(5, 'bobot_status_kerja', '35', 'Bobot Skor: Status Kerja (%)', 'Kontribusi status pekerjaan anggota terhadap skor kemandirian', 'integer', '2026-05-17 10:41:31'), +(6, 'bobot_tanggungan', '25', 'Bobot Skor: Tanggungan (%)', 'Kontribusi jumlah tanggungan terhadap skor kemandirian', 'integer', '2026-05-17 10:41:31'), +(7, 'threshold_skor_mandiri', '70', 'Threshold Skor Kemandirian (%)', 'Skor minimum agar warga masuk kategori \"Berpotensi Mandiri\"', 'integer', '2026-05-17 10:41:31'), +(8, 'kapasitas_default_ibadah', '20', 'Kapasitas Default per Rumah Ibadah (KK)', 'Jumlah KK yang idealnya ditangani 1 rumah ibadah jika tidak diset manual', 'integer', '2026-05-17 10:41:31'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `redistribusi_bantuan` +-- + +CREATE TABLE `redistribusi_bantuan` ( + `id` int(11) NOT NULL, + `ibadah_pengirim_id` int(11) NOT NULL COMMENT 'Rumah ibadah yang punya surplus', + `ibadah_penerima_id` int(11) NOT NULL COMMENT 'Rumah ibadah yang butuh tambahan', + `jumlah_kk` smallint(5) UNSIGNED DEFAULT 0 COMMENT 'Jumlah KK yang dialihkan', + `estimasi_dana` decimal(15,2) DEFAULT 0.00 COMMENT 'Estimasi dana yang ikut dialihkan', + `status` enum('saran_sistem','disetujui','ditolak','selesai') DEFAULT 'saran_sistem', + `catatan_sistem` text DEFAULT NULL COMMENT 'Alasan/penjelasan dari sistem', + `catatan_admin` text DEFAULT NULL COMMENT 'Catatan keputusan admin', + `disetujui_oleh` varchar(150) DEFAULT NULL, + `tanggal_saran` timestamp NOT NULL DEFAULT current_timestamp(), + `tanggal_keputusan` timestamp NULL DEFAULT NULL, + `periode_bulan` char(7) DEFAULT NULL COMMENT 'Format YYYY-MM, periode redistribusi' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `riwayat_kemandirian` +-- + +CREATE TABLE `riwayat_kemandirian` ( + `id` int(11) NOT NULL, + `rumah_warga_id` int(11) NOT NULL, + `tanggal` date NOT NULL, + `skor` tinyint(3) UNSIGNED DEFAULT 0, + `status` enum('sangat_bergantung','bergantung','berkembang','berpotensi_mandiri','mandiri') DEFAULT 'bergantung', + `penghasilan_saat_ini` decimal(15,2) DEFAULT 0.00, + `catatan` text DEFAULT NULL, + `created_at` timestamp NOT NULL DEFAULT current_timestamp() +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `riwayat_verifikasi` +-- + +CREATE TABLE `riwayat_verifikasi` ( + `id` int(11) NOT NULL, + `rumah_warga_id` int(11) NOT NULL, + `tanggal` date NOT NULL, + `petugas` varchar(150) DEFAULT NULL, + `skor_sebelum` tinyint(3) UNSIGNED DEFAULT NULL, + `skor_sesudah` tinyint(3) UNSIGNED DEFAULT NULL, + `perubahan` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'Field yang berubah saat verifikasi' CHECK (json_valid(`perubahan`)), + `catatan` text DEFAULT NULL, + `created_at` timestamp NOT NULL DEFAULT current_timestamp() +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `rumah_ibadah` +-- + +CREATE TABLE `rumah_ibadah` ( + `id` int(11) NOT NULL, + `nama` varchar(200) NOT NULL, + `jenis` enum('masjid','gereja','klenteng','pura','vihara','lainnya') NOT NULL DEFAULT 'masjid', + `alamat` text DEFAULT NULL, + `kelurahan` varchar(100) DEFAULT NULL, + `kecamatan` varchar(100) DEFAULT NULL, + `kota` varchar(100) DEFAULT NULL, + `latitude` decimal(10,8) NOT NULL, + `longitude` decimal(11,8) NOT NULL, + `penanggung_jawab` varchar(150) DEFAULT NULL, + `kontak` varchar(50) DEFAULT NULL, + `keterangan` text DEFAULT NULL, + `kapasitas_kk` smallint(5) UNSIGNED DEFAULT 20 COMMENT 'Kapasitas maksimal KK yang dapat ditangani', + `anggaran_bulanan` decimal(15,2) DEFAULT 0.00 COMMENT 'Estimasi anggaran bantuan tersedia per bulan (Rp)', + `radius_primer` smallint(5) UNSIGNED DEFAULT 500 COMMENT 'Radius cakupan utama dalam meter', + `created_at` timestamp NOT NULL DEFAULT current_timestamp(), + `updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp() +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- +-- Dumping data for table `rumah_ibadah` +-- + +INSERT INTO `rumah_ibadah` (`id`, `nama`, `jenis`, `alamat`, `kelurahan`, `kecamatan`, `kota`, `latitude`, `longitude`, `penanggung_jawab`, `kontak`, `keterangan`, `kapasitas_kk`, `anggaran_bulanan`, `radius_primer`, `created_at`, `updated_at`) VALUES +(1, 'Masjid Al-Ikhlas', 'masjid', 'Jl. Merdeka No. 1', 'Sungai Jawi', 'Pontianak Kota', 'Pontianak', -0.02436519, 109.32911396, 'H. Ahmad Fauzi', '08123456789', 'Masjid utama kecamatan', 20, 0.00, 500, '2026-05-17 06:06:37', '2026-05-17 07:29:38'), +(2, 'Paroki Santo Yoseph Katedral', 'gereja', 'Jl. Katedral No. 5', 'Mariana', 'Pontianak Kota', 'Pontianak', -0.02727270, 109.33816910, 'Pastor Yohanes', '08234567890', 'Gereja Katedral', 20, 0.00, 500, '2026-05-17 06:06:37', '2026-05-17 06:06:37'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `rumah_warga` +-- + +CREATE TABLE `rumah_warga` ( + `id` int(11) NOT NULL, + `no_kk` varchar(16) NOT NULL, + `nama_kepala_keluarga` varchar(200) NOT NULL, + `nik` varchar(16) DEFAULT NULL, + `alamat` text DEFAULT NULL, + `kelurahan` varchar(100) DEFAULT NULL, + `kecamatan` varchar(100) DEFAULT NULL, + `kota` varchar(100) DEFAULT NULL, + `latitude` decimal(10,8) NOT NULL, + `longitude` decimal(11,8) NOT NULL, + `jumlah_tanggungan` int(11) DEFAULT 1, + `penghasilan_bulanan` decimal(15,2) DEFAULT 0.00, + `pekerjaan` varchar(100) DEFAULT NULL, + `status_pekerjaan` enum('tetap','tidak_tetap','tidak_bekerja','pensiun') DEFAULT 'tidak_bekerja', + `luas_rumah` decimal(8,2) DEFAULT NULL COMMENT 'm2', + `jenis_lantai` enum('tanah','semen','keramik','kayu','lainnya') DEFAULT 'semen', + `jenis_dinding` enum('tembok','kayu','bambu','lainnya') DEFAULT 'tembok', + `sumber_air` enum('pdam','sumur','sungai','hujan','lainnya') DEFAULT 'sumur', + `status_tempat_tinggal` enum('milik_sendiri','sewa','numpang','dinas') DEFAULT 'milik_sendiri', + `kategori_kemiskinan` enum('sangat_miskin','miskin','rentan_miskin') DEFAULT 'miskin', + `status_bantuan` enum('belum_terima','sudah_terima','sedang_proses') DEFAULT 'belum_terima', + `rekomendasi_bantuan` enum('bantuan_rutin','bantuan_pelatihan','keduanya','tidak_ada') DEFAULT 'bantuan_rutin', + `keterangan` text DEFAULT NULL, + `tanggal_verifikasi` date DEFAULT NULL COMMENT 'Tanggal terakhir data diverifikasi petugas', + `petugas_verifikasi` varchar(150) DEFAULT NULL COMMENT 'Nama petugas yang melakukan verifikasi', + `skor_kepercayaan` tinyint(3) UNSIGNED DEFAULT 100 COMMENT 'Skor kepercayaan data 0-100 (100=baru diverifikasi)', + `status_verifikasi` enum('terverifikasi','perlu_update','kadaluarsa') DEFAULT 'terverifikasi' COMMENT 'Status keterbaruan data', + `skor_kemandirian` tinyint(3) UNSIGNED DEFAULT 0 COMMENT 'Skor potensi kemandirian 0-100 (dihitung otomatis)', + `status_kemandirian` enum('sangat_bergantung','bergantung','berkembang','berpotensi_mandiri','mandiri') DEFAULT 'bergantung' COMMENT 'Status kemandirian warga', + `catatan_kemandirian` text DEFAULT NULL COMMENT 'Catatan perkembangan kemandirian dari petugas', + `tanggal_exit_plan` date DEFAULT NULL COMMENT 'Target tanggal warga keluar dari daftar bantuan', + `created_at` timestamp NOT NULL DEFAULT current_timestamp(), + `updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp() +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- +-- Dumping data for table `rumah_warga` +-- + +INSERT INTO `rumah_warga` (`id`, `no_kk`, `nama_kepala_keluarga`, `nik`, `alamat`, `kelurahan`, `kecamatan`, `kota`, `latitude`, `longitude`, `jumlah_tanggungan`, `penghasilan_bulanan`, `pekerjaan`, `status_pekerjaan`, `luas_rumah`, `jenis_lantai`, `jenis_dinding`, `sumber_air`, `status_tempat_tinggal`, `kategori_kemiskinan`, `status_bantuan`, `rekomendasi_bantuan`, `keterangan`, `tanggal_verifikasi`, `petugas_verifikasi`, `skor_kepercayaan`, `status_verifikasi`, `skor_kemandirian`, `status_kemandirian`, `catatan_kemandirian`, `tanggal_exit_plan`, `created_at`, `updated_at`) VALUES +(1, '1234567890987654', 'Budi Santoso', '1234567890123456', 'Jl. Gang Melati No. 3', 'Sungai Jawi', 'Pontianak Kota', 'Pontianak', -0.02399236, 109.32659805, 4, 800000.00, 'Buruh Harian', 'tidak_tetap', 36.00, 'semen', 'kayu', 'sumur', 'sewa', 'miskin', 'belum_terima', 'bantuan_pelatihan', 'Kepala keluarga masih produktif', '2026-05-17', NULL, 100, 'terverifikasi', 0, 'bergantung', NULL, NULL, '2026-05-17 06:06:37', '2026-05-17 10:41:31'), +(2, '0987654321234567', 'Siti Aminah', '9876543210987654', 'Jl. Flamboyan No. 12', 'Sungai Jawi', 'Pontianak Kota', 'Pontianak', -0.02216577, 109.32456493, 2, 0.00, 'Ibu Rumah Tangga', 'tidak_bekerja', 24.00, 'tanah', 'bambu', 'sungai', 'numpang', 'sangat_miskin', 'belum_terima', 'bantuan_rutin', 'Suami meninggal, tidak ada penghasilan', '2026-05-17', NULL, 100, 'terverifikasi', 0, 'bergantung', NULL, NULL, '2026-05-17 06:06:37', '2026-05-17 10:41:31'), +(3, '1111111111111111', 'Test User', '1111111111111111', 'Test Alamat', 'Test', 'Test', 'Pontianak', -0.02300000, 109.32600000, 1, 500000.00, 'Test', 'tidak_bekerja', 30.00, 'semen', 'tembok', 'sumur', 'milik_sendiri', 'miskin', 'belum_terima', 'bantuan_rutin', 'Test data', '2026-05-17', NULL, 100, 'terverifikasi', 0, 'bergantung', NULL, NULL, '2026-05-17 08:08:57', '2026-05-17 10:41:31'), +(4, '9999999999999999', 'Ahmad Wijaya', '', '', '', '', '', -0.02300000, 109.32600000, 1, 0.00, '', 'tidak_bekerja', NULL, 'semen', 'tembok', 'sumur', 'milik_sendiri', 'miskin', 'belum_terima', 'bantuan_rutin', '', '2026-05-17', NULL, 100, 'terverifikasi', 0, 'bergantung', NULL, NULL, '2026-05-17 08:13:55', '2026-05-17 10:41:31'); + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `anggota_keluarga` +-- +ALTER TABLE `anggota_keluarga` + ADD PRIMARY KEY (`id`), + ADD KEY `fk_anggota_warga` (`rumah_warga_id`); + +-- +-- Indexes for table `konfigurasi_sistem` +-- +ALTER TABLE `konfigurasi_sistem` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `uk_kunci` (`kunci`); + +-- +-- Indexes for table `redistribusi_bantuan` +-- +ALTER TABLE `redistribusi_bantuan` + ADD PRIMARY KEY (`id`), + ADD KEY `fk_redist_pengirim` (`ibadah_pengirim_id`), + ADD KEY `fk_redist_penerima` (`ibadah_penerima_id`), + ADD KEY `idx_periode` (`periode_bulan`), + ADD KEY `idx_status` (`status`); + +-- +-- Indexes for table `riwayat_kemandirian` +-- +ALTER TABLE `riwayat_kemandirian` + ADD PRIMARY KEY (`id`), + ADD KEY `fk_kemandirian_warga` (`rumah_warga_id`); + +-- +-- Indexes for table `riwayat_verifikasi` +-- +ALTER TABLE `riwayat_verifikasi` + ADD PRIMARY KEY (`id`), + ADD KEY `fk_riwayat_warga` (`rumah_warga_id`); + +-- +-- Indexes for table `rumah_ibadah` +-- +ALTER TABLE `rumah_ibadah` + ADD PRIMARY KEY (`id`); + +-- +-- Indexes for table `rumah_warga` +-- +ALTER TABLE `rumah_warga` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `no_kk` (`no_kk`), + ADD UNIQUE KEY `uk_no_kk` (`no_kk`); + +-- +-- AUTO_INCREMENT for dumped tables +-- + +-- +-- AUTO_INCREMENT for table `anggota_keluarga` +-- +ALTER TABLE `anggota_keluarga` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7; + +-- +-- AUTO_INCREMENT for table `konfigurasi_sistem` +-- +ALTER TABLE `konfigurasi_sistem` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=9; + +-- +-- AUTO_INCREMENT for table `redistribusi_bantuan` +-- +ALTER TABLE `redistribusi_bantuan` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + +-- +-- AUTO_INCREMENT for table `riwayat_kemandirian` +-- +ALTER TABLE `riwayat_kemandirian` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + +-- +-- AUTO_INCREMENT for table `riwayat_verifikasi` +-- +ALTER TABLE `riwayat_verifikasi` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + +-- +-- AUTO_INCREMENT for table `rumah_ibadah` +-- +ALTER TABLE `rumah_ibadah` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3; + +-- +-- AUTO_INCREMENT for table `rumah_warga` +-- +ALTER TABLE `rumah_warga` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5; + +-- +-- Constraints for dumped tables +-- + +-- +-- Constraints for table `anggota_keluarga` +-- +ALTER TABLE `anggota_keluarga` + ADD CONSTRAINT `fk_anggota_warga` FOREIGN KEY (`rumah_warga_id`) REFERENCES `rumah_warga` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `redistribusi_bantuan` +-- +ALTER TABLE `redistribusi_bantuan` + ADD CONSTRAINT `fk_redist_penerima` FOREIGN KEY (`ibadah_penerima_id`) REFERENCES `rumah_ibadah` (`id`) ON DELETE CASCADE, + ADD CONSTRAINT `fk_redist_pengirim` FOREIGN KEY (`ibadah_pengirim_id`) REFERENCES `rumah_ibadah` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `riwayat_kemandirian` +-- +ALTER TABLE `riwayat_kemandirian` + ADD CONSTRAINT `fk_kemandirian_warga` FOREIGN KEY (`rumah_warga_id`) REFERENCES `rumah_warga` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `riwayat_verifikasi` +-- +ALTER TABLE `riwayat_verifikasi` + ADD CONSTRAINT `fk_riwayat_warga` FOREIGN KEY (`rumah_warga_id`) REFERENCES `rumah_warga` (`id`) ON DELETE CASCADE; +COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/04/database/laporan_migration.sql b/04/database/laporan_migration.sql new file mode 100644 index 0000000..883ccca --- /dev/null +++ b/04/database/laporan_migration.sql @@ -0,0 +1,44 @@ +-- laporan_migration.sql +-- Jalankan script ini di phpMyAdmin untuk database "bansos_gis" +-- Fitur: Pelaporan Warga oleh Kontributor & Koordinator + +-- Tambah kolom no_telp ke tabel users jika belum ada +ALTER TABLE `users` + ADD COLUMN IF NOT EXISTS `no_telp` VARCHAR(20) NULL DEFAULT NULL + COMMENT 'Nomor telepon/WA pengguna'; + +-- Buat tabel laporan_warga +CREATE TABLE IF NOT EXISTS `laporan_warga` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `rumah_warga_id` INT(11) NOT NULL COMMENT 'Warga yang dilaporkan', + `pelapor_id` INT(11) NOT NULL COMMENT 'User yang membuat laporan', + `koordinator_ibadah_id` INT(11) NULL DEFAULT NULL COMMENT 'Rumah ibadah pelapor (jika koordinator)', + `jenis_laporan` ENUM( + 'data_tidak_sesuai', + 'warga_mampu', + 'warga_pindah', + 'warga_meninggal', + 'data_ganda', + 'lainnya' + ) NOT NULL DEFAULT 'data_tidak_sesuai' COMMENT 'Jenis ketidaksesuaian yang dilaporkan', + `deskripsi` TEXT NOT NULL COMMENT 'Penjelasan detail dari pelapor', + `prioritas` ENUM('rendah', 'sedang', 'tinggi') NOT NULL DEFAULT 'sedang', + `status` ENUM( + 'pending', + 'ditinjau', + 'diproses', + 'selesai', + 'ditolak' + ) NOT NULL DEFAULT 'pending' COMMENT 'Status penanganan laporan', + `catatan_admin` TEXT NULL DEFAULT NULL COMMENT 'Catatan atau keputusan admin', + `ditugaskan_ke` VARCHAR(150) NULL DEFAULT NULL COMMENT 'Nama petugas yang ditugaskan cek lapangan', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(), + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP(), + PRIMARY KEY (`id`), + KEY `fk_laporan_warga` (`rumah_warga_id`), + KEY `fk_laporan_pelapor` (`pelapor_id`), + KEY `idx_laporan_status` (`status`), + KEY `idx_laporan_prioritas` (`prioritas`), + CONSTRAINT `fk_laporan_warga` FOREIGN KEY (`rumah_warga_id`) REFERENCES `rumah_warga` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_laporan_pelapor` FOREIGN KEY (`pelapor_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; diff --git a/04/database/migrations.sql b/04/database/migrations.sql new file mode 100644 index 0000000..0f95519 --- /dev/null +++ b/04/database/migrations.sql @@ -0,0 +1,79 @@ +-- migrations.sql +-- Run this SQL script in phpMyAdmin or your MySQL client for database "bansos_gis" + +-- 1. Membuat tabel 'users' untuk multi-role +CREATE TABLE IF NOT EXISTS `users` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `username` VARCHAR(100) NOT NULL UNIQUE, + `email` VARCHAR(150) NOT NULL UNIQUE, + `password` VARCHAR(255) NOT NULL, + `role` ENUM('admin', 'petugas', 'koordinator', 'kontributor') NOT NULL DEFAULT 'kontributor', + `rumah_ibadah_id` INT(11) NULL DEFAULT NULL COMMENT 'Terhubung ke rumah ibadah tertentu untuk Petugas & Koordinator', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(), + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP(), + PRIMARY KEY (`id`), + CONSTRAINT `fk_users_rumah_ibadah` FOREIGN KEY (`rumah_ibadah_id`) REFERENCES `rumah_ibadah` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- 2. Membuat tabel 'kebutuhan' rumah ibadah +CREATE TABLE IF NOT EXISTS `kebutuhan` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `rumah_ibadah_id` INT(11) NOT NULL, + `jenis_bantuan` VARCHAR(150) NOT NULL COMMENT 'Contoh: Beras, Uang Operasional, Jasa Renovasi, dll', + `kategori` ENUM('uang', 'barang', 'jasa') NOT NULL DEFAULT 'barang', + `satuan` VARCHAR(32) NULL DEFAULT 'unit' COMMENT 'satuan untuk jumlah_dibutuhkan (orang/jam/shift/unit/nominal)', + `jumlah_dibutuhkan` DECIMAL(15,2) NOT NULL DEFAULT 0.00, + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(), + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP(), + PRIMARY KEY (`id`), + CONSTRAINT `fk_kebutuhan_rumah_ibadah` FOREIGN KEY (`rumah_ibadah_id`) REFERENCES `rumah_ibadah` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- 3. Membuat tabel 'kontribusi' oleh kontributor +CREATE TABLE IF NOT EXISTS `kontribusi` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `user_id` INT(11) NOT NULL, + `kebutuhan_id` INT(11) NOT NULL, + `jenis_kontribusi` ENUM('uang', 'barang', 'jasa') NOT NULL, + `jumlah` DECIMAL(15,2) NOT NULL DEFAULT 0.00, + `detail_barang_jasa` TEXT NULL COMMENT 'Deskripsi detail barang/jasa yang disumbangkan', + `status` ENUM('pending', 'confirmed') NOT NULL DEFAULT 'pending', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(), + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP(), + PRIMARY KEY (`id`), + CONSTRAINT `fk_kontribusi_users` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_kontribusi_kebutuhan` FOREIGN KEY (`kebutuhan_id`) REFERENCES `kebutuhan` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- 4. Seed User Awal (Password: password123) +-- Hash bcrypt untuk password123: $2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi +INSERT INTO `users` (`username`, `email`, `password`, `role`, `rumah_ibadah_id`) +VALUES +('admin', 'admin@webgis.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'admin', NULL) +ON DUPLICATE KEY UPDATE `username`=`username`; + +INSERT INTO `users` (`username`, `email`, `password`, `role`, `rumah_ibadah_id`) +VALUES +('petugas', 'petugas@webgis.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'petugas', 1) +ON DUPLICATE KEY UPDATE `username`=`username`; + +INSERT INTO `users` (`username`, `email`, `password`, `role`, `rumah_ibadah_id`) +VALUES +('koordinator', 'koordinator@webgis.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'koordinator', 1) +ON DUPLICATE KEY UPDATE `username`=`username`; + +INSERT INTO `users` (`username`, `email`, `password`, `role`, `rumah_ibadah_id`) +VALUES +('kontributor', 'kontributor@webgis.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'kontributor', NULL) +ON DUPLICATE KEY UPDATE `username`=`username`; + +-- 5. Seed Kebutuhan Awal untuk Demonstrasi +INSERT INTO `kebutuhan` (`id`, `rumah_ibadah_id`, `jenis_bantuan`, `kategori`, `jumlah_dibutuhkan`) +VALUES +(1, 1, 'Beras Sembako (KK)', 'barang', 10.00), +(2, 1, 'Dana Operasional Santunan', 'uang', 5000000.00), +(3, 2, 'Relawan Pembagi Paket Beras', 'jasa', 5.00) +ON DUPLICATE KEY UPDATE `id`=`id`; + +-- Backfill/ensure `satuan` exists for existing installations +ALTER TABLE `kebutuhan` ADD COLUMN IF NOT EXISTS `satuan` VARCHAR(32) NULL DEFAULT 'unit' COMMENT 'satuan untuk jumlah_dibutuhkan (orang/jam/shift/unit/nominal)'; diff --git a/04/index.html b/04/index.html new file mode 100644 index 0000000..e9c4f42 --- /dev/null +++ b/04/index.html @@ -0,0 +1,1226 @@ + + + + + + + WebGIS Bansos — Pemetaan Rumah Ibadah & Rumah Warga + + + + + + + + + +
+ + + + + +
+ + + +
+
+ + +
+
+
Detail
+ +
+
+
+ + +
+ Radius: + 500 m + · + Terdampak: + 0 rumah + + + + + + +
+ + +
+ + +
+ +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/04/uploads/dokumentasi/dok_2_1779757288.jpg b/04/uploads/dokumentasi/dok_2_1779757288.jpg new file mode 100644 index 0000000..32f3b4c Binary files /dev/null and b/04/uploads/dokumentasi/dok_2_1779757288.jpg differ diff --git a/04/uploads/dokumentasi/dok_2_1779759076.png b/04/uploads/dokumentasi/dok_2_1779759076.png new file mode 100644 index 0000000..d019cd6 Binary files /dev/null and b/04/uploads/dokumentasi/dok_2_1779759076.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..259c6fb --- /dev/null +++ b/index.html @@ -0,0 +1,688 @@ + + + + + + + WebGIS Portal — Devi Natalia + + + + + + + + + + +
+
+
+
+
+ Sistem Informasi Geografis C +
+

Website Sistem Informasi
Geografis Kota Pontianak

+

Kumpulan proyek pemetaan digital interaktif — dari data kependudukan, infrastruktur + energi, hingga distribusi bantuan sosial.

+
+ Devi Natalia +
+ D1041231027 + · + Sistem Informasi Geografis C +
+
+
+
+ + +
+
+ Daftar Proyek +
+ 3 proyek +
+ + +
+ + + + + + + \ No newline at end of file