Files
WebGIS/pertemuan1/script.js
T

578 lines
24 KiB
JavaScript

// ==========================================
// 1. INISIALISASI PETA & LAYER
// ==========================================
var map = L.map('map', { zoomControl: false }).setView([-0.0227, 109.3425], 14);
// Zoom control di kanan bawah (tampilan modern)
L.control.zoom({ position: 'bottomright' }).addTo(map);
// OpenStreetMap Tile Layer
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
// Layer Groups
var jalanLayer = L.layerGroup().addTo(map);
var parsilLayer = L.layerGroup().addTo(map);
var drawnItems = new L.FeatureGroup().addTo(map);
// Layer Control (Checkbox di kanan atas)
var overlayMaps = {
"🛣️ Jaringan Jalan": jalanLayer,
"🏘️ Parsil Bangunan": parsilLayer
};
L.control.layers(null, overlayMaps, { collapsed: true, position: 'bottomleft' }).addTo(map);
// Global Variables
var databasePencarian = [];
var sidebarDrawMode = null; // 'polyline' | 'polygon'
var sidebarKoordinat = "";
var sidebarDrawHandler = null;
// ==========================================
// 2. FUNGSI LOAD DATA DARI DATABASE
// ==========================================
function loadJalan() {
jalanLayer.clearLayers();
// Reset database pencarian khusus kategori Jalan
databasePencarian = databasePencarian.filter(item => item.kategori !== 'Jalan');
fetch('load.php?jenis=jalan')
.then(res => res.json())
.then(data => {
data.forEach(item => {
try {
var geom = JSON.parse(item.koordinat);
// Warna berdasarkan status jalan
var warna = item.status === 'Jalan Nasional' ? '#ef4444' // Red
: item.status === 'Jalan Provinsi' ? '#fbbf24' // Yellow/Amber
: '#10b981'; // Green (Kabupaten)
var viewHTML = `
<div class="popup-container" id="popup-jalan-${item.id}">
<div class="popup-header popup-header-jalan">
<h4>${item.nama_jalan}</h4>
<span class="popup-type-badge">🛣️ Jaringan Jalan</span>
</div>
<div class="popup-body">
<div class="info-row">
<span class="info-label">Status Jalan</span>
<span class="info-value" style="color:${warna}; font-weight:600;">${item.status}</span>
</div>
<div class="info-row">
<span class="info-label">Panjang Terukur</span>
<span class="info-value">${item.panjang} m</span>
</div>
<div class="btn-group">
<button class="btn btn-edit" onclick="formEditJalan('${item.id}', '${item.nama_jalan}', '${item.status}')">✏️ Edit</button>
<button class="btn btn-delete" onclick="deleteData('jalan', '${item.id}')">🗑️ Hapus</button>
</div>
</div>
</div>`;
var layerJalan = L.geoJSON(geom, {
style: { color: warna, weight: 5, opacity: 0.85 }
}).bindPopup(viewHTML).addTo(jalanLayer);
layerJalan.on('click', function () {
map.fitBounds(layerJalan.getBounds(), { padding: [50, 50] });
});
databasePencarian.push({
namaLower: item.nama_jalan.toLowerCase(),
namaAsli: item.nama_jalan,
kategori: 'Jalan',
ikon: '🛣️',
tipe: 'area',
layer: layerJalan
});
} catch (error) {
console.error("Error parse koordinat jalan ID: " + item.id, error);
}
});
})
.catch(e => console.log('Belum ada data Jalan', e));
}
function loadParsil() {
parsilLayer.clearLayers();
// Reset database pencarian khusus kategori Parsil
databasePencarian = databasePencarian.filter(item => item.kategori !== 'Parsil');
fetch('load.php?jenis=parsil')
.then(res => res.json())
.then(data => {
data.forEach(item => {
try {
var geom = JSON.parse(item.koordinat);
// Warna berdasarkan status sertifikat tanah
var warna = item.status === 'SHM' ? '#10b981' // Green
: item.status === 'HGB' ? '#3b82f6' // Blue
: item.status === 'HGU' ? '#8b5cf6' // Purple
: '#f59e0b'; // Orange (HP)
var viewHTML = `
<div class="popup-container" id="popup-parsil-${item.id}">
<div class="popup-header popup-header-parsil">
<h4>${item.nama_pemilik}</h4>
<span class="popup-type-badge">🏘️ Parsil Bangunan</span>
</div>
<div class="popup-body">
<div class="info-row">
<span class="info-label">Status Sertifikat</span>
<span class="info-value" style="color:${warna}; font-weight:600;">${item.status}</span>
</div>
<div class="info-row">
<span class="info-label">Luas Terukur</span>
<span class="info-value">${item.luas} m²</span>
</div>
<div class="btn-group">
<button class="btn btn-edit" onclick="formEditParsil('${item.id}', '${item.nama_pemilik}', '${item.status}')">✏️ Edit</button>
<button class="btn btn-delete" onclick="deleteData('parsil', '${item.id}')">🗑️ Hapus</button>
</div>
</div>
</div>`;
var layerParsil = L.geoJSON(geom, {
style: { fillColor: warna, color: warna, weight: 2, fillOpacity: 0.45 }
}).bindPopup(viewHTML).addTo(parsilLayer);
layerParsil.on('click', function () {
map.fitBounds(layerParsil.getBounds(), { padding: [50, 50] });
});
databasePencarian.push({
namaLower: item.nama_pemilik.toLowerCase(),
namaAsli: item.nama_pemilik,
kategori: 'Parsil',
ikon: '🏘️',
tipe: 'area',
layer: layerParsil
});
} catch (error) {
console.error("Error parse koordinat parsil ID: " + item.id, error);
}
});
})
.catch(e => console.log('Belum ada data Parsil', e));
}
// Load data awal
loadJalan();
loadParsil();
// ==========================================
// 3. EDIT & UPDATE DATA
// ==========================================
function formEditJalan(id, nama, status) {
setTimeout(function () {
var container = document.getElementById(`popup-jalan-${id}`);
if (!container) return;
container.innerHTML = `
<div class="popup-header popup-header-jalan" style="border-radius: 0;">
<h4 style="margin:0;">Edit Detail Jalan</h4>
</div>
<div class="popup-body">
<form onsubmit="updateData(event, 'jalan', '${id}')">
<div class="sfield" style="margin-bottom:8px;">
<span class="info-label">Nama Jalan</span>
<input type="text" id="edit_nama" value="${nama}" required style="margin-bottom:0;">
</div>
<div class="sfield" style="margin-bottom:12px;">
<span class="info-label">Status Jalan</span>
<select id="edit_status" style="margin-bottom:0;">
<option value="Jalan Nasional" ${status === 'Jalan Nasional' ? 'selected' : ''}>Jalan Nasional</option>
<option value="Jalan Provinsi" ${status === 'Jalan Provinsi' ? 'selected' : ''}>Jalan Provinsi</option>
<option value="Jalan Kabupaten" ${status === 'Jalan Kabupaten' ? 'selected' : ''}>Jalan Kabupaten</option>
</select>
</div>
<div class="btn-group">
<button type="submit" class="btn btn-save">💾 Simpan</button>
<button type="button" class="btn btn-delete" onclick="loadJalan();">Batal</button>
</div>
</form>
</div>`;
}, 50);
}
function formEditParsil(id, nama, status) {
setTimeout(function () {
var container = document.getElementById(`popup-parsil-${id}`);
if (!container) return;
container.innerHTML = `
<div class="popup-header popup-header-parsil" style="border-radius: 0;">
<h4 style="margin:0;">Edit Detail Parsil</h4>
</div>
<div class="popup-body">
<form onsubmit="updateData(event, 'parsil', '${id}')">
<div class="sfield" style="margin-bottom:8px;">
<span class="info-label">Nama Pemilik</span>
<input type="text" id="edit_nama" value="${nama}" required style="margin-bottom:0;">
</div>
<div class="sfield" style="margin-bottom:12px;">
<span class="info-label">Status Sertifikat</span>
<select id="edit_status" style="margin-bottom:0;">
<option value="SHM" ${status === 'SHM' ? 'selected' : ''}>SHM (Hak Milik)</option>
<option value="HGB" ${status === 'HGB' ? 'selected' : ''}>HGB (Guna Bangunan)</option>
<option value="HGU" ${status === 'HGU' ? 'selected' : ''}>HGU (Guna Usaha)</option>
<option value="HP" ${status === 'HP' ? 'selected' : ''}>HP (Hak Pakai)</option>
</select>
</div>
<div class="btn-group">
<button type="submit" class="btn btn-save">💾 Simpan</button>
<button type="button" class="btn btn-delete" onclick="loadParsil();">Batal</button>
</div>
</form>
</div>`;
}, 50);
}
function updateData(e, tabel, id) {
e.preventDefault();
var formData = new FormData();
formData.append('tabel', tabel);
formData.append('id', id);
if (tabel === 'jalan') {
formData.append('nama_jalan', document.getElementById('edit_nama').value);
formData.append('status', document.getElementById('edit_status').value);
} else if (tabel === 'parsil') {
formData.append('nama_pemilik', document.getElementById('edit_nama').value);
formData.append('status', document.getElementById('edit_status').value);
}
fetch('update_data.php', { method: 'POST', body: formData })
.then(r => r.text())
.then(res => {
alert(res);
map.closePopup();
if (tabel === 'jalan') loadJalan();
if (tabel === 'parsil') loadParsil();
})
.catch(error => console.error("Error updating data:", error));
}
// ==========================================
// 4. HAPUS DATA
// ==========================================
function deleteData(tabel, id) {
if (confirm('Apakah Anda yakin ingin menghapus data ini secara permanen?')) {
var formData = new FormData();
formData.append('id', id);
formData.append('tabel', tabel);
fetch('delete_data.php', { method: 'POST', body: formData })
.then(r => r.text())
.then(res => {
alert(res);
map.closePopup();
if (tabel === 'jalan') loadJalan();
if (tabel === 'parsil') loadParsil();
})
.catch(error => console.error("Error deleting data:", error));
}
}
// ==========================================
// 5. SISTEM PENCARIAN & AUTOCMPLETE
// ==========================================
function jalankanPencarian() {
var kataKunci = document.getElementById('search-input').value.toLowerCase();
var kategori = document.getElementById('search-category').value;
var wadahHasil = document.getElementById('search-results');
wadahHasil.innerHTML = '';
if (kataKunci.length < 2) {
wadahHasil.style.display = 'none';
return;
}
var hasil = databasePencarian.filter(function (item) {
var cocokNama = item.namaLower.includes(kataKunci);
var cocokKategori = (kategori === 'Semua') || (item.kategori === kategori);
return cocokNama && cocokKategori;
});
if (hasil.length > 0) {
wadahHasil.style.display = 'block';
wadahHasil.innerHTML = `<div class="results-header">${hasil.length} hasil ditemukan</div>`;
hasil.forEach(function (item) {
var badgeClass = item.kategori === 'Jalan' ? 'badge-Jalan' : 'badge-Parsil';
var bgColors = item.kategori === 'Jalan' ? 'rgba(245,158,11,0.1)' : 'rgba(163,116,255,0.1)';
var div = document.createElement('div');
div.className = 'result-item';
div.innerHTML = `
<div class="result-item-icon" style="background: ${bgColors};">
${item.ikon}
</div>
<div class="result-item-text">
<div class="result-item-name">${item.namaAsli}</div>
<div class="result-item-sub">Klik untuk zoom ke lokasi</div>
</div>
<span class="badge-kategori ${badgeClass}">${item.kategori}</span>`;
div.onclick = function () {
map.fitBounds(item.layer.getBounds(), { padding: [50, 50] });
item.layer.openPopup();
wadahHasil.style.display = 'none';
document.getElementById('search-input').value = item.namaAsli;
toggleClearBtn();
};
wadahHasil.appendChild(div);
});
} else {
wadahHasil.style.display = 'block';
wadahHasil.innerHTML = `
<div class="result-empty">
<div class="result-item" style="color:#ef4444; justify-content:center;">Data tidak ditemukan</div>
</div>`;
}
}
function toggleClearBtn() {
var input = document.getElementById('search-input');
var clearBtn = document.getElementById('search-clear');
clearBtn.style.display = input.value.length > 0 ? 'flex' : 'none';
}
function clearSearch() {
document.getElementById('search-input').value = '';
document.getElementById('search-results').style.display = 'none';
document.getElementById('search-clear').style.display = 'none';
}
// Tutup hasil saat klik di luar
document.addEventListener('click', function (e) {
var wrapper = document.querySelector('.search-wrapper');
if (wrapper && !wrapper.contains(e.target)) {
document.getElementById('search-results').style.display = 'none';
}
});
// Shortcut Ctrl + K untuk fokus ke pencarian
document.addEventListener('keydown', function (e) {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault();
document.getElementById('search-input').focus();
}
if (e.key === 'Escape') {
clearSearch();
document.getElementById('search-input').blur();
}
});
// ==========================================
// 6. LOGIKA INPUT SIDEBAR & PROGRAMMATIC DRAW
// ==========================================
// Buka/Tutup Sidebar
document.getElementById('sidebar-toggle').addEventListener('click', function () {
var sidebar = document.getElementById('input-sidebar');
sidebar.classList.toggle('open');
this.classList.toggle('open');
});
document.getElementById('sidebar-close').addEventListener('click', function () {
document.getElementById('input-sidebar').classList.remove('open');
document.getElementById('sidebar-toggle').classList.remove('open');
});
// Ganti Tab
function switchSidebarTab(tabName) {
document.querySelectorAll('.stab').forEach(function (t) { t.classList.remove('active'); });
document.querySelectorAll('.stab-content').forEach(function (c) { c.classList.remove('active'); });
document.querySelector('.stab[data-tab="' + tabName + '"]').classList.add('active');
document.getElementById('tab-' + tabName).classList.add('active');
// Batalkan mode draw jika ganti tab
deactivatePickMode();
}
// Aktifkan mode gambar garis/poligon secara terprogram
function activateDrawMode(type) {
deactivatePickMode();
sidebarDrawMode = type;
if (type === 'polyline') {
sidebarDrawHandler = new L.Draw.Polyline(map, {
shapeOptions: { color: '#6366f1', weight: 4 }
});
document.getElementById('coords-jalan-val').textContent = 'Sedang menggambar...';
document.getElementById('btn-draw-jalan').classList.add('active-pick');
} else if (type === 'polygon') {
sidebarDrawHandler = new L.Draw.Polygon(map, {
shapeOptions: { color: '#6366f1', fillColor: '#6366f1', fillOpacity: 0.3 }
});
document.getElementById('coords-parsil-val').textContent = 'Sedang menggambar...';
document.getElementById('btn-draw-parsil').classList.add('active-pick');
}
if (sidebarDrawHandler) sidebarDrawHandler.enable();
document.getElementById('pick-overlay').classList.add('active');
var teks = {
polyline: 'Klik titik-titik ruas jalan di peta. Klik dua kali titik terakhir untuk selesai.',
polygon: 'Klik titik-titik batas lahan di peta. Klik dua kali titik terakhir untuk selesai.'
};
document.getElementById('pick-overlay-text').textContent = teks[type];
map.getContainer().style.cursor = 'crosshair';
}
// Batalkan mode draw
function deactivatePickMode() {
sidebarDrawMode = null;
document.getElementById('pick-overlay').classList.remove('active');
document.querySelectorAll('.btn-pick-location').forEach(function (b) { b.classList.remove('active-pick'); });
map.getContainer().style.cursor = '';
if (sidebarDrawHandler) {
try { sidebarDrawHandler.disable(); } catch (e) { }
sidebarDrawHandler = null;
}
}
// Menangkap event Leaflet Draw CREATED
map.on(L.Draw.Event.CREATED, function (e) {
var type = e.layerType, layer = e.layer;
var geojson = layer.toGeoJSON();
drawnItems.clearLayers();
drawnItems.addLayer(layer);
if (sidebarDrawMode === 'polyline' && type === 'polyline') {
// turf.length menghitung dalam KM, ubah ke meter
var panjangMeter = (turf.length(geojson, { units: 'kilometers' }) * 1000).toFixed(2);
sidebarKoordinat = JSON.stringify(geojson.geometry);
var el = document.getElementById('coords-jalan-val');
el.textContent = panjangMeter + ' m';
el.classList.add('has-coords');
document.getElementById('sb_panjang_jalan').value = panjangMeter + ' m';
deactivatePickMode();
} else if (sidebarDrawMode === 'polygon' && type === 'polygon') {
// turf.area menghitung dalam meter persegi
var luasMeter = turf.area(geojson).toFixed(2);
sidebarKoordinat = JSON.stringify(geojson.geometry);
var el = document.getElementById('coords-parsil-val');
el.textContent = luasMeter + ' m²';
el.classList.add('has-coords');
document.getElementById('sb_luas_parsil').value = luasMeter + ' m²';
deactivatePickMode();
}
});
// ==========================================
// 7. SIMPAN DATA DARI SIDEBAR
// ==========================================
function saveJalanSidebar() {
if (!sidebarKoordinat) {
alert('Silakan gambar ruas jalan di peta terlebih dahulu!');
return;
}
var nama = document.getElementById('sb_nama_jalan').value.trim();
if (!nama) {
alert('Nama jalan wajib diisi!');
return;
}
var formData = new FormData();
formData.append('nama_jalan', nama);
formData.append('status', document.getElementById('sb_status_jalan').value);
formData.append('panjang', document.getElementById('sb_panjang_jalan').value);
formData.append('koordinat', sidebarKoordinat);
fetch('save_jalan.php', { method: 'POST', body: formData })
.then(r => r.text())
.then(res => {
alert(res);
document.getElementById('sb_nama_jalan').value = '';
document.getElementById('sb_panjang_jalan').value = '';
document.getElementById('coords-jalan-val').textContent = 'Belum digambar';
document.getElementById('coords-jalan-val').classList.remove('has-coords');
sidebarKoordinat = '';
drawnItems.clearLayers();
loadJalan();
})
.catch(e => console.error("Error saving jalan:", e));
}
function saveParsilSidebar() {
if (!sidebarKoordinat) {
alert('Silakan gambar area parsil di peta terlebih dahulu!');
return;
}
var nama = document.getElementById('sb_nama_parsil').value.trim();
if (!nama) {
alert('Nama pemilik wajib diisi!');
return;
}
var formData = new FormData();
formData.append('nama_pemilik', nama);
formData.append('status', document.getElementById('sb_status_parsil').value);
formData.append('luas', document.getElementById('sb_luas_parsil').value);
formData.append('koordinat', sidebarKoordinat);
fetch('save_parsil.php', { method: 'POST', body: formData })
.then(r => r.text())
.then(res => {
alert(res);
document.getElementById('sb_nama_parsil').value = '';
document.getElementById('sb_luas_parsil').value = '';
document.getElementById('coords-parsil-val').textContent = 'Belum digambar';
document.getElementById('coords-parsil-val').classList.remove('has-coords');
sidebarKoordinat = '';
drawnItems.clearLayers();
loadParsil();
})
.catch(e => console.error("Error saving parsil:", e));
}
// ==========================================
// 8. DROPDOWN KATEGORI DI HEADER
// ==========================================
document.addEventListener('DOMContentLoaded', function () {
var wrap = document.getElementById('custom-select-wrap');
var trigger = document.getElementById('custom-select-trigger');
var options = document.getElementById('custom-select-options');
var hiddenInput = document.getElementById('search-category');
var optionItems = document.querySelectorAll('.custom-option');
// Toggle dropdown
wrap.addEventListener('click', function (e) {
e.stopPropagation();
options.classList.toggle('show');
});
// Pilihan dropdown diklik
optionItems.forEach(function (item) {
item.addEventListener('click', function (e) {
e.stopPropagation();
optionItems.forEach(opt => opt.classList.remove('selected'));
this.classList.add('selected');
trigger.textContent = this.textContent;
hiddenInput.value = this.getAttribute('data-value');
options.classList.remove('show');
jalankanPencarian();
});
});
// Tutup dropdown jika klik di luar
document.addEventListener('click', function () {
options.classList.remove('show');
});
});