Files
2026-06-10 19:05:05 +07:00

390 lines
17 KiB
JavaScript

let tempLat, tempLng;
const map = L.map('map').setView([-0.0263, 109.3425], 13);
const baseOsm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
const greenIcon = new L.Icon({
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41]
});
const redIcon = new L.Icon({
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41]
});
const layerSPBU = L.layerGroup().addTo(map);
const drawnItems = new L.FeatureGroup().addTo(map);
let searchableLayers = [];
// SETUP LEAFLET DRAW TOOLBAR
const drawControl = new L.Control.Draw({
draw: {
polyline: { shapeOptions: { color: 'blue' } },
polygon: { shapeOptions: { color: 'orange' } },
marker: { icon: redIcon },
circle: false, rectangle: false, circlemarker: false
},
edit: { featureGroup: drawnItems }
}).addTo(map);
document.getElementById('drawControlWrapper').appendChild(drawControl.getContainer());
// FUNGSI RENDER DATA SPBU (POINT)
function createSPBUMarker(data) {
const statusBuka = data.buka.trim();
const markerIcon = (statusBuka === 'Ya') ? greenIcon : redIcon;
const marker = L.marker([data.lat, data.lng], { icon: markerIcon, draggable: true });
function updatePopupContent(id, nama, wa, buka, lat, lng, leafletId) {
return `<div class="popup-content">
<b>${nama}</b><br>WA: ${wa}<br>Buka 24: ${buka}<br>
Lat: ${lat.toFixed(6)}<br>Lng: ${lng.toFixed(6)}<br>
<div class="button-group">
<button class="btn-edit" onclick="editData(${id}, '${nama}', '${wa}', '${buka}')">Edit</button>
<button class="btn-delete" onclick="deleteMarker(${id}, ${leafletId})">Hapus</button>
</div>
</div>`;
}
marker.bindPopup(updatePopupContent(data.id, data.nama, data.wa, data.buka, data.lat, data.lng, marker._leaflet_id));
marker.on('dragend', async function (e) {
const newLatLng = marker.getLatLng();
const fd = new FormData();
fd.append('id', data.id); fd.append('lat', newLatLng.lat); fd.append('lng', newLatLng.lng);
await fetch('../spbu/update_koordinat.php', { method: 'POST', body: fd });
marker.setPopupContent(updatePopupContent(data.id, data.nama, data.wa, data.buka, newLatLng.lat, newLatLng.lng, marker._leaflet_id));
});
marker.addTo(layerSPBU);
searchableLayers.push({ nama: data.nama, layer: marker, tipe: 'titik' });
}
if (typeof DATA_SPBU !== 'undefined') {
DATA_SPBU.forEach(spbu => createSPBUMarker(spbu));
}
// EVENT HANDLING LEAFLET DRAW (CREATED & EDITED)
map.on(L.Draw.Event.CREATED, async function (e) {
const layer = e.layer;
const type = e.layerType;
if (type === 'polyline') {
const coords = layer.getLatLngs();
let panjang = 0;
for (let i = 0; i < coords.length - 1; i++) { panjang += coords[i].distanceTo(coords[i + 1]); }
const nama = prompt("Nama Jalan:");
const status = prompt("Status (Nasional/Provinsi/Kabupaten):");
if (nama && status) {
const fd = new FormData();
fd.append('nama', nama); fd.append('status', status); fd.append('panjang', panjang.toFixed(2)); fd.append('koordinat', JSON.stringify(coords));
await fetch('../jalan/tambah_jalan.php', { method: 'POST', body: fd }); location.reload();
}
} else if (type === 'polygon') {
const coords = layer.getLatLngs()[0];
const luas = L.GeometryUtil.geodesicArea(coords);
const pemilik = prompt("Nama Pemilik:");
const status = prompt("Status (SHM/HGB/HGU/HP):");
if (pemilik && status) {
const fd = new FormData();
fd.append('pemilik', pemilik); fd.append('status', status); fd.append('luas', luas.toFixed(2)); fd.append('koordinat', JSON.stringify(coords));
await fetch('../parsil/tambah_parsil.php', { method: 'POST', body: fd }); location.reload();
}
} else if (type === 'marker') {
const latlng = layer.getLatLng();
tempLat = latlng.lat; tempLng = latlng.lng;
const formContent = `<div class="popup-content">
<h4>Tambah SPBU Baru</h4>
<input type="text" id="input_nama" placeholder="Nama SPBU">
<input type="text" id="input_wa" placeholder="No WA">
<select id="input_buka">
<option value="Ya">Buka 24 Jam</option>
<option value="Tidak">Tidak 24 Jam</option>
</select>
<button class="btn-save" onclick="simpanData()">Simpan</button>
</div>`;
L.popup().setLatLng(latlng).setContent(formContent).openOn(map);
map.removeLayer(layer);
}
});
map.on(L.Draw.Event.EDITED, async function (e) {
const layers = e.layers;
layers.eachLayer(async function (layer) {
const id = layer.db_id;
const type = layer.db_type;
if (id && type) {
let coords_str = '';
let nilai_baru = 0;
if (type === 'jalan') {
const coords = layer.getLatLngs();
let panjang = 0;
for (let i = 0; i < coords.length - 1; i++) { panjang += coords[i].distanceTo(coords[i + 1]); }
nilai_baru = panjang.toFixed(2);
coords_str = JSON.stringify(coords);
} else if (type === 'parsil') {
const coords = layer.getLatLngs()[0];
nilai_baru = L.GeometryUtil.geodesicArea(coords).toFixed(2);
coords_str = JSON.stringify(coords);
}
const fd = new FormData();
fd.append('id', id); fd.append('type', type); fd.append('nilai', nilai_baru); fd.append('koordinat', coords_str);
await fetch('../shared/update_geometri.php', { method: 'POST', body: fd });
}
});
alert("Perubahan garis/bidang berhasil disimpan!");
location.reload();
});
// CRUD SPBU
window.simpanData = async function() {
const formData = new FormData();
formData.append('nama_spbu', document.getElementById('input_nama').value);
formData.append('no_wa', document.getElementById('input_wa').value);
formData.append('buka_24', document.getElementById('input_buka').value);
formData.append('latitude', tempLat); formData.append('longitude', tempLng);
const response = await fetch('../spbu/tambah_spbu.php', { method: 'POST', body: formData });
if ((await response.json()).status === 'success') location.reload();
}
window.updateData = async function(id) {
const formData = new FormData();
formData.append('id', id);
formData.append('nama', document.getElementById('edit_nama').value);
formData.append('wa', document.getElementById('edit_wa').value);
formData.append('buka', document.getElementById('edit_buka').value);
const response = await fetch('../spbu/update_spbu.php', { method: 'POST', body: formData });
if ((await response.json()).status === 'success') location.reload();
}
window.deleteMarker = async function(id, leafletId) {
if (confirm("Hapus data?")) {
const response = await fetch(`../spbu/hapus_spbu.php?id=${id}`);
if ((await response.json()).status === 'success') location.reload();
}
}
window.editData = function(id, nama, wa, buka) {
const editForm = `<div class="popup-content">
<h4>Edit SPBU</h4>
<input type="text" id="edit_nama" value="${nama}">
<input type="text" id="edit_wa" value="${wa}">
<select id="edit_buka">
<option value="Ya" ${buka === 'Ya' ? 'selected' : ''}>Buka 24 Jam</option>
<option value="Tidak" ${buka === 'Tidak' ? 'selected' : ''}>Tidak 24 Jam</option>
</select>
<button class="btn-save" onclick="updateData(${id})">Update Data</button>
</div>`;
map.closePopup();
L.popup().setLatLng(map.getCenter()).setContent(editForm).openOn(map);
}
// CRUD JALAN (POLYLINE)
window.editDataJalan = function(id, nama, status) {
const editForm = `<div class="popup-content">
<h4>Edit Jalan</h4>
<input type="text" id="edit_nama_jalan" value="${nama}">
<select id="edit_status_jalan">
<option value="Nasional" ${status === 'Nasional' ? 'selected' : ''}>Nasional</option>
<option value="Provinsi" ${status === 'Provinsi' ? 'selected' : ''}>Provinsi</option>
<option value="Kabupaten" ${status === 'Kabupaten' ? 'selected' : ''}>Kabupaten</option>
</select>
<button class="btn-save" onclick="updateDataJalan(${id})">Update Jalan</button>
</div>`;
map.closePopup();
L.popup().setLatLng(map.getCenter()).setContent(editForm).openOn(map);
}
window.updateDataJalan = async function(id) {
const fd = new FormData();
fd.append('id', id);
fd.append('nama', document.getElementById('edit_nama_jalan').value);
fd.append('status', document.getElementById('edit_status_jalan').value);
const response = await fetch('../jalan/update_jalan.php', { method: 'POST', body: fd });
if ((await response.json()).status === 'success') location.reload();
}
window.deleteJalan = async function(id) {
if (confirm("Hapus data jalan ini?")) {
const response = await fetch(`../jalan/hapus_jalan.php?id=${id}`);
if ((await response.json()).status === 'success') location.reload();
}
}
// CRUD PARSIL (POLYGON)
window.editDataParsil = function(id, pemilik, status) {
const editForm = `<div class="popup-content">
<h4>Edit Parsil</h4>
<input type="text" id="edit_pemilik_parsil" value="${pemilik}">
<select id="edit_status_parsil">
<option value="SHM" ${status === 'SHM' ? 'selected' : ''}>SHM</option>
<option value="HGB" ${status === 'HGB' ? 'selected' : ''}>HGB</option>
<option value="HGU" ${status === 'HGU' ? 'selected' : ''}>HGU</option>
<option value="HP" ${status === 'HP' ? 'selected' : ''}>HP</option>
</select>
<button class="btn-save" onclick="updateDataParsil(${id})">Update Parsil</button>
</div>`;
map.closePopup();
L.popup().setLatLng(map.getCenter()).setContent(editForm).openOn(map);
}
window.updateDataParsil = async function(id) {
const fd = new FormData();
fd.append('id', id);
fd.append('pemilik', document.getElementById('edit_pemilik_parsil').value);
fd.append('status', document.getElementById('edit_status_parsil').value);
const response = await fetch('../parsil/update_parsil.php', { method: 'POST', body: fd });
if ((await response.json()).status === 'success') location.reload();
}
window.deleteParsil = async function(id) {
if (confirm("Hapus data parsil ini?")) {
const response = await fetch(`../parsil/hapus_parsil.php?id=${id}`);
if ((await response.json()).status === 'success') location.reload();
}
}
// RENDER SPASIAL (LOAD DARI DB)
function getColorJalan(status) { return status === 'Nasional' ? 'red' : status === 'Provinsi' ? 'blue' : 'green'; }
function getColorParsil(status) {
const colors = { 'SHM': '#8B4513', 'HGB': '#FFD700', 'HGU': '#ADFF2F', 'HP': '#87CEEB' };
return colors[status] || '#CCCCCC';
}
async function loadSpasial() {
const resJ = await fetch('../jalan/get_jalan.php');
const dataJ = await resJ.json();
L.geoJSON(dataJ, {
style: function (f) { return { color: getColorJalan(f.properties.status), weight: 4 }; },
onEachFeature: function (f, layer) {
layer.bindPopup(`
<div class="popup-content">
<b>${f.properties.nama}</b><br>
Status: ${f.properties.status}<br>
Panjang: ${f.properties.panjang} m<br>
<div class="button-group">
<button class="btn-edit" onclick="editDataJalan(${f.properties.id}, '${f.properties.nama}', '${f.properties.status}')">Edit</button>
<button class="btn-delete" onclick="deleteJalan(${f.properties.id})">Hapus</button>
</div>
</div>
`);
layer.db_id = f.properties.id;
layer.db_type = 'jalan';
drawnItems.addLayer(layer);
searchableLayers.push({ nama: f.properties.nama, layer: layer, tipe: 'garis' });
}
});
const resP = await fetch('../parsil/get_parsil.php');
const dataP = await resP.json();
L.geoJSON(dataP, {
style: function (f) {
return { fillColor: getColorParsil(f.properties.status), fillOpacity: 0.6, color: "black", weight: 1 };
},
onEachFeature: function (f, layer) {
layer.bindPopup(`
<div class="popup-content">
<b>Pemilik: ${f.properties.pemilik}</b><br>
Status: ${f.properties.status}<br>
Luas: ${f.properties.luas} m²<br>
<div class="button-group">
<button class="btn-edit" onclick="editDataParsil(${f.properties.id}, '${f.properties.pemilik}', '${f.properties.status}')">Edit</button>
<button class="btn-delete" onclick="deleteParsil(${f.properties.id})">Hapus</button>
</div>
</div>
`);
layer.db_id = f.properties.id;
layer.db_type = 'parsil';
drawnItems.addLayer(layer);
searchableLayers.push({ nama: f.properties.pemilik, layer: layer, tipe: 'bidang' });
}
});
}
// LOAD BASEMAP KECAMATAN
async function loadBasemapPontianak() {
const adminGrp = L.layerGroup().addTo(map);
async function fetchGeoJSON(url) {
const response = await fetch(url);
if (!response.ok) throw new Error(`File tidak ditemukan: ${url}`);
return await response.json();
}
function getColorKecamatan(nama) {
if (!nama) return "#cccccc";
const n = nama.toUpperCase();
if (n.includes("BARAT")) return "#e41a1c";
if (n.includes("KOTA")) return "#377eb8";
if (n.includes("SELATAN")) return "#4daf4a";
if (n.includes("TENGGARA")) return "#984ea3";
if (n.includes("TIMUR")) return "#ff7f00";
if (n.includes("UTARA")) return "#f1c40f";
return "#a65628";
}
try {
const dataAdmin = await fetchGeoJSON('../assets/data/Admin_Kecamatan.json');
L.geoJSON(dataAdmin, {
style: function (feature) {
const namaKecamatan = feature.properties.Ket || "";
return { color: "black", weight: 2, fillOpacity: 0.2, fillColor:getColorKecamatan(namaKecamatan) };
},
onEachFeature: function (feature, layer) {
const namaKecamatan = feature.properties.Ket || "Tidak Diketahui";
const jumlahPenduduk = feature.properties.penduduk || 0;
const formatPenduduk = new Intl.NumberFormat('id-ID').format(jumlahPenduduk);
layer.bindPopup(`
<div style="text-align: center;">
<b>Kecamatan ${namaKecamatan}</b><br>
<hr style="margin: 5px 0;">
Jumlah Penduduk: <b>${formatPenduduk} Jiwa</b>
</div>
`);
}
}).addTo(adminGrp);
} catch (e) {
console.error("Gagal memuat peta kecamatan:", e.message);
}
const baseLayers = { "OpenStreetMap": baseOsm };
const overlays = {
"Batas Kecamatan": adminGrp,
"SPBU (Titik)": layerSPBU,
"Jalan & Tanah (Mode Edit)": drawnItems
};
L.control.layers(baseLayers, overlays).addTo(map);
}
// FUNGSI PENCARIAN DATA
window.cariData = function() {
const keyword = document.getElementById('searchInput').value.toLowerCase().trim();
if (!keyword) { alert("Masukkan kata kunci pencarian!"); return; }
let found = false;
for (let item of searchableLayers) {
if (item.nama && item.nama.toLowerCase().includes(keyword)) {
found = true;
if (item.tipe === 'titik') { map.flyTo(item.layer.getLatLng(), 17); }
else { map.flyToBounds(item.layer.getBounds(), { maxZoom: 17 }); }
item.layer.openPopup();
break;
}
}
if (!found) alert("Data tidak ditemukan! Pastikan nama SPBU, Jalan, atau Pemilik sudah benar.");
}
window.handleEnter = function(e) {
if (e.key === 'Enter') cariData();
}
loadSpasial();
loadBasemapPontianak();