Files
d1041231060-WebGIS-PovertyMap/tugas.php
T
2026-06-12 20:18:21 +07:00

741 lines
37 KiB
PHP

<?php include 'koneksi.php'; ?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WebGIS Tugas SIG B | Universitas Tanjungpura</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
#topnav { background: linear-gradient(135deg, #1e3a5f 0%, #2d6a9f 100%); color: white; padding: 10px 16px; display: flex; align-items: center; gap: 12px; flex-shrink: 0; }
#topnav .brand { font-weight: 700; font-size: 15px; }
#topnav .meta { font-size: 11px; opacity: 0.75; margin-left: auto; }
#app { display: flex; flex: 1; overflow: hidden; }
#sidebar { width: 285px; background: #fff; border-right: 1px solid #dee2e6; overflow-y: auto; flex-shrink: 0; display: flex; flex-direction: column; }
.sb-section { padding: 12px; border-bottom: 1px solid #f0f4f8; }
.sb-title { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .6px; color: #8c9ab0; margin-bottom: 8px; }
.stat-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 5px; }
.stat-card { background: #f8f9ff; border: 1px solid #e9edf4; border-radius: 6px; padding: 7px 4px; text-align: center; }
.stat-num { font-size: 18px; font-weight: 800; line-height: 1.1; }
.stat-lbl { font-size: 9.5px; color: #6c757d; margin-top: 1px; }
.layer-row { display: flex; align-items: center; gap: 8px; padding: 5px 2px; cursor: pointer; border-radius: 4px; }
.layer-row:hover { background: #f8f9ff; }
.layer-dot { width: 13px; height: 13px; border-radius: 50%; flex-shrink: 0; }
.layer-dot.sq { border-radius: 3px; }
.layer-dot.line { height: 4px; border-radius: 2px; margin: 5px 0; }
.layer-lbl { font-size: 12px; color: #334155; flex: 1; }
.layer-cnt { font-size: 11px; color: #94a3b8; background: #f1f5f9; border-radius: 8px; padding: 1px 6px; }
.btn-layer { width: 100%; text-align: left; font-size: 12px; margin-bottom: 4px; border-radius: 6px; }
#map { flex: 1; }
#pick-hint { position: fixed; top: 72px; left: 50%; transform: translateX(-50%); background: rgba(30,58,95,.92); color: white; padding: 8px 18px; border-radius: 20px; font-size: 13px; z-index: 3000; display: none; backdrop-filter: blur(4px); box-shadow: 0 4px 12px rgba(0,0,0,.2); }
.popup-title { font-weight: 700; font-size: 13px; margin-bottom: 5px; color: #1e3a5f; border-bottom: 1px solid #eee; padding-bottom: 4px; }
.popup-row { font-size: 12px; color: #334155; margin: 3px 0; display: flex; gap: 4px; }
.popup-row b { color: #64748b; min-width: 60px; }
.popup-btns { display: flex; gap: 6px; margin-top: 8px; }
.pbtn { font-size: 11px; padding: 3px 10px; border-radius: 4px; border: none; cursor: pointer; font-weight: 600; }
.pbtn.edit { background: #3b82f6; color: white; }
.pbtn.del { background: #ef4444; color: white; }
.modal-header { background: linear-gradient(135deg, #1e3a5f, #2d6a9f); color: white; }
.modal-header .btn-close { filter: invert(1); }
label.form-label { font-weight: 600; font-size: 13px; }
</style>
</head>
<body>
<div id="topnav">
<i class="fas fa-map-marked-alt fa-lg"></i>
<span class="brand">WebGIS SIG B &mdash; Universitas Tanjungpura</span>
<span class="meta">Athallah Ghathfan Aqila | D1041231060 | Dr. Ir. Yus Sholva</span>
<a href="index.php" class="btn btn-sm btn-light ms-2" title="Kembali ke Portal">
<i class="fas fa-home text-primary me-1"></i>Portal
</a>
</div>
<div id="app">
<div id="sidebar">
<!-- Stats -->
<div class="sb-section">
<div class="sb-title">Ringkasan Data</div>
<div class="stat-grid">
<div class="stat-card">
<div class="stat-num text-primary" id="cnt-spbu">0</div>
<div class="stat-lbl">SPBU</div>
</div>
<div class="stat-card">
<div class="stat-num text-warning" id="cnt-jalan">0</div>
<div class="stat-lbl">Jalan</div>
</div>
<div class="stat-card">
<div class="stat-num" style="color:#8b5cf6" id="cnt-persil">0</div>
<div class="stat-lbl">Persil</div>
</div>
<div class="stat-card">
<div class="stat-num text-secondary" id="cnt-wilayah">0</div>
<div class="stat-lbl">Wilayah</div>
</div>
</div>
</div>
<!-- Layer Controls -->
<div class="sb-section">
<div class="sb-title">Tampilkan Layer</div>
<div class="layer-row" onclick="toggleLayer('spbu')">
<input type="checkbox" id="chk-spbu" checked onclick="event.stopPropagation();toggleLayer('spbu')">
<div class="layer-dot" style="background:#3b82f6"></div>
<span class="layer-lbl">SPBU</span>
<span class="layer-cnt" id="lc-spbu">0</span>
</div>
<div class="layer-row" onclick="toggleLayer('jalan')">
<input type="checkbox" id="chk-jalan" checked onclick="event.stopPropagation();toggleLayer('jalan')">
<div class="layer-dot line" style="background:#f59e0b;width:13px"></div>
<span class="layer-lbl">Ruas Jalan</span>
<span class="layer-cnt" id="lc-jalan">0</span>
</div>
<div class="layer-row" onclick="toggleLayer('persil')">
<input type="checkbox" id="chk-persil" checked onclick="event.stopPropagation();toggleLayer('persil')">
<div class="layer-dot sq" style="background:#8b5cf6"></div>
<span class="layer-lbl">Persil Tanah</span>
<span class="layer-cnt" id="lc-persil">0</span>
</div>
<div class="layer-row" onclick="toggleLayer('wilayah')">
<input type="checkbox" id="chk-wilayah" checked onclick="event.stopPropagation();toggleLayer('wilayah')">
<div class="layer-dot sq" style="background:#a855f7;opacity:.7"></div>
<span class="layer-lbl">Batas Wilayah</span>
<span class="layer-cnt" id="lc-wilayah">0</span>
</div>
</div>
<!-- Actions -->
<div class="sb-section">
<div class="sb-title">Tambah Data</div>
<button class="btn btn-primary btn-sm btn-layer" onclick="openAdd('spbu')">
<i class="fas fa-gas-pump me-2"></i>Tambah SPBU
</button>
<button class="btn btn-warning btn-sm btn-layer text-dark" onclick="openAdd('jalan')">
<i class="fas fa-road me-2"></i>Tambah Ruas Jalan
</button>
<button class="btn btn-sm btn-layer" style="background:#8b5cf6;color:white;border:none" onclick="openAdd('persil')">
<i class="fas fa-draw-polygon me-2"></i>Tambah Persil Tanah
</button>
<button class="btn btn-sm btn-layer" style="background:#a855f7;color:white;border:none" onclick="openAdd('wilayah')">
<i class="fas fa-map me-2"></i>Tambah Batas Wilayah
</button>
<hr class="my-2">
<button class="btn btn-outline-secondary btn-sm btn-layer" onclick="geolocate()">
<i class="fas fa-crosshairs me-2"></i>Lokasi Saya Saat Ini
</button>
</div>
<!-- Legend -->
<div class="sb-section">
<div class="sb-title">Legenda Jalan</div>
<div class="d-flex align-items-center gap-2 mb-1">
<div style="width:18px;height:4px;border-radius:2px;background:#dc2626"></div>
<small class="text-muted">Jalan Nasional</small>
</div>
<div class="d-flex align-items-center gap-2 mb-1">
<div style="width:18px;height:4px;border-radius:2px;background:#f59e0b"></div>
<small class="text-muted">Jalan Provinsi</small>
</div>
<div class="d-flex align-items-center gap-2 mb-1">
<div style="width:18px;height:4px;border-radius:2px;background:#64748b"></div>
<small class="text-muted">Jalan Kabupaten</small>
</div>
<hr class="my-2">
<div class="sb-title">Legenda Persil</div>
<div class="d-flex align-items-center gap-2 mb-1">
<div class="layer-dot" style="background:#22c55e"></div>
<small class="text-muted">SHM</small>
</div>
<div class="d-flex align-items-center gap-2 mb-1">
<div class="layer-dot" style="background:#3b82f6"></div>
<small class="text-muted">HGB</small>
</div>
<div class="d-flex align-items-center gap-2 mb-1">
<div class="layer-dot" style="background:#f59e0b"></div>
<small class="text-muted">HGU</small>
</div>
<div class="d-flex align-items-center gap-2 mb-1">
<div class="layer-dot" style="background:#94a3b8"></div>
<small class="text-muted">HP</small>
</div>
</div>
</div>
<div id="map"></div>
</div>
<div id="pick-hint">
<i class="fas fa-map-pin me-2"></i>Klik peta untuk pilih lokasi
<button class="btn btn-sm btn-light ms-3 py-0" onclick="cancelPick()">Batal</button>
</div>
<!-- Modal SPBU -->
<div class="modal fade" id="modal-spbu" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="title-spbu"><i class="fas fa-gas-pump me-2"></i>Data SPBU</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="spbu-id">
<div class="mb-3">
<label class="form-label">Nama SPBU <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="spbu-nama" placeholder="Contoh: SPBU 64.771.01">
</div>
<div class="row g-2 mb-3">
<div class="col">
<label class="form-label">Nomor SPBU</label>
<input type="text" class="form-control" id="spbu-nomor" placeholder="64.771.01">
</div>
<div class="col">
<label class="form-label">Status 24 Jam</label>
<select class="form-select" id="spbu-status24">
<option value="Ya">Ya</option>
<option value="Tidak">Tidak</option>
</select>
</div>
</div>
<div class="row g-2 mb-3">
<div class="col">
<label class="form-label">Latitude <span class="text-danger">*</span></label>
<input type="number" step="any" class="form-control" id="spbu-lat" placeholder="-0.0000">
</div>
<div class="col">
<label class="form-label">Longitude <span class="text-danger">*</span></label>
<input type="number" step="any" class="form-control" id="spbu-lng" placeholder="109.0000">
</div>
</div>
<button class="btn btn-outline-primary btn-sm w-100" onclick="startPick('spbu')">
<i class="fas fa-map-pin me-1"></i>Pilih Lokasi dari Peta
</button>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
<button class="btn btn-primary" onclick="saveSpbu()"><i class="fas fa-save me-1"></i>Simpan</button>
</div>
</div>
</div>
</div>
<!-- Modal Jalan -->
<div class="modal fade" id="modal-jalan" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="title-jalan"><i class="fas fa-road me-2"></i>Data Ruas Jalan</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="jalan-id">
<input type="hidden" id="jalan-geojson">
<div class="mb-3">
<label class="form-label">Nama Jalan <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="jalan-nama" placeholder="Contoh: Jl. Gajahmada">
</div>
<div class="row g-2 mb-3">
<div class="col">
<label class="form-label">Status Jalan</label>
<select class="form-select" id="jalan-status">
<option>Nasional</option><option>Provinsi</option><option selected>Kabupaten</option>
</select>
</div>
<div class="col">
<label class="form-label">Panjang (m)</label>
<input type="number" class="form-control" id="jalan-panjang" placeholder="0">
</div>
</div>
<div class="mb-3">
<label class="form-label">Jalur GeoJSON <span class="text-danger">*</span></label>
<button class="btn btn-warning btn-sm mb-2" onclick="startDraw('polyline','jalan')">
<i class="fas fa-pencil-alt me-1"></i>Gambar Jalur di Peta
</button>
<textarea class="form-control font-monospace" id="jalan-geojson-view" rows="3" placeholder='{"type":"LineString","coordinates":[[lng,lat],...]}' readonly></textarea>
<div id="jalan-geojson-status"></div>
<small class="text-muted">Klik ganda untuk selesai menggambar</small>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
<button class="btn btn-warning" onclick="saveJalan()"><i class="fas fa-save me-1"></i>Simpan</button>
</div>
</div>
</div>
</div>
<!-- Modal Persil -->
<div class="modal fade" id="modal-persil" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="title-persil"><i class="fas fa-draw-polygon me-2"></i>Data Persil Tanah</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="persil-id">
<input type="hidden" id="persil-geojson">
<div class="row g-2 mb-3">
<div class="col">
<label class="form-label">Nomor Persil <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="persil-nomor" placeholder="Contoh: P-2024-001">
</div>
<div class="col">
<label class="form-label">Status Kepemilikan</label>
<select class="form-select" id="persil-status">
<option>SHM</option><option>HGB</option><option>HGU</option><option>HP</option>
</select>
</div>
</div>
<div class="mb-3">
<label class="form-label">Luas (m²)</label>
<input type="number" class="form-control" id="persil-luas" placeholder="0">
</div>
<div class="mb-3">
<label class="form-label">Poligon GeoJSON <span class="text-danger">*</span></label>
<button class="btn btn-sm mb-2" style="background:#8b5cf6;color:white;border:none" onclick="startDraw('polygon','persil')">
<i class="fas fa-vector-square me-1"></i>Gambar Poligon di Peta
</button>
<textarea class="form-control font-monospace" id="persil-geojson-view" rows="3" placeholder='{"type":"Polygon","coordinates":[[[lng,lat],...]]}' readonly></textarea>
<div id="persil-geojson-status"></div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
<button class="btn btn-sm" style="background:#8b5cf6;color:white;border:none;padding:.375rem .75rem" onclick="savePersil()"><i class="fas fa-save me-1"></i>Simpan</button>
</div>
</div>
</div>
</div>
<!-- Modal Wilayah -->
<div class="modal fade" id="modal-wilayah" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="title-wilayah"><i class="fas fa-map me-2"></i>Batas Wilayah Administrasi</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="wil-id">
<input type="hidden" id="wil-geojson">
<div class="row g-2 mb-3">
<div class="col-8">
<label class="form-label">Nama Wilayah <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="wil-nama" placeholder="Contoh: RT 001 / Kelurahan Bansir">
</div>
<div class="col-4">
<label class="form-label">Tipe Wilayah</label>
<select class="form-select" id="wil-tipe">
<option>RT</option><option>RW</option><option>Kelurahan</option><option>Kecamatan</option>
</select>
</div>
</div>
<div class="mb-3">
<label class="form-label">Batas GeoJSON <span class="text-danger">*</span></label>
<button class="btn btn-sm mb-2" style="background:#a855f7;color:white;border:none" onclick="startDraw('polygon','wilayah')">
<i class="fas fa-draw-polygon me-1"></i>Gambar Batas Wilayah
</button>
<textarea class="form-control font-monospace" id="wil-geojson-view" rows="3" placeholder='{"type":"Polygon","coordinates":[...]}' readonly></textarea>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
<button class="btn btn-sm" style="background:#a855f7;color:white;border:none;padding:.375rem .75rem" onclick="saveWilayah()"><i class="fas fa-save me-1"></i>Simpan</button>
</div>
</div>
</div>
</div>
<!-- Toast -->
<div class="position-fixed bottom-0 end-0 p-3" style="z-index:9999">
<div id="toast-el" class="toast align-items-center text-white border-0" role="alert">
<div class="d-flex">
<div class="toast-body fw-semibold" id="toast-msg">...</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
<script>
const map = L.map('map', { zoomControl: true }).setView([-0.025, 109.34], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; OpenStreetMap contributors | WebGIS SIG B Untan'
}).addTo(map);
const LG = {
spbu: L.layerGroup().addTo(map),
jalan: L.layerGroup().addTo(map),
persil: L.layerGroup().addTo(map),
wilayah: L.layerGroup().addTo(map),
};
let D = { spbu:[], jalan:[], persil:[], wilayah:[] };
let pickFor = null, drawTarget = null;
let userMarker = null, drawTempLayer = null;
const drawnItems = new L.FeatureGroup();
map.addLayer(drawnItems);
function mkIcon(bg, fa) {
return L.divIcon({
className: '',
html: `<div style="background:${bg};width:30px;height:30px;border-radius:50%;display:flex;align-items:center;justify-content:center;border:2.5px solid white;box-shadow:0 2px 6px rgba(0,0,0,.35);color:white;font-size:13px"><i class="fas fa-${fa}"></i></div>`,
iconSize: [30,30], iconAnchor:[15,15], popupAnchor:[0,-15]
});
}
const ICONS = {
spbu: mkIcon('#3b82f6','gas-pump'),
me: mkIcon('#f59e0b','person-walking'),
};
const JCOL = { Nasional:'#dc2626', Provinsi:'#f59e0b', Kabupaten:'#64748b' };
const PCOL = { SHM:'#22c55e', HGB:'#3b82f6', HGU:'#f59e0b', HP:'#94a3b8' };
async function loadData() {
const r = await fetch('get_all_data.php');
D = await r.json();
renderAll();
}
function renderAll() {
Object.values(LG).forEach(l => l.clearLayers());
renderSpbu(); renderJalan(); renderPersil(); renderWilayah();
updateStats();
}
function renderSpbu() {
D.spbu.forEach(d => {
if (!d.lat||!d.lng) return;
L.marker([d.lat,d.lng],{icon:ICONS.spbu})
.bindPopup(`<div class="popup-title"><i class="fas fa-gas-pump me-1 text-primary"></i>${d.nama}</div>
<div class="popup-row"><b>Nomor</b>${d.nomor||'-'}</div>
<div class="popup-row"><b>24 Jam</b>${d.status_24jam}</div>
<div class="popup-btns">
<button class="pbtn edit" onclick="openEdit('spbu',${d.id})">Edit</button>
<button class="pbtn del" onclick="delItem('spbu',${d.id})">Hapus</button>
</div>`)
.addTo(LG.spbu);
});
document.getElementById('lc-spbu').textContent = D.spbu.length;
}
function renderJalan() {
D.jalan.forEach(d => {
if (!d.geojson) return;
try {
L.geoJSON(JSON.parse(d.geojson), {
style: { color: JCOL[d.status_jalan]||'#64748b', weight: 5, opacity: 0.85 }
}).bindPopup(`<div class="popup-title"><i class="fas fa-road me-1"></i>${d.nama_jalan}</div>
<div class="popup-row"><b>Status</b>${d.status_jalan}</div>
<div class="popup-row"><b>Panjang</b>${d.panjang_m||'-'} m</div>
<div class="popup-btns">
<button class="pbtn edit" onclick="openEdit('jalan',${d.id})">Edit</button>
<button class="pbtn del" onclick="delItem('jalan',${d.id})">Hapus</button>
</div>`)
.addTo(LG.jalan);
} catch(e){}
});
document.getElementById('lc-jalan').textContent = D.jalan.length;
}
function renderPersil() {
D.persil.forEach(d => {
if (!d.geojson) return;
try {
const c = PCOL[d.status_milik]||'#8b5cf6';
L.geoJSON(JSON.parse(d.geojson), {
style: { color: c, fillColor: c, fillOpacity: 0.3, weight: 2 }
}).bindPopup(`<div class="popup-title"><i class="fas fa-draw-polygon me-1" style="color:#8b5cf6"></i>${d.nomor_persil}</div>
<div class="popup-row"><b>Status</b>${d.status_milik}</div>
<div class="popup-row"><b>Luas</b>${d.luas_m2||'-'} m²</div>
<div class="popup-btns">
<button class="pbtn edit" onclick="openEdit('persil',${d.id})">Edit</button>
<button class="pbtn del" onclick="delItem('persil',${d.id})">Hapus</button>
</div>`)
.addTo(LG.persil);
} catch(e){}
});
document.getElementById('lc-persil').textContent = D.persil.length;
}
function renderWilayah() {
D.wilayah.forEach(d => {
if (!d.geojson) return;
try {
L.geoJSON(JSON.parse(d.geojson), {
style: { color:'#a855f7', fillColor:'#a855f7', fillOpacity:0.08, weight:2, dashArray:'6 4' }
}).bindPopup(`<div class="popup-title"><i class="fas fa-map me-1" style="color:#a855f7"></i>${d.nama}</div>
<div class="popup-row"><b>Tipe</b>${d.tipe}</div>
<div class="popup-btns">
<button class="pbtn edit" onclick="openEdit('wilayah',${d.id})">Edit</button>
<button class="pbtn del" onclick="delItem('wilayah',${d.id})">Hapus</button>
</div>`)
.addTo(LG.wilayah);
} catch(e){}
});
document.getElementById('lc-wilayah').textContent = D.wilayah.length;
}
function updateStats() {
['spbu','jalan','persil','wilayah'].forEach(k => {
document.getElementById('cnt-'+k).textContent = D[k]?.length ?? 0;
});
}
function toggleLayer(name) {
const chk = document.getElementById('chk-'+name);
if (chk.checked) map.addLayer(LG[name]); else map.removeLayer(LG[name]);
}
function startPick(type) {
pickFor = type;
document.getElementById('pick-hint').style.display = 'block';
map.getContainer().style.cursor = 'crosshair';
bootstrap.Modal.getInstance(document.getElementById('modal-'+type))?.hide();
}
function cancelPick() {
pickFor = null;
document.getElementById('pick-hint').style.display = 'none';
map.getContainer().style.cursor = '';
}
map.on('click', async function(e) {
if (!pickFor) return;
const {lat, lng} = e.latlng;
const t = pickFor;
document.getElementById(t+'-lat').value = lat.toFixed(7);
document.getElementById(t+'-lng').value = lng.toFixed(7);
try {
const r = await fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&accept-language=id`);
const j = await r.json();
const aEl = document.getElementById(t+'-alamat');
if (aEl) aEl.value = j.display_name||'';
} catch(err){}
cancelPick();
bootstrap.Modal.getOrCreateInstance(document.getElementById('modal-'+t)).show();
});
function startDraw(shapeType, target) {
drawTarget = target;
document.querySelectorAll('.modal').forEach(m => bootstrap.Modal.getInstance(m)?.hide());
if (drawTempLayer) { map.removeLayer(drawTempLayer); drawTempLayer = null; }
if (shapeType === 'polyline') {
new L.Draw.Polyline(map, {shapeOptions:{color:'#f59e0b',weight:4}}).enable();
} else {
new L.Draw.Polygon(map, {shapeOptions:{color:'#8b5cf6',weight:2,fillOpacity:0.3}}).enable();
}
showToast('Klik peta untuk menggambar titik. Klik ganda untuk selesai.','info');
}
map.on('draw:created', function(e) {
if (drawTempLayer) map.removeLayer(drawTempLayer);
drawTempLayer = e.layer;
map.addLayer(drawTempLayer);
const gj = JSON.stringify(e.layer.toGeoJSON().geometry);
if (drawTarget === 'jalan') {
document.getElementById('jalan-geojson').value = gj;
document.getElementById('jalan-geojson-view').value = gj;
document.getElementById('jalan-geojson-status').innerHTML = '<small class="text-success mt-1 d-block"><i class="fas fa-check-circle me-1"></i>Jalur baru sudah digambar</small>';
bootstrap.Modal.getOrCreateInstance(document.getElementById('modal-jalan')).show();
} else if (drawTarget === 'persil') {
document.getElementById('persil-geojson').value = gj;
document.getElementById('persil-geojson-view').value = gj;
document.getElementById('persil-geojson-status').innerHTML = '<small class="text-success mt-1 d-block"><i class="fas fa-check-circle me-1"></i>Area persil baru sudah digambar</small>';
bootstrap.Modal.getOrCreateInstance(document.getElementById('modal-persil')).show();
} else if (drawTarget === 'wilayah') {
document.getElementById('wil-geojson').value = gj;
document.getElementById('wil-geojson-view').value = gj;
bootstrap.Modal.getOrCreateInstance(document.getElementById('modal-wilayah')).show();
}
});
document.getElementById('modal-jalan').addEventListener('hidden.bs.modal',()=>{
if(drawTempLayer){map.removeLayer(drawTempLayer);drawTempLayer=null;}
});
document.getElementById('modal-persil').addEventListener('hidden.bs.modal',()=>{
if(drawTempLayer){map.removeLayer(drawTempLayer);drawTempLayer=null;}
});
function openAdd(type) {
resetForm(type);
document.getElementById(`title-${type}`).innerHTML = `<i class="fas fa-plus-circle me-2"></i>Tambah Data ${type.charAt(0).toUpperCase()+type.slice(1)}`;
new bootstrap.Modal(document.getElementById('modal-'+type)).show();
}
function openEdit(type, id) {
const d = D[type].find(x => x.id==id);
if (!d) return;
resetForm(type);
document.getElementById(`title-${type}`).innerHTML = `<i class="fas fa-edit me-2"></i>Edit Data ${type.charAt(0).toUpperCase()+type.slice(1)}`;
fillForm(type, d);
new bootstrap.Modal(document.getElementById('modal-'+type)).show();
}
function resetForm(type) {
const fields = {
spbu: ['spbu-id','spbu-nama','spbu-nomor','spbu-lat','spbu-lng'],
jalan: ['jalan-id','jalan-nama','jalan-panjang','jalan-geojson','jalan-geojson-view'],
persil: ['persil-id','persil-nomor','persil-luas','persil-geojson','persil-geojson-view'],
wilayah: ['wil-id','wil-nama','wil-geojson','wil-geojson-view'],
};
(fields[type]||[]).forEach(id => { const el=document.getElementById(id); if(el) el.value=''; });
if (type==='spbu') document.getElementById('spbu-status24').value='Tidak';
if (type==='jalan') { const s=document.getElementById('jalan-geojson-status'); if(s) s.innerHTML=''; }
if (type==='persil') { const s=document.getElementById('persil-geojson-status'); if(s) s.innerHTML=''; }
}
function fillForm(type, d) {
if (type==='spbu') {
document.getElementById('spbu-id').value=d.id;
document.getElementById('spbu-nama').value=d.nama||'';
document.getElementById('spbu-nomor').value=d.nomor||'';
document.getElementById('spbu-status24').value=d.status_24jam||'Tidak';
document.getElementById('spbu-lat').value=d.lat;
document.getElementById('spbu-lng').value=d.lng;
} else if (type==='jalan') {
document.getElementById('jalan-id').value=d.id;
document.getElementById('jalan-nama').value=d.nama_jalan||'';
document.getElementById('jalan-status').value=d.status_jalan||'Kabupaten';
document.getElementById('jalan-panjang').value=d.panjang_m||'';
document.getElementById('jalan-geojson').value=d.geojson||'';
document.getElementById('jalan-geojson-view').value=d.geojson||'';
if (d.geojson) {
try {
if (drawTempLayer) { map.removeLayer(drawTempLayer); drawTempLayer = null; }
drawTempLayer = L.geoJSON(JSON.parse(d.geojson), {
style: { color:'#f59e0b', weight:6, opacity:0.9, dashArray:'8 4' }
}).addTo(map);
map.fitBounds(drawTempLayer.getBounds(), {padding:[40,40]});
} catch(e) {}
document.getElementById('jalan-geojson-status').innerHTML =
'<small class="text-success mt-1 d-block"><i class="fas fa-check-circle me-1"></i>Geometri tersimpan — simpan langsung atau gambar ulang untuk mengubah jalur</small>';
}
} else if (type==='persil') {
document.getElementById('persil-id').value=d.id;
document.getElementById('persil-nomor').value=d.nomor_persil||'';
document.getElementById('persil-status').value=d.status_milik||'SHM';
document.getElementById('persil-luas').value=d.luas_m2||'';
document.getElementById('persil-geojson').value=d.geojson||'';
document.getElementById('persil-geojson-view').value=d.geojson||'';
if (d.geojson) {
try {
if (drawTempLayer) { map.removeLayer(drawTempLayer); drawTempLayer = null; }
drawTempLayer = L.geoJSON(JSON.parse(d.geojson), {
style: { color:'#8b5cf6', weight:3, fillColor:'#8b5cf6', fillOpacity:0.25, dashArray:'6 3' }
}).addTo(map);
map.fitBounds(drawTempLayer.getBounds(), {padding:[40,40]});
} catch(e) {}
document.getElementById('persil-geojson-status').innerHTML =
'<small class="text-success mt-1 d-block"><i class="fas fa-check-circle me-1"></i>Geometri tersimpan — simpan langsung atau gambar ulang untuk mengubah area</small>';
}
} else if (type==='wilayah') {
document.getElementById('wil-id').value=d.id;
document.getElementById('wil-nama').value=d.nama||'';
document.getElementById('wil-tipe').value=d.tipe||'RT';
document.getElementById('wil-geojson').value=d.geojson||'';
document.getElementById('wil-geojson-view').value=d.geojson||'';
}
}
async function post(url, fd) {
const r = await fetch(url, {method:'POST', body:fd});
return r.json();
}
async function saveSpbu() {
const id = document.getElementById('spbu-id').value;
const fd = new FormData();
fd.append('nama', document.getElementById('spbu-nama').value);
fd.append('nomor', document.getElementById('spbu-nomor').value);
fd.append('status', document.getElementById('spbu-status24').value);
fd.append('lat', document.getElementById('spbu-lat').value);
fd.append('lng', document.getElementById('spbu-lng').value);
let url = 'simpan_spbu.php';
if (id) { fd.append('id',id); fd.append('type','spbu'); url='update_data.php'; }
const j = await post(url, fd);
if(j.status==='success'){closeModal('spbu');await loadData();showToast('SPBU berhasil disimpan','success');}
else showToast('Error: '+(j.message||'Gagal'),'danger');
}
async function saveJalan() {
const gj = document.getElementById('jalan-geojson').value;
if(!gj){showToast('Gambar jalur di peta terlebih dahulu','warning');return;}
const id = document.getElementById('jalan-id').value;
const fd = new FormData();
fd.append('nama_jalan', document.getElementById('jalan-nama').value);
fd.append('status_jalan', document.getElementById('jalan-status').value);
fd.append('panjang', document.getElementById('jalan-panjang').value||0);
fd.append('geojson', gj);
let url = 'simpan_jalan.php';
if (id) { fd.append('id',id); fd.append('type','jalan'); url='update_data.php'; }
const j = await post(url, fd);
if(j.status==='success'){closeModal('jalan');if(drawTempLayer){map.removeLayer(drawTempLayer);drawTempLayer=null;}await loadData();showToast('Jalan berhasil disimpan','success');}
else showToast('Error: '+(j.message||'Gagal'),'danger');
}
async function savePersil() {
const gj = document.getElementById('persil-geojson').value;
if(!gj){showToast('Gambar persil di peta terlebih dahulu','warning');return;}
const id = document.getElementById('persil-id').value;
const fd = new FormData();
fd.append('nomor_persil', document.getElementById('persil-nomor').value);
fd.append('status_milik', document.getElementById('persil-status').value);
fd.append('luas', document.getElementById('persil-luas').value||0);
fd.append('geojson', gj);
let url = 'simpan_persil.php';
if (id) { fd.append('id',id); fd.append('type','persil'); url='update_data.php'; }
const j = await post(url, fd);
if(j.status==='success'){closeModal('persil');if(drawTempLayer){map.removeLayer(drawTempLayer);drawTempLayer=null;}await loadData();showToast('Persil berhasil disimpan','success');}
else showToast('Error: '+(j.message||'Gagal'),'danger');
}
async function saveWilayah() {
const gj = document.getElementById('wil-geojson').value;
if(!gj){showToast('Gambar batas wilayah di peta terlebih dahulu','warning');return;}
const id = document.getElementById('wil-id').value;
const fd = new FormData();
fd.append('nama', document.getElementById('wil-nama').value);
fd.append('tipe', document.getElementById('wil-tipe').value);
fd.append('geojson', gj);
let url = 'simpan_wilayah.php';
if (id) { fd.append('id',id); fd.append('type','wilayah'); url='update_data.php'; }
const j = await post(url, fd);
if(j.status==='success'){closeModal('wilayah');if(drawTempLayer){map.removeLayer(drawTempLayer);drawTempLayer=null;}await loadData();showToast('Wilayah berhasil disimpan','success');}
else showToast('Error: '+(j.message||'Gagal'),'danger');
}
async function delItem(type, id) {
if(!confirm('Hapus data ini?')) return;
const fd = new FormData();
fd.append('type',type); fd.append('id',id);
const j = await post('hapus.php', fd);
if(j.status==='success'){await loadData();showToast('Data berhasil dihapus','success');}
else showToast('Gagal: '+(j.message||'Error'),'danger');
}
function closeModal(type) {
bootstrap.Modal.getInstance(document.getElementById('modal-'+type))?.hide();
}
function geolocate() {
navigator.geolocation.getCurrentPosition(pos => {
const {latitude:lat, longitude:lng} = pos.coords;
if (userMarker) map.removeLayer(userMarker);
userMarker = L.marker([lat,lng],{icon:ICONS.me}).addTo(map).bindPopup('<b>📍 Lokasi Saya</b>').openPopup();
map.setView([lat,lng], 16);
showToast('Lokasi ditemukan!','success');
}, () => showToast('Gagal mendapatkan lokasi GPS','danger'));
}
function showToast(msg, type='success') {
const colors = {success:'#22c55e', danger:'#ef4444', warning:'#f59e0b', info:'#3b82f6'};
const el = document.getElementById('toast-el');
el.style.background = colors[type]||colors.success;
document.getElementById('toast-msg').textContent = msg;
new bootstrap.Toast(el, {delay:3000}).show();
}
loadData();
</script>
</body>
</html>