Files
Anderi Saputra adec71e763 first commit
2026-06-10 17:00:19 +07:00

1060 lines
46 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WebGIS - Peta Interaktif</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; font-family: Arial, sans-serif; font-size: 14px; }
#map { height: 100vh; }
/* ===== TOP BAR ===== */
#topbar {
position: fixed; top: 12px; left: 50%; transform: translateX(-50%);
z-index: 1100; display: flex; align-items: center; gap: 8px;
background: white; border-radius: 10px; padding: 8px 12px;
box-shadow: 0 4px 16px rgba(0,0,0,0.18); width: 420px; max-width: 90vw;
}
#searchInput {
flex: 1; border: none; outline: none; font-size: 14px; background: transparent;
}
#searchFilter {
border: 1px solid #ddd; border-radius: 6px; padding: 4px 6px;
font-size: 13px; background: #f8f8f8; cursor: pointer;
}
#btnSearch {
background: #3498db; color: white; border: none; border-radius: 6px;
padding: 6px 12px; cursor: pointer; font-size: 13px; white-space: nowrap;
}
#btnSearch:hover { background: #2980b9; }
/* ===== SEARCH RESULTS ===== */
#searchResults {
position: fixed; top: 58px; left: 50%; transform: translateX(-50%);
background: white; border-radius: 8px; box-shadow: 0 4px 16px rgba(0,0,0,0.15);
z-index: 1090; width: 420px; max-width: 90vw; max-height: 300px;
overflow-y: auto; display: none;
}
.search-item {
padding: 10px 14px; cursor: pointer; border-bottom: 1px solid #f0f0f0;
display: flex; align-items: center; gap: 10px;
}
.search-item:hover { background: #f5f5f5; }
.search-item:last-child { border-bottom: none; }
.search-badge {
font-size: 11px; padding: 2px 7px; border-radius: 10px;
color: white; font-weight: 700; white-space: nowrap;
}
.badge-spbu { background: #27ae60; }
.badge-jalan { background: #e67e22; }
.badge-kavling { background: #8e44ad; }
/* ===== LEGEND ===== */
#legend {
position: fixed; top: 70px; right: 12px; z-index: 1000;
background: white; border-radius: 10px; padding: 12px 14px;
box-shadow: 0 4px 16px rgba(0,0,0,0.15); min-width: 200px; font-size: 13px;
}
#legend h4 {
font-size: 13px; font-weight: 700; color: #333; margin-bottom: 8px;
text-transform: uppercase; letter-spacing: 0.5px;
}
.legend-section { margin-bottom: 10px; }
.legend-title {
font-weight: 700; font-size: 12px; color: #555; margin-bottom: 4px;
border-bottom: 1px solid #eee; padding-bottom: 2px;
}
.legend-item {
display: flex; align-items: center; gap: 8px; margin-bottom: 4px;
cursor: pointer; padding: 2px 4px; border-radius: 4px; transition: background 0.15s;
}
.legend-item:hover { background: #f0f0f0; }
.legend-dot { width: 14px; height: 14px; border-radius: 50%; border: 2px solid rgba(0,0,0,0.2); flex-shrink: 0; }
.legend-line { width: 22px; height: 4px; border-radius: 2px; flex-shrink: 0; }
.legend-polygon { width: 18px; height: 14px; border-radius: 3px; border: 2px solid rgba(0,0,0,0.25); flex-shrink: 0; opacity: 0.75; }
/* ===== MODE BAR ===== */
#modeBar {
position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
z-index: 1000; display: flex; gap: 8px; background: white;
border-radius: 12px; padding: 8px; box-shadow: 0 4px 16px rgba(0,0,0,0.18);
}
.modeBtn {
padding: 8px 16px; border: none; border-radius: 8px; cursor: pointer;
font-size: 13px; font-weight: 600; transition: all 0.2s; background: #f0f0f0; color: #555;
}
.modeBtn.active { color: white; }
.modeBtn#btnModeSPBU.active { background: #27ae60; }
.modeBtn#btnModeJalan.active { background: #e67e22; }
.modeBtn#btnModeKavling.active { background: #8e44ad; }
.modeBtn#btnModeView.active { background: #2980b9; }
/* ===== FORMS ===== */
.form-popup {
position: fixed; top: 20px; right: 230px;
background: white; padding: 16px; border-radius: 12px;
box-shadow: 0 8px 25px rgba(0,0,0,0.2); display: none; z-index: 1050; width: 270px;
}
.form-popup h3 { margin-bottom: 12px; font-size: 15px; color: #2c3e50; }
.form-popup label { font-size: 12px; color: #666; display: block; margin-bottom: 2px; }
.form-popup input, .form-popup select {
width: 100%; margin-bottom: 10px; padding: 7px 9px;
border-radius: 6px; border: 1px solid #ccc; font-size: 13px;
}
.form-popup input:read-only { background: #f5f5f5; color: #888; }
.form-row { display: flex; gap: 8px; }
.form-row .btn { flex: 1; padding: 8px; }
.btn {
padding: 8px 14px; border: none; border-radius: 6px;
cursor: pointer; font-size: 13px; font-weight: 600;
}
.btn-save { background: #2ecc71; color: white; }
.btn-save:hover { background: #27ae60; }
.btn-cancel { background: #e74c3c; color: white; }
.btn-cancel:hover { background: #c0392b; }
/* ===== DRAWING HINT ===== */
#drawHint {
position: fixed; top: 70px; left: 50%; transform: translateX(-50%);
background: rgba(44,62,80,0.88); color: white; padding: 8px 16px;
border-radius: 8px; font-size: 13px; z-index: 1050; display: none; pointer-events: none;
}
/* ===== NOTIFICATION ===== */
#notif {
position: fixed; bottom: 80px; left: 50%; transform: translateX(-50%);
background: #2ecc71; color: white; padding: 10px 22px;
border-radius: 8px; display: none; z-index: 2000;
font-size: 13px; font-weight: 600; box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
/* ===== POPUP STYLE ===== */
.leaflet-popup-content { font-size: 13px; min-width: 190px; }
.leaflet-popup-content b { font-size: 14px; color: #2c3e50; }
.popup-actions { margin-top: 8px; display: flex; gap: 6px; }
.popup-actions button {
flex: 1; padding: 5px; border: none; border-radius: 5px;
cursor: pointer; font-size: 12px; font-weight: 600;
}
.btn-hapus { background: #e74c3c; color: white; }
.btn-edit { background: #3498db; color: white; }
/* ===== DRAW ACTION BUTTONS ===== */
#btnFinishDraw {
position: fixed; bottom: 80px; right: 12px; z-index: 1050; display: none;
background: #3498db; color: white; border: none; border-radius: 8px;
padding: 10px 18px; font-size: 14px; font-weight: 700; cursor: pointer;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
#btnFinishDraw:hover { background: #2980b9; }
#btnUndoDraw {
position: fixed; bottom: 80px; right: 160px; z-index: 1050; display: none;
background: #e67e22; color: white; border: none; border-radius: 8px;
padding: 10px 18px; font-size: 14px; font-weight: 700; cursor: pointer;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
#btnUndoDraw:hover { background: #d35400; }
/* ===== EDIT MODAL ===== */
#editModal {
position: fixed; inset: 0; background: rgba(0,0,0,0.45);
z-index: 2100; display: none; align-items: center; justify-content: center;
min-height: 400px;
}
#editModal.show { display: flex; }
#editModalBox {
background: white; border-radius: 14px; padding: 20px 22px;
width: 320px; box-shadow: 0 12px 40px rgba(0,0,0,0.25);
}
#editModalBox h3 { font-size: 15px; color: #2c3e50; margin-bottom: 14px; }
#editModalBox label { font-size: 12px; color: #666; display: block; margin-bottom: 2px; }
#editModalBox input, #editModalBox select {
width: 100%; margin-bottom: 10px; padding: 7px 9px;
border-radius: 6px; border: 1px solid #ccc; font-size: 13px;
}
#editModalBox input:read-only { background: #f5f5f5; color: #888; }
.edit-hint {
background: #eaf4ff; border-left: 3px solid #3498db;
padding: 8px 10px; border-radius: 4px; font-size: 12px;
color: #2471a3; margin-bottom: 12px; line-height: 1.6;
}
.modal-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 4px; }
.modal-actions .btn { flex: 1; min-width: 70px; }
.btn-orange { background: #e67e22; color: white; }
.btn-orange:hover { background: #d35400; }
</style>
</head>
<body>
<div id="map"></div>
<!-- TOP SEARCH BAR -->
<div id="topbar">
<span style="font-size:18px;">&#128269;</span>
<input type="text" id="searchInput" placeholder="Cari SPBU, Jalan, atau Kavling..."
oninput="liveSearch(this.value)"
onkeydown="if(event.key==='Enter')doSearch()"
onfocus="onSearchFocus()">
<select id="searchFilter" onchange="onFilterChange(this.value)">
<option value="all">Semua</option>
<option value="spbu">SPBU</option>
<option value="jalan">Jalan</option>
<option value="kavling">Kavling</option>
</select>
<button id="btnSearch" onclick="doSearch()">Cari</button>
</div>
<!-- SEARCH RESULTS DROPDOWN -->
<div id="searchResults"></div>
<!-- DRAWING HINT -->
<div id="drawHint"></div>
<!-- LEGEND -->
<div id="legend">
<h4>🗺️ Legenda</h4>
<div class="legend-section">
<div class="legend-title">⛽ SPBU</div>
<div class="legend-item" onclick="filterLayer('spbu','Ya')">
<div class="legend-dot" style="background:#27ae60;"></div>
<span>SPBU 24 Jam</span>
</div>
<div class="legend-item" onclick="filterLayer('spbu','Tidak')">
<div class="legend-dot" style="background:#e74c3c;"></div>
<span>SPBU Tidak 24 Jam</span>
</div>
</div>
<div class="legend-section">
<div class="legend-title">🛣️ Jalan</div>
<div class="legend-item" onclick="filterLayer('jalan','Nasional')">
<div class="legend-line" style="background:#c0392b;"></div>
<span>Jalan Nasional</span>
</div>
<div class="legend-item" onclick="filterLayer('jalan','Provinsi')">
<div class="legend-line" style="background:#e67e22;"></div>
<span>Jalan Provinsi</span>
</div>
<div class="legend-item" onclick="filterLayer('jalan','Kabupaten')">
<div class="legend-line" style="background:#f1c40f;"></div>
<span>Jalan Kabupaten</span>
</div>
</div>
<div class="legend-section">
<div class="legend-title">🏘️ Kavling</div>
<div class="legend-item" onclick="filterLayer('kavling','SHM')">
<div class="legend-polygon" style="background:#3498db;"></div>
<span>SHM</span>
</div>
<div class="legend-item" onclick="filterLayer('kavling','HGB')">
<div class="legend-polygon" style="background:#2ecc71;"></div>
<span>HGB</span>
</div>
<div class="legend-item" onclick="filterLayer('kavling','HGU')">
<div class="legend-polygon" style="background:#9b59b6;"></div>
<span>HGU</span>
</div>
<div class="legend-item" onclick="filterLayer('kavling','HP')">
<div class="legend-polygon" style="background:#e74c3c;"></div>
<span>HP (Hak Pakai)</span>
</div>
</div>
<div style="margin-top:8px;padding-top:8px;border-top:1px solid #eee;">
<div class="legend-item" onclick="resetFilter()" style="justify-content:center;">
<span style="font-size:12px;color:#3498db;font-weight:700;">↺ Tampilkan Semua</span>
</div>
</div>
</div>
<!-- MODE BAR -->
<div id="modeBar">
<button class="modeBtn active" id="btnModeView" onclick="setMode('view')">👁️ Lihat</button>
<button class="modeBtn" id="btnModeSPBU" onclick="setMode('spbu')">⛽ SPBU</button>
<button class="modeBtn" id="btnModeJalan" onclick="setMode('jalan')">🛣️ Jalan</button>
<button class="modeBtn" id="btnModeKavling" onclick="setMode('kavling')">🏘️ Kavling</button>
</div>
<!-- FORM SPBU -->
<div id="formSPBU" class="form-popup">
<h3>⛽ Tambah SPBU</h3>
<label>Nama SPBU</label>
<input type="text" id="spbu_nama" placeholder="Masukkan nama SPBU">
<label>No WhatsApp</label>
<input type="text" id="spbu_wa" placeholder="08xxxxxxxxxx">
<label>Buka 24 Jam?</label>
<select id="spbu_buka">
<option value="Ya">Ya</option>
<option value="Tidak">Tidak</option>
</select>
<div class="form-row">
<button class="btn btn-save" onclick="simpanSPBU()">Simpan</button>
<button class="btn btn-cancel" onclick="tutupFormSPBU()">Batal</button>
</div>
</div>
<!-- FORM JALAN -->
<div id="formJalan" class="form-popup">
<h3>🛣️ Simpan Data Jalan</h3>
<label>Nama Jalan</label>
<input type="text" id="jalan_nama" placeholder="Nama jalan">
<label>Status Jalan</label>
<select id="jalan_status">
<option value="Nasional">Jalan Nasional</option>
<option value="Provinsi">Jalan Provinsi</option>
<option value="Kabupaten">Jalan Kabupaten</option>
</select>
<label>Panjang Jalan (otomatis)</label>
<input type="text" id="jalan_panjang" readonly placeholder="Dihitung otomatis (meter)">
<div class="form-row">
<button class="btn btn-save" onclick="simpanJalan()">Simpan</button>
<button class="btn btn-cancel" onclick="tutupFormJalan()">Batal</button>
</div>
</div>
<!-- FORM KAVLING -->
<div id="formKavling" class="form-popup">
<h3>🏘️ Simpan Data Kavling</h3>
<label>Nama/No Kavling</label>
<input type="text" id="kavling_nama" placeholder="Nama atau nomor kavling">
<label>Status Kepemilikan</label>
<select id="kavling_status">
<option value="SHM">Sertifikat Hak Milik (SHM)</option>
<option value="HGB">Sertifikat Hak Guna Bangunan (HGB)</option>
<option value="HGU">Sertifikat Hak Guna Usaha (HGU)</option>
<option value="HP">Sertifikat Hak Pakai (HP)</option>
</select>
<label>Luas Tanah (otomatis)</label>
<input type="text" id="kavling_luas" readonly placeholder="Dihitung otomatis (m²)">
<div class="form-row">
<button class="btn btn-save" onclick="simpanKavling()">Simpan</button>
<button class="btn btn-cancel" onclick="tutupFormKavling()">Batal</button>
</div>
</div>
<!-- FINISH / UNDO DRAW BUTTONS -->
<button id="btnUndoDraw" onclick="handleUndo()">Undo</button>
<button id="btnFinishDraw" onclick="handleFinish()">Selesai</button>
<!-- NOTIFICATION -->
<div id="notif"></div>
<!-- ===== EDIT MODAL ===== -->
<div id="editModal">
<div id="editModalBox">
<h3 id="editModalTitle">Edit Data</h3>
<!-- EDIT JALAN -->
<div id="editJalanForm" style="display:none;">
<div class="edit-hint">
Ubah nama atau status jalan saja, ATAU klik <b>"Gambar Ulang Rute"</b> untuk menggambar rute baru di peta. Panjang akan dihitung ulang secara otomatis.
</div>
<label>Nama Jalan</label>
<input type="text" id="edit_jalan_nama">
<label>Status Jalan</label>
<select id="edit_jalan_status">
<option value="Nasional">Jalan Nasional</option>
<option value="Provinsi">Jalan Provinsi</option>
<option value="Kabupaten">Jalan Kabupaten</option>
</select>
<label>Panjang Jalan</label>
<input type="text" id="edit_jalan_panjang" readonly>
<div class="modal-actions">
<button class="btn btn-orange" onclick="startEditGeom('jalan')">Gambar Ulang Rute</button>
<button class="btn btn-save" onclick="simpanEditJalan()">Simpan</button>
<button class="btn btn-cancel" onclick="tutupEditModal()">Batal</button>
</div>
</div>
<!-- EDIT KAVLING -->
<div id="editKavlingForm" style="display:none;">
<div class="edit-hint">
Ubah nama atau status saja, ATAU klik <b>"Gambar Ulang Kavling"</b> untuk menggambar batas baru di peta. Luas akan dihitung ulang secara otomatis.
</div>
<label>Nama Kavling</label>
<input type="text" id="edit_kavling_nama">
<label>Status Kepemilikan</label>
<select id="edit_kavling_status">
<option value="SHM">Sertifikat Hak Milik (SHM)</option>
<option value="HGB">Sertifikat Hak Guna Bangunan (HGB)</option>
<option value="HGU">Sertifikat Hak Guna Usaha (HGU)</option>
<option value="HP">Sertifikat Hak Pakai (HP)</option>
</select>
<label>Luas Tanah</label>
<input type="text" id="edit_kavling_luas" readonly>
<div class="modal-actions">
<button class="btn btn-orange" onclick="startEditGeom('kavling')">Gambar Ulang Kavling</button>
<button class="btn btn-save" onclick="simpanEditKavling()">Simpan</button>
<button class="btn btn-cancel" onclick="tutupEditModal()">Batal</button>
</div>
</div>
</div>
</div>
<script>
// ===================================================
// MAP INIT
// ===================================================
const map = L.map('map').setView([-0.0556148, 109.3485769], 13);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19, attribution: 'OpenStreetMap'
}).addTo(map);
// ===================================================
// STATE
// ===================================================
let currentMode = 'view';
let drawPoints = [];
let drawPolyline = null;
let drawPolygon = null;
let tempMarkers = [];
let previewMarker = null;
let latKlik, lngKlik;
// Edit state
let editingType = null;
let editingId = null;
let editingLayer = null;
let isRedrawing = false;
let editDrawPoints = [];
let editDrawPolyline = null;
let editDrawPolygon = null;
let editTempMarkers = [];
const layerGroups = {
spbu: L.layerGroup().addTo(map),
jalan: L.layerGroup().addTo(map),
kavling: L.layerGroup().addTo(map)
};
let cachedData = { spbu: [], jalan: [], kavling: [] };
// ===================================================
// UTILITIES
// ===================================================
function showNotif(text, color) {
const n = document.getElementById('notif');
n.textContent = text;
n.style.background = color || '#2ecc71';
n.style.display = 'block';
setTimeout(() => { n.style.display = 'none'; }, 2500);
}
function setHint(text) {
const h = document.getElementById('drawHint');
if (text) { h.textContent = text; h.style.display = 'block'; }
else { h.style.display = 'none'; }
}
// ===================================================
// MODE
// ===================================================
function setMode(mode) {
if (currentMode !== 'view' && !isRedrawing) cancelDraw();
currentMode = mode;
document.querySelectorAll('.modeBtn').forEach(b => b.classList.remove('active'));
const ids = { view:'btnModeView', spbu:'btnModeSPBU', jalan:'btnModeJalan', kavling:'btnModeKavling' };
document.getElementById(ids[mode]).classList.add('active');
if (mode === 'spbu') setHint('Klik kanan pada peta untuk menambah SPBU');
else if (mode === 'jalan') setHint('Klik untuk menambah titik jalan. Klik Selesai jika sudah selesai.');
else if (mode === 'kavling') setHint('Klik untuk menambah titik kavling (min 3 titik). Klik Selesai.');
else setHint(null);
}
function cancelDraw() {
drawPoints = [];
if (drawPolyline) { map.removeLayer(drawPolyline); drawPolyline = null; }
if (drawPolygon) { map.removeLayer(drawPolygon); drawPolygon = null; }
tempMarkers.forEach(m => map.removeLayer(m)); tempMarkers = [];
if (previewMarker) { map.removeLayer(previewMarker); previewMarker = null; }
document.getElementById('btnFinishDraw').style.display = 'none';
document.getElementById('btnUndoDraw').style.display = 'none';
document.getElementById('formJalan').style.display = 'none';
document.getElementById('formKavling').style.display = 'none';
}
// ===================================================
// CLICK EVENTS
// ===================================================
map.on('contextmenu', function(e) {
if (currentMode !== 'spbu') return;
latKlik = e.latlng.lat; lngKlik = e.latlng.lng;
if (previewMarker) map.removeLayer(previewMarker);
previewMarker = L.marker([latKlik, lngKlik], { opacity: 0.5 }).addTo(map);
document.getElementById('formSPBU').style.display = 'block';
});
map.on('click', function(e) {
document.getElementById('searchResults').style.display = 'none';
if (isRedrawing) { addEditDrawPoint(e.latlng); return; }
if (currentMode === 'jalan') addDrawPoint(e.latlng);
else if (currentMode === 'kavling') addDrawPoint(e.latlng);
});
// ===== Normal draw =====
function addDrawPoint(latlng) {
drawPoints.push(latlng);
const m = L.circleMarker(latlng, { radius:5, color:'#e67e22', fillColor:'#e67e22', fillOpacity:1 }).addTo(map);
tempMarkers.push(m);
if (currentMode === 'jalan') {
if (drawPolyline) map.removeLayer(drawPolyline);
drawPolyline = L.polyline(drawPoints, { color:'#e67e22', weight:3, dashArray:'6,4' }).addTo(map);
document.getElementById('btnFinishDraw').style.display = drawPoints.length >= 2 ? 'block' : 'none';
document.getElementById('btnUndoDraw').style.display = 'block';
} else if (currentMode === 'kavling') {
if (drawPolygon) map.removeLayer(drawPolygon);
if (drawPoints.length >= 3)
drawPolygon = L.polygon(drawPoints, { color:'#8e44ad', fillColor:'#9b59b6', fillOpacity:0.3, weight:2, dashArray:'6,4' }).addTo(map);
document.getElementById('btnFinishDraw').style.display = drawPoints.length >= 3 ? 'block' : 'none';
document.getElementById('btnUndoDraw').style.display = 'block';
}
}
function handleUndo() {
if (isRedrawing) undoEditDrawPoint();
else undoLastPoint();
}
function undoLastPoint() {
if (!drawPoints.length) return;
drawPoints.pop();
const last = tempMarkers.pop();
if (last) map.removeLayer(last);
if (drawPolyline) { map.removeLayer(drawPolyline); drawPolyline = null; }
if (drawPolygon) { map.removeLayer(drawPolygon); drawPolygon = null; }
if (drawPoints.length >= 2 && currentMode === 'jalan')
drawPolyline = L.polyline(drawPoints, { color:'#e67e22', weight:3, dashArray:'6,4' }).addTo(map);
if (drawPoints.length >= 3 && currentMode === 'kavling')
drawPolygon = L.polygon(drawPoints, { color:'#8e44ad', fillColor:'#9b59b6', fillOpacity:0.3, weight:2, dashArray:'6,4' }).addTo(map);
document.getElementById('btnFinishDraw').style.display =
(currentMode==='jalan' && drawPoints.length>=2)||(currentMode==='kavling' && drawPoints.length>=3) ? 'block' : 'none';
document.getElementById('btnUndoDraw').style.display = drawPoints.length >= 1 ? 'block' : 'none';
}
function handleFinish() {
if (isRedrawing) finishEditGeom();
else finishDraw();
}
function finishDraw() {
if (currentMode === 'jalan') {
if (drawPoints.length < 2) { showNotif('Minimal 2 titik untuk jalan!', '#e74c3c'); return; }
let p = 0;
for (let i = 1; i < drawPoints.length; i++) p += drawPoints[i-1].distanceTo(drawPoints[i]);
document.getElementById('jalan_panjang').value = Math.round(p) + ' meter';
document.getElementById('formJalan').style.display = 'block';
document.getElementById('btnFinishDraw').style.display = 'none';
document.getElementById('btnUndoDraw').style.display = 'none';
} else if (currentMode === 'kavling') {
if (drawPoints.length < 3) { showNotif('Minimal 3 titik untuk kavling!', '#e74c3c'); return; }
document.getElementById('kavling_luas').value = Math.round(hitungLuasPolygon(drawPoints)) + ' m2';
document.getElementById('formKavling').style.display = 'block';
document.getElementById('btnFinishDraw').style.display = 'none';
document.getElementById('btnUndoDraw').style.display = 'none';
}
}
function hitungLuasPolygon(latlngs) {
const n = latlngs.length;
if (n < 3) return 0;
let cx = 0, cy = 0;
latlngs.forEach(p => { cx += p.lat; cy += p.lng; });
cx /= n; cy /= n;
const origin = L.latLng(cx, cy);
const pts = latlngs.map(p => ({
x: L.latLng(cx, p.lng).distanceTo(origin) * (p.lng > cy ? 1 : -1),
y: L.latLng(p.lat, cy).distanceTo(origin) * (p.lat > cx ? 1 : -1)
}));
let area = 0;
for (let i = 0; i < n; i++) {
const j = (i+1) % n;
area += pts[i].x * pts[j].y - pts[j].x * pts[i].y;
}
return Math.abs(area / 2);
}
// ===================================================
// ICONS / COLORS
// ===================================================
function getIconSPBU(buka) {
return new L.Icon({
iconUrl: buka === 'Ya'
? 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-green.png'
: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-red.png',
shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png',
iconSize: [25,41], iconAnchor: [12,41]
});
}
function getColorJalan(s) { return {Nasional:'#c0392b',Provinsi:'#e67e22',Kabupaten:'#f1c40f'}[s]||'#888'; }
function getColorKavling(s) { return {SHM:'#3498db',HGB:'#2ecc71',HGU:'#9b59b6',HP:'#e74c3c'}[s]||'#888'; }
// ===================================================
// POPUP
// ===================================================
function popupSPBU(id, nama, wa, buka) {
return `<b>⛽${nama}</b><br>WA: ${wa}<br>24 Jam: ${buka}
<div class="popup-actions">
<button class="btn-hapus" onclick="hapusSPBU(${id})">🗑 Hapus</button>
<button class="btn-edit" onclick="editSPBU(${id})">✏ Geser</button>
</div>`;
}
function popupJalan(id, nama, status, panjang) {
return `<b>🛣️${nama}</b><br>Status: Jalan ${status}<br>Panjang: ${panjang}
<div class="popup-actions">
<button class="btn-hapus" onclick="hapusJalan(${id})">🗑 Hapus</button>
<button class="btn-edit" onclick="bukaEditJalan(${id})">✏ Edit</button>
</div>`;
}
function popupKavling(id, nama, status, luas) {
return `<b>🏘️${nama}</b><br>Status: ${status}<br>Luas: ${luas}
<div class="popup-actions">
<button class="btn-hapus" onclick="hapusKavling(${id})">🗑 Hapus</button>
<button class="btn-edit" onclick="bukaEditKavling(${id})">✏ Edit</button>
</div>`;
}
// ===================================================
// SPBU CRUD
// ===================================================
function tutupFormSPBU() {
document.getElementById('formSPBU').style.display = 'none';
if (previewMarker) { map.removeLayer(previewMarker); previewMarker = null; }
}
function simpanSPBU() {
const nama = document.getElementById('spbu_nama').value.trim();
const wa = document.getElementById('spbu_wa').value.trim();
const buka = document.getElementById('spbu_buka').value;
if (!nama) { showNotif('Nama SPBU wajib diisi!', '#e74c3c'); return; }
fetch('save_spbu.php', {
method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:`nama=${encodeURIComponent(nama)}&wa=${encodeURIComponent(wa)}&buka=${buka}&lat=${latKlik}&lng=${lngKlik}`
}).then(r=>r.text()).then(r => {
const id = r.split('|')[1];
const marker = L.marker([latKlik,lngKlik],{icon:getIconSPBU(buka)})
.bindPopup(popupSPBU(id,nama,wa,buka),{offset:[0,-35]});
layerGroups.spbu.addLayer(marker);
cachedData.spbu.push({id,nama,wa,buka,lat:latKlik,lng:lngKlik,_marker:marker});
if (previewMarker) { map.removeLayer(previewMarker); previewMarker=null; }
document.getElementById('formSPBU').style.display='none';
document.getElementById('spbu_nama').value='';
document.getElementById('spbu_wa').value='';
showNotif('SPBU berhasil disimpan!');
}).catch(()=>showNotif('Gagal menyimpan SPBU','#e74c3c'));
}
function hapusSPBU(id) {
if (!confirm('Hapus data SPBU ini?')) return;
fetch('delete_spbu.php?id='+id).then(()=>{
layerGroups.spbu.eachLayer(layer=>{
if (layer instanceof L.Marker && layer._popup && layer.getPopup().getContent().includes(`hapusSPBU(${id})`))
layerGroups.spbu.removeLayer(layer);
});
cachedData.spbu = cachedData.spbu.filter(d=>d.id!=id);
showNotif('SPBU dihapus');
});
}
function editSPBU(id) {
showNotif('Geser marker untuk memperbarui lokasi','#3498db');
layerGroups.spbu.eachLayer(layer=>{
if (layer instanceof L.Marker && layer._popup && layer.getPopup().getContent().includes(`hapusSPBU(${id})`)) {
layer.closePopup(); layer.dragging.enable();
layer.once('dragend',function(e){
fetch('update_spbu.php',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:`id=${id}&lat=${e.target.getLatLng().lat}&lng=${e.target.getLatLng().lng}`
}).then(()=>{layer.dragging.disable();showNotif('Lokasi SPBU diperbarui!');});
});
}
});
}
// ===================================================
// JALAN CRUD
// ===================================================
function tutupFormJalan() {
document.getElementById('formJalan').style.display='none';
cancelDraw(); setMode('view');
}
function simpanJalan() {
const nama=document.getElementById('jalan_nama').value.trim();
const status=document.getElementById('jalan_status').value;
const panjang=document.getElementById('jalan_panjang').value;
if (!nama){showNotif('Nama jalan wajib diisi!','#e74c3c');return;}
const geojson=JSON.stringify({type:'LineString',coordinates:drawPoints.map(p=>[p.lng,p.lat])});
fetch('save_jalan.php',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:`nama=${encodeURIComponent(nama)}&status=${status}&panjang=${encodeURIComponent(panjang)}&geojson=${encodeURIComponent(geojson)}`
}).then(r=>r.text()).then(r=>{
const id=r.split('|')[1];
if(drawPolyline){map.removeLayer(drawPolyline);drawPolyline=null;}
tempMarkers.forEach(m=>map.removeLayer(m));tempMarkers=[];
const line=L.polyline(drawPoints.map(p=>[p.lat,p.lng]),{color:getColorJalan(status),weight:4})
.bindPopup(popupJalan(id,nama,status,panjang));
layerGroups.jalan.addLayer(line);
cachedData.jalan.push({id,nama,status,panjang,_layer:line});
drawPoints=[];
document.getElementById('formJalan').style.display='none';
document.getElementById('jalan_nama').value='';
showNotif('Jalan berhasil disimpan!');setMode('view');
}).catch(()=>showNotif('Gagal menyimpan jalan','#e74c3c'));
}
function hapusJalan(id) {
if (!confirm('Hapus data jalan ini?')) return;
fetch('delete_jalan.php?id='+id).then(()=>{
layerGroups.jalan.eachLayer(layer=>{
if(layer._popup&&layer.getPopup().getContent().includes(`hapusJalan(${id})`))
layerGroups.jalan.removeLayer(layer);
});
cachedData.jalan=cachedData.jalan.filter(d=>d.id!=id);
showNotif('Jalan dihapus');
});
}
// ===== EDIT JALAN =====
function bukaEditJalan(id) {
const data=cachedData.jalan.find(d=>d.id==id);
if(!data)return;
editingType='jalan'; editingId=id; editingLayer=data._layer;
document.getElementById('edit_jalan_nama').value=data.nama;
document.getElementById('edit_jalan_status').value=data.status;
document.getElementById('edit_jalan_panjang').value=data.panjang;
document.getElementById('editJalanForm').style.display='block';
document.getElementById('editKavlingForm').style.display='none';
document.getElementById('editModalTitle').textContent='Edit Jalan';
document.getElementById('editModal').classList.add('show');
map.closePopup();
}
function startEditGeom(type) {
document.getElementById('editModal').classList.remove('show');
isRedrawing=true; editDrawPoints=[]; editDrawPolyline=null; editDrawPolygon=null;
editTempMarkers.forEach(m=>map.removeLayer(m)); editTempMarkers=[];
if(editingLayer) editingLayer.setStyle({opacity:0.2,fillOpacity:0.05});
setHint(type==='jalan' ? 'Klik titik-titik rute jalan baru, lalu klik Selesai.'
: 'Klik titik-titik batas kavling baru (min 3), lalu klik Selesai.');
document.getElementById('btnFinishDraw').style.display='none';
document.getElementById('btnUndoDraw').style.display='block';
}
function addEditDrawPoint(latlng) {
editDrawPoints.push(latlng);
const m=L.circleMarker(latlng,{radius:5,color:'#9b59b6',fillColor:'#9b59b6',fillOpacity:1}).addTo(map);
editTempMarkers.push(m);
if(editingType==='jalan'){
if(editDrawPolyline)map.removeLayer(editDrawPolyline);
editDrawPolyline=L.polyline(editDrawPoints,{color:'#9b59b6',weight:3,dashArray:'6,4'}).addTo(map);
document.getElementById('btnFinishDraw').style.display=editDrawPoints.length>=2?'block':'none';
} else {
if(editDrawPolygon)map.removeLayer(editDrawPolygon);
if(editDrawPoints.length>=3)
editDrawPolygon=L.polygon(editDrawPoints,{color:'#9b59b6',fillColor:'#9b59b6',fillOpacity:0.25,weight:2,dashArray:'6,4'}).addTo(map);
document.getElementById('btnFinishDraw').style.display=editDrawPoints.length>=3?'block':'none';
}
document.getElementById('btnUndoDraw').style.display='block';
}
function undoEditDrawPoint() {
if(!editDrawPoints.length)return;
editDrawPoints.pop();
const last=editTempMarkers.pop(); if(last)map.removeLayer(last);
if(editDrawPolyline){map.removeLayer(editDrawPolyline);editDrawPolyline=null;}
if(editDrawPolygon){map.removeLayer(editDrawPolygon);editDrawPolygon=null;}
if(editDrawPoints.length>=2&&editingType==='jalan')
editDrawPolyline=L.polyline(editDrawPoints,{color:'#9b59b6',weight:3,dashArray:'6,4'}).addTo(map);
if(editDrawPoints.length>=3&&editingType==='kavling')
editDrawPolygon=L.polygon(editDrawPoints,{color:'#9b59b6',fillColor:'#9b59b6',fillOpacity:0.25,weight:2,dashArray:'6,4'}).addTo(map);
document.getElementById('btnFinishDraw').style.display=
(editingType==='jalan'&&editDrawPoints.length>=2)||(editingType==='kavling'&&editDrawPoints.length>=3)?'block':'none';
}
function finishEditGeom() {
if(editingType==='jalan'){
if(editDrawPoints.length<2){showNotif('Minimal 2 titik!','#e74c3c');return;}
let p=0;
for(let i=1;i<editDrawPoints.length;i++) p+=editDrawPoints[i-1].distanceTo(editDrawPoints[i]);
document.getElementById('edit_jalan_panjang').value=Math.round(p)+' meter';
} else {
if(editDrawPoints.length<3){showNotif('Minimal 3 titik!','#e74c3c');return;}
document.getElementById('edit_kavling_luas').value=Math.round(hitungLuasPolygon(editDrawPoints))+' m2';
}
if(editDrawPolyline){map.removeLayer(editDrawPolyline);editDrawPolyline=null;}
if(editDrawPolygon){map.removeLayer(editDrawPolygon);editDrawPolygon=null;}
editTempMarkers.forEach(m=>map.removeLayer(m));editTempMarkers=[];
setHint(null);
document.getElementById('btnFinishDraw').style.display='none';
document.getElementById('btnUndoDraw').style.display='none';
isRedrawing=false;
if(editingLayer) editingLayer.setStyle({opacity:1,fillOpacity:0.35});
document.getElementById('editModal').classList.add('show');
}
function tutupEditModal() {
document.getElementById('editModal').classList.remove('show');
isRedrawing=false; editDrawPoints=[];
if(editDrawPolyline){map.removeLayer(editDrawPolyline);editDrawPolyline=null;}
if(editDrawPolygon){map.removeLayer(editDrawPolygon);editDrawPolygon=null;}
editTempMarkers.forEach(m=>map.removeLayer(m));editTempMarkers=[];
if(editingLayer) editingLayer.setStyle({opacity:1,fillOpacity:0.35});
setHint(null);
document.getElementById('btnFinishDraw').style.display='none';
document.getElementById('btnUndoDraw').style.display='none';
editingType=null;editingId=null;editingLayer=null;
}
function simpanEditJalan() {
const nama=document.getElementById('edit_jalan_nama').value.trim();
const status=document.getElementById('edit_jalan_status').value;
const panjang=document.getElementById('edit_jalan_panjang').value;
if(!nama){showNotif('Nama jalan wajib diisi!','#e74c3c');return;}
let coords,geojson;
if(editDrawPoints.length>=2){
coords=editDrawPoints.map(p=>[p.lat,p.lng]);
geojson=JSON.stringify({type:'LineString',coordinates:editDrawPoints.map(p=>[p.lng,p.lat])});
} else {
const lls=editingLayer.getLatLngs();
coords=lls.map(p=>[p.lat,p.lng]);
geojson=JSON.stringify({type:'LineString',coordinates:lls.map(p=>[p.lng,p.lat])});
}
fetch('update_jalan.php',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:`id=${editingId}&nama=${encodeURIComponent(nama)}&status=${status}&panjang=${encodeURIComponent(panjang)}&geojson=${encodeURIComponent(geojson)}`
}).then(()=>{
editingLayer.setLatLngs(coords);
editingLayer.setStyle({color:getColorJalan(status),weight:4,opacity:1});
editingLayer.bindPopup(popupJalan(editingId,nama,status,panjang));
const d=cachedData.jalan.find(x=>x.id==editingId);
if(d){d.nama=nama;d.status=status;d.panjang=panjang;}
tutupEditModal();showNotif('Jalan berhasil diupdate!');
}).catch(()=>showNotif('Gagal update jalan','#e74c3c'));
}
// ===================================================
// KAVLING CRUD
// ===================================================
function tutupFormKavling() {
document.getElementById('formKavling').style.display='none';
cancelDraw();setMode('view');
}
function simpanKavling() {
const nama=document.getElementById('kavling_nama').value.trim();
const status=document.getElementById('kavling_status').value;
const luas=document.getElementById('kavling_luas').value;
if(!nama){showNotif('Nama kavling wajib diisi!','#e74c3c');return;}
const geojson=JSON.stringify({type:'Polygon',
coordinates:[drawPoints.map(p=>[p.lng,p.lat]).concat([[drawPoints[0].lng,drawPoints[0].lat]])]});
fetch('save_kavling.php',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:`nama=${encodeURIComponent(nama)}&status=${status}&luas=${encodeURIComponent(luas)}&geojson=${encodeURIComponent(geojson)}`
}).then(r=>r.text()).then(r=>{
const id=r.split('|')[1];
if(drawPolygon){map.removeLayer(drawPolygon);drawPolygon=null;}
tempMarkers.forEach(m=>map.removeLayer(m));tempMarkers=[];
const color=getColorKavling(status);
const poly=L.polygon(drawPoints.map(p=>[p.lat,p.lng]),{color,fillColor:color,fillOpacity:0.35,weight:2})
.bindPopup(popupKavling(id,nama,status,luas));
layerGroups.kavling.addLayer(poly);
cachedData.kavling.push({id,nama,status,luas,_layer:poly});
drawPoints=[];
document.getElementById('formKavling').style.display='none';
document.getElementById('kavling_nama').value='';
showNotif('Kavling berhasil disimpan!');setMode('view');
}).catch(()=>showNotif('Gagal menyimpan kavling','#e74c3c'));
}
function hapusKavling(id) {
if(!confirm('Hapus data kavling ini?'))return;
fetch('delete_kavling.php?id='+id).then(()=>{
layerGroups.kavling.eachLayer(layer=>{
if(layer._popup&&layer.getPopup().getContent().includes(`hapusKavling(${id})`))
layerGroups.kavling.removeLayer(layer);
});
cachedData.kavling=cachedData.kavling.filter(d=>d.id!=id);
showNotif('Kavling dihapus');
});
}
// ===== EDIT KAVLING =====
function bukaEditKavling(id) {
const data=cachedData.kavling.find(d=>d.id==id);
if(!data)return;
editingType='kavling';editingId=id;editingLayer=data._layer;
document.getElementById('edit_kavling_nama').value=data.nama;
document.getElementById('edit_kavling_status').value=data.status;
document.getElementById('edit_kavling_luas').value=data.luas;
document.getElementById('editJalanForm').style.display='none';
document.getElementById('editKavlingForm').style.display='block';
document.getElementById('editModalTitle').textContent='Edit Kavling';
document.getElementById('editModal').classList.add('show');
map.closePopup();
}
function simpanEditKavling() {
const nama=document.getElementById('edit_kavling_nama').value.trim();
const status=document.getElementById('edit_kavling_status').value;
const luas=document.getElementById('edit_kavling_luas').value;
if(!nama){showNotif('Nama kavling wajib diisi!','#e74c3c');return;}
let coords,geojson;
if(editDrawPoints.length>=3){
coords=editDrawPoints.map(p=>[p.lat,p.lng]);
geojson=JSON.stringify({type:'Polygon',
coordinates:[editDrawPoints.map(p=>[p.lng,p.lat]).concat([[editDrawPoints[0].lng,editDrawPoints[0].lat]])]});
} else {
const lls=editingLayer.getLatLngs()[0];
coords=lls.map(p=>[p.lat,p.lng]);
geojson=JSON.stringify({type:'Polygon',coordinates:[lls.map(p=>[p.lng,p.lat]).concat([[lls[0].lng,lls[0].lat]])]});
}
fetch('update_kavling.php',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:`id=${editingId}&nama=${encodeURIComponent(nama)}&status=${status}&luas=${encodeURIComponent(luas)}&geojson=${encodeURIComponent(geojson)}`
}).then(()=>{
const color=getColorKavling(status);
editingLayer.setLatLngs(coords);
editingLayer.setStyle({color,fillColor:color,fillOpacity:0.35,weight:2,opacity:1});
editingLayer.bindPopup(popupKavling(editingId,nama,status,luas));
const d=cachedData.kavling.find(x=>x.id==editingId);
if(d){d.nama=nama;d.status=status;d.luas=luas;}
tutupEditModal();showNotif('Kavling berhasil diupdate!');
}).catch(()=>showNotif('Gagal update kavling','#e74c3c'));
}
// ===================================================
// LOAD DATA
// ===================================================
function loadAllData() {
fetch('load_spbu.php').then(r=>r.json()).then(data=>{
data.forEach(d=>{
const marker=L.marker([d.latitude,d.longitude],{icon:getIconSPBU(d.buka_24jam)})
.bindPopup(popupSPBU(d.id,d.nama_spbu,d.no_wa,d.buka_24jam),{offset:[0,-35]});
layerGroups.spbu.addLayer(marker);
cachedData.spbu.push({id:d.id,nama:d.nama_spbu,wa:d.no_wa,buka:d.buka_24jam,lat:d.latitude,lng:d.longitude,_marker:marker});
});
}).catch(e=>console.warn('load_spbu error',e));
fetch('load_jalan.php').then(r=>r.json()).then(data=>{
data.forEach(d=>{
const geo=JSON.parse(d.geojson);
const coords=geo.coordinates.map(c=>[c[1],c[0]]);
const line=L.polyline(coords,{color:getColorJalan(d.status_jalan),weight:4})
.bindPopup(popupJalan(d.id,d.nama_jalan,d.status_jalan,d.panjang_jalan));
layerGroups.jalan.addLayer(line);
cachedData.jalan.push({id:d.id,nama:d.nama_jalan,status:d.status_jalan,panjang:d.panjang_jalan,_layer:line});
});
}).catch(e=>console.warn('load_jalan error',e));
fetch('load_kavling.php').then(r=>r.json()).then(data=>{
data.forEach(d=>{
const geo=JSON.parse(d.geojson);
const coords=geo.coordinates[0].map(c=>[c[1],c[0]]);
const color=getColorKavling(d.status_kepemilikan);
const poly=L.polygon(coords,{color,fillColor:color,fillOpacity:0.35,weight:2})
.bindPopup(popupKavling(d.id,d.nama_kavling,d.status_kepemilikan,d.luas_tanah));
layerGroups.kavling.addLayer(poly);
cachedData.kavling.push({id:d.id,nama:d.nama_kavling,status:d.status_kepemilikan,luas:d.luas_tanah,_layer:poly});
});
}).catch(e=>console.warn('load_kavling error',e));
}
// ===================================================
// SEARCH — live search + tampilkan semua saat filter dipilih
// ===================================================
function onFilterChange(val) {
const q = document.getElementById('searchInput').value.trim();
if (val !== 'all' && q === '') showAllForType(val);
else liveSearch(q);
}
function onSearchFocus() {
const val = document.getElementById('searchFilter').value;
const q = document.getElementById('searchInput').value.trim();
if (val !== 'all' && q === '') showAllForType(val);
}
function showAllForType(type) {
const results = [];
if (type==='spbu')
cachedData.spbu.forEach(d=>results.push({type:'spbu',label:d.nama,sub:`24 Jam: ${d.buka}`,data:d}));
else if (type==='jalan')
cachedData.jalan.forEach(d=>results.push({type:'jalan',label:d.nama,sub:`Jalan ${d.status} - ${d.panjang}`,data:d}));
else if (type==='kavling')
cachedData.kavling.forEach(d=>results.push({type:'kavling',label:d.nama,sub:`${d.status} - ${d.luas}`,data:d}));
renderSearchResults(results, results.length === 0 ? `Belum ada data ${type}` : null);
}
function liveSearch(query) {
query = query.trim().toLowerCase();
const filter = document.getElementById('searchFilter').value;
const box = document.getElementById('searchResults');
if (!query && filter === 'all') { box.style.display='none'; return; }
if (!query && filter !== 'all') { showAllForType(filter); return; }
const results = [];
if (filter==='all'||filter==='spbu')
cachedData.spbu.forEach(d=>{if(d.nama.toLowerCase().includes(query))results.push({type:'spbu',label:d.nama,sub:`24 Jam: ${d.buka}`,data:d});});
if (filter==='all'||filter==='jalan')
cachedData.jalan.forEach(d=>{if(d.nama.toLowerCase().includes(query))results.push({type:'jalan',label:d.nama,sub:`Jalan ${d.status} - ${d.panjang}`,data:d});});
if (filter==='all'||filter==='kavling')
cachedData.kavling.forEach(d=>{if(d.nama.toLowerCase().includes(query))results.push({type:'kavling',label:d.nama,sub:`${d.status} - ${d.luas}`,data:d});});
renderSearchResults(results);
}
function renderSearchResults(results, emptyMsg) {
const box = document.getElementById('searchResults');
if (results.length === 0) {
box.innerHTML = `<div class="search-item" style="color:#999;cursor:default;">${emptyMsg||'Tidak ada hasil ditemukan'}</div>`;
} else {
box.innerHTML = results.map(r=>`
<div class="search-item">
<span class="search-badge badge-${r.type}">${r.type.toUpperCase()}</span>
<div>
<div style="font-weight:700;font-size:13px;">${r.label}</div>
<div style="font-size:12px;color:#888;">${r.sub}</div>
</div>
</div>`).join('');
box.querySelectorAll('.search-item').forEach((el,i)=>{el.onclick=()=>focusResult(results[i]);});
}
box.style.display='block';
}
function doSearch() { liveSearch(document.getElementById('searchInput').value); }
function focusResult(r) {
document.getElementById('searchResults').style.display='none';
document.getElementById('searchInput').value=r.label;
if (r.type==='spbu') {
map.setView([r.data.lat,r.data.lng],17);
if(r.data._marker) r.data._marker.openPopup();
}
else if (r.data._layer) {
map.fitBounds(r.data._layer.getBounds(),{padding:[40,40]});
r.data._layer.openPopup();
}
setTimeout(()=>{
document.getElementById('searchInput').value='';
},800);
}
// ===================================================
// LEGEND FILTER
// ===================================================
function filterLayer(type,value) {
if (type==='spbu') {
layerGroups.spbu.eachLayer(l=>{
const c=l._popup?l.getPopup().getContent():'';
const match=value==='Ya'?c.includes('24 Jam: Ya'):c.includes('24 Jam: Tidak');
l.setOpacity(match?1:0.15);
});
} else if (type==='jalan') {
layerGroups.jalan.eachLayer(l=>{
const c=l._popup?l.getPopup().getContent():'';
l.setStyle({opacity:c.includes(`Jalan ${value}`)?1:0.1});
});
} else if (type==='kavling') {
layerGroups.kavling.eachLayer(l=>{
const c=l._popup?l.getPopup().getContent():'';
l.setStyle(c.includes(`Status: ${value}`)?{opacity:1,fillOpacity:0.35}:{opacity:0.1,fillOpacity:0.05});
});
}
showNotif('Filter: '+value,'#3498db');
}
function resetFilter() {
layerGroups.spbu.eachLayer(l=>l.setOpacity(1));
layerGroups.jalan.eachLayer(l=>l.setStyle({opacity:1}));
layerGroups.kavling.eachLayer(l=>l.setStyle({opacity:1,fillOpacity:0.35}));
showNotif('Semua layer ditampilkan','#27ae60');
}
// ===================================================
// INIT
// ===================================================
loadAllData();
</script>
</body>
</html>