1060 lines
46 KiB
HTML
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;">🔍</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> |