Files
2026-06-13 13:38:52 +07:00

982 lines
35 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WebGIS FINAL FIX</title>
<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>
html, body { height:100%; margin:0; }
#map { height:100%; }
input, select { width:120px; margin-bottom:5px; }
button { background:green; color:white; border:none; padding:4px; cursor:pointer; margin-right:5px;}
#control-panel {
position: absolute;
bottom: 10px;
left: 10px;
top: auto;
z-index: 1001;
background: white;
padding: 12px;
border-radius: 8px;
box-shadow: 0 0 14px rgba(0,0,0,0.2);
font-family: Arial, sans-serif;
width: 280px;
}
#control-panel label {
display: block;
margin-top: 8px;
font-weight: 600;
font-size: 13px;
}
#control-panel input,
#control-panel select,
#control-panel button {
width: 100%;
box-sizing: border-box;
margin-top: 4px;
margin-bottom: 10px;
padding: 8px;
font-size: 13px;
}
#control-panel button {
background: #2e8b57;
color: white;
border: none;
}
.radius-handle {
background: white;
border: 2px solid #007bff;
border-radius: 50%;
width: 14px;
height: 14px;
box-shadow: 0 0 4px rgba(0,0,0,0.3);
}
</style>
</head>
<body>
<div id="control-panel">
<b>Kontrol Pemetaan</b>
<label for="marker-type">Jenis titik baru</label>
<select id="marker-type">
<option value="worship">Rumah Ibadah</option>
<option value="poor_house">Rumah Penduduk Miskin</option>
<option value="point">Data Umum</option>
</select>
<label for="default-radius">Radius Rumah Ibadah (m)</label>
<input id="default-radius" type="number" min="1" max="500" value="200">
<button id="refresh-data">Muat Ulang Data</button>
<small>Gunakan tombol gambar marker lalu klik titik untuk menyimpan data.</small>
</div>
<div id="map"></div>
<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 src="https://cdn.jsdelivr.net/npm/@turf/turf@6/turf.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function(){
// =====================
// INIT MAP
// =====================
var map = L.map('map').setView([-0.0554, 109.3494], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
// =====================
// ICONS
// =====================
var iconWorship = L.icon({
iconUrl: 'https://maps.google.com/mapfiles/ms/icons/blue-dot.png',
iconSize: [32, 32]
});
var iconPoorGreen = L.icon({
iconUrl: 'https://maps.google.com/mapfiles/ms/icons/green-dot.png',
iconSize: [32, 32]
});
var iconPoorRed = L.icon({
iconUrl: 'https://maps.google.com/mapfiles/ms/icons/red-dot.png',
iconSize: [32, 32]
});
var iconGeneric = L.icon({
iconUrl: 'https://maps.google.com/mapfiles/ms/icons/purple-dot.png',
iconSize: [32, 32]
});
var iconPointOpen = L.icon({
iconUrl: 'https://maps.google.com/mapfiles/ms/icons/orange-dot.png',
iconSize: [32, 32]
});
var iconPointClosed = L.icon({
iconUrl: 'https://maps.google.com/mapfiles/ms/icons/yellow-dot.png',
iconSize: [32, 32]
});
function getPointIcon(type, buka) {
if (type === 'worship') return iconWorship;
if (type === 'poor_house') return buka === 'green' ? iconPoorGreen : iconPoorRed;
if (type === 'point') return buka === 'ya' ? iconPointOpen : iconPointClosed;
return iconGeneric;
}
// =====================
// LAYERS
// =====================
var drawnItems = new L.FeatureGroup().addTo(map);
var worshipLayer = new L.LayerGroup().addTo(map);
var poorLayer = new L.LayerGroup().addTo(map);
var pointLayer = new L.LayerGroup().addTo(map);
var circleLayer = new L.LayerGroup().addTo(map);
var kecamatanLayer = new L.LayerGroup().addTo(map);
var pointMarkers = {};
var worshipCircles = {};
var worshipHandles = {};
var resizeHandleIcon = L.divIcon({
className: 'radius-handle',
html: '',
iconSize: [14, 14],
iconAnchor: [7, 7]
});
function getHandleLatLng(circle) {
var center = circle.getLatLng();
var dest = turf.destination(turf.point([center.lng, center.lat]), circle.getRadius() / 1000, 90, { units: 'kilometers' });
return L.latLng(dest.geometry.coordinates[1], dest.geometry.coordinates[0]);
}
function updateRadiusPopupDisplay(id, radius) {
var input = document.getElementById('radius_input_' + id);
var value = document.getElementById('radius_value_' + id);
if (input) input.value = Math.round(radius);
if (value) value.innerText = Math.round(radius);
}
function createWorshipResizeHandle(circle, id) {
var handle = L.marker(getHandleLatLng(circle), {
icon: resizeHandleIcon,
draggable: true,
zIndexOffset: 1000
});
handle.on('drag', function(e) {
var newRadius = Math.max(1, map.distance(circle.getLatLng(), e.target.getLatLng()));
circle.setRadius(newRadius);
updateRadiusPopupDisplay(id, newRadius);
});
handle.on('dragend', function(e) {
var handleLatLng = e.target.getLatLng();
var newRadius = Math.max(1, map.distance(circle.getLatLng(), handleLatLng));
var bearing = turf.bearing(turf.point([circle.getLatLng().lng, circle.getLatLng().lat]), turf.point([handleLatLng.lng, handleLatLng.lat]));
var dest = turf.destination(turf.point([circle.getLatLng().lng, circle.getLatLng().lat]), newRadius / 1000, bearing, { units: 'kilometers' });
handle.setLatLng(L.latLng(dest.geometry.coordinates[1], dest.geometry.coordinates[0]));
saveWorshipRadius(id, newRadius);
});
circleLayer.addLayer(handle);
return handle;
}
function setWorshipCircleRadius(id, radius, save) {
var circle = worshipCircles[id];
if (!circle) return;
radius = Math.max(1, Math.min(500, radius));
circle.setRadius(radius);
updateRadiusPopupDisplay(id, radius);
if (worshipHandles[id]) {
worshipHandles[id].setLatLng(getHandleLatLng(circle));
}
if (save) {
var fd = new FormData();
fd.append('id', id);
fd.append('tipe', 'worship');
fd.append('radius_meter', radius);
fetch('update_point.php', { method: 'POST', body: fd })
.then(function() { loadData(); })
.catch(function(err) { console.error('Update radius error:', err); });
}
}
// =====================
// LOAD GEOJSON KECAMATAN
// =====================
fetch('Admin_Kecamatan.json')
.then(res => res.json())
.then(data => {
L.geoJSON(data, {
style: {
color: "purple",
weight: 2,
fillOpacity: 0.3
},
onEachFeature: function(feature, layer){
var luas = turf.area(feature);
var nama = feature.properties.Ket ||
feature.properties.nama ||
feature.properties.NAMOBJ ||
"Tidak ada";
layer.bindPopup(`
<b>Kecamatan</b><br>
Nama: ${nama}<br>
Luas: ${(luas/1000000).toFixed(2)} km²<br>
Populasi: ${feature.properties.Populasi}
`);
layer.bindTooltip(nama);
}
}).addTo(kecamatanLayer);
});
// =====================
// DRAW CONTROL
// =====================
var drawControl = new L.Control.Draw({
edit: { featureGroup: drawnItems },
draw: {
marker:true,
polygon:true,
polyline:true,
circle:true
}
});
map.addControl(drawControl);
// =====================
// MODE DRAW
// =====================
var isDrawing=false;
map.on('draw:drawstart', function(){
isDrawing = true;
});
map.on('draw:drawstop', function(){
isDrawing = false;
});
// =====================
// SIMPAN GAMBAR
// =====================
map.on(L.Draw.Event.CREATED, function (e) {
var layer = e.layer;
var type = e.layerType;
drawnItems.addLayer(layer);
if (type === 'marker') {
showMarkerForm(layer);
return;
}
if (type === 'polyline') {
var latlngs = layer.getLatLngs();
var panjang = 0;
for (var i = 0; i < latlngs.length - 1; i++) {
panjang += map.distance(latlngs[i], latlngs[i+1]);
}
var status = prompt("Status Jalan (Nasional/Provinsi/Kabupaten):");
var color = 'gray';
var weight = 4;
if (status === 'Nasional') { color = '#c0392b'; weight = 6; }
else if (status === 'Provinsi') { color = '#2980b9'; weight = 5; }
else if (status === 'Kabupaten') { color = '#27ae60'; weight = 4; }
layer.setStyle({ color: color, weight: weight });
fetch('save.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tipe: "jalan",
status: status,
nilai: panjang,
geom: layer.toGeoJSON()
})
})
.then(response => response.text())
.then(data => {
loadGambar();
})
.catch(error => {
console.error('Save error:', error);
});
}
if (type === 'circle') {
// Save circle as a worship radius (ask minimal info)
var center = layer.getLatLng();
var radius = Math.round(layer.getRadius());
var nama = prompt('Nama Rumah Ibadah:');
if (!nama) { drawnItems.removeLayer(layer); return; }
var jenis = prompt('Jenis (Masjid/Gereja/etc):', 'Masjid') || 'Masjid';
var alamat = prompt('Alamat (opsional):', '');
var kontak = prompt('Kontak / WA (opsional):', '');
var kegiatan = prompt('Kegiatan (opsional):', 'Pengajian');
var kapasitas = prompt('Kapasitas (angka):', '100');
var fd = new FormData();
fd.append('tipe', 'worship');
fd.append('lat', center.lat);
fd.append('lng', center.lng);
fd.append('nama', nama);
fd.append('jenis', jenis);
fd.append('alamat', alamat);
fd.append('kontak', kontak);
fd.append('no_wa', kontak);
fd.append('kegiatan', kegiatan);
fd.append('kapasitas', kapasitas || 0);
fd.append('radius_meter', radius || getDefaultRadius());
fetch('save_point.php', { method: 'POST', body: fd })
.then(function(res){ return res.json(); })
.then(function(data){
if (!data.success) { alert('Gagal menyimpan: ' + (data.error || 'Unknown')); drawnItems.removeLayer(layer); return; }
loadData();
})
.catch(function(err){ console.error('Save circle error:', err); drawnItems.removeLayer(layer); });
}
if (type === 'polygon') {
var geojson = layer.toGeoJSON();
var luas = turf.area(geojson);
var status = prompt("Status Tanah (SHM/HGB/HGU/HP):");
var color = 'gray';
var fillOpacity = 0.3;
if (status === 'SHM') { color = 'green'; fillOpacity = 0.5; }
else if (status === 'HGB') { color = 'yellow'; fillOpacity = 0.5; }
else if (status === 'HGU') { color = 'orange'; fillOpacity = 0.5; }
else if (status === 'HP') { color = 'red'; fillOpacity = 0.5; }
layer.setStyle({ color: color, weight: 2, fillOpacity: fillOpacity });
fetch('save.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tipe: "parsil",
status: status,
nilai: luas,
geom: geojson
})
})
.then(response => response.text())
.then(data => {
loadGambar();
})
.catch(error => {
console.error('Save error:', error);
});
}
});
// =====================
// EDIT
// =====================
map.on(L.Draw.Event.EDITED, function (e) {
e.layers.eachLayer(function(layer){
var geojson = layer.toGeoJSON();
fetch('update.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: layer._id,
geojson: geojson
})
});
});
});
// =====================
// DELETE
// =====================
map.on(L.Draw.Event.DELETED, function (e) {
e.layers.eachLayer(function(layer){
fetch('delete.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: layer._id,
tipe: layer._tipe
})
});
});
});
// =====================
// GLOBAL FUNCTIONS FOR MARKER FORM
// =====================
function getDefaultRadius() {
var el = document.getElementById('default-radius');
return el ? Math.min(500, Math.max(1, parseInt(el.value) || 200)) : 200;
}
function calculatePoorColor(data) {
if (!data || !data.latitude || !data.longitude) return 'red';
var latlng = L.latLng(data.latitude, data.longitude);
for (var id in worshipCircles) {
var circle = worshipCircles[id];
if (circle && map.distance(latlng, circle.getLatLng()) <= circle.getRadius()) {
return 'green';
}
}
return 'red';
}
function updatePointPosition(id, latlng, tipe) {
var fd = new FormData();
fd.append('id', id);
fd.append('tipe', tipe);
fd.append('lat', latlng.lat);
fd.append('lng', latlng.lng);
fetch('update_point.php', { method: 'POST', body: fd })
.then(function() { loadData(); })
.catch(function(err) { console.error('Update position error:', err); });
}
window.deletePoint = function(id) {
if (!confirm('Hapus data ini?')) return;
var marker = pointMarkers[id];
var tipe = marker && marker._tipe ? marker._tipe : '';
var fd = new FormData();
fd.append('id', id);
fd.append('tipe', tipe);
fetch('delete_point.php', { method: 'POST', body: fd })
.then(function(){ loadData(); })
.catch(function(err){ console.error('Delete error:', err); });
};
function showMarkerForm(layer) {
var layerId = layer._leaflet_id;
var selectedType = document.getElementById('marker-type').value;
var defaultRadius = getDefaultRadius();
var formHtml = '';
if (selectedType === 'worship') {
formHtml = '<div style="width:300px;">' +
'<b>Tambah Rumah Ibadah</b><br><br>' +
'Nama:<br><input id="nama_' + layerId + '" type="text"><br>' +
'Jenis:<br><input id="jenis_' + layerId + '" type="text" value="Masjid"><br>' +
'Alamat:<br><input id="alamat_' + layerId + '" type="text"><br>' +
'Kontak / WA:<br><input id="kontak_' + layerId + '" type="text"><br>' +
'Kegiatan:<br><input id="kegiatan_' + layerId + '" type="text" value="Pengajian"><br>' +
'Kapasitas:<br><input id="kapasitas_' + layerId + '" type="number" min="1" value="100"><br>' +
'Radius (m):<br><input id="radius_' + layerId + '" type="number" min="1" max="500" value="' + defaultRadius + '"><br>' +
'<button onclick="savePoint(' + layerId + ')">Simpan</button> ' +
'<button onclick="cancelPoint(' + layerId + ')" style="background:#c0392b;">Batal</button>' +
'</div>';
} else if (selectedType === 'poor_house') {
formHtml = '<div style="width:300px;">' +
'<b>Tambah Rumah Penduduk Miskin</b><br><br>' +
'Nama Kepala Keluarga:<br><input id="nama_' + layerId + '" type="text"><br>' +
'Alamat:<br><input id="alamat_' + layerId + '" type="text"><br>' +
'Jumlah Anggota:<br><input id="jumlah_' + layerId + '" type="number" min="1" value="1"><br>' +
'Kondisi Rumah:<br><input id="kondisi_' + layerId + '" type="text" value="Tidak layak"><br>' +
'Sumber Penghasilan:<br><input id="sumber_' + layerId + '" type="text"><br>' +
'Kebutuhan Prioritas:<br><input id="kebutuhan_' + layerId + '" type="text"><br>' +
'Status Bantuan:<br><input id="status_' + layerId + '" type="text" value="Belum Terlayani"><br>' +
'<button onclick="savePoint(' + layerId + ')">Simpan</button> ' +
'<button onclick="cancelPoint(' + layerId + ')" style="background:#c0392b;">Batal</button>' +
'</div>';
} else {
formHtml = '<div style="width:300px;">' +
'<b>Tambah Data Umum</b><br><br>' +
'Nama:<br><input id="nama_' + layerId + '" type="text"><br>' +
'WA:<br><input id="wa_' + layerId + '" type="text"><br>' +
'Waktu Operasi:<br><select id="buka_' + layerId + '"><option value="ya">Buka 24 Jam</option><option value="tidak">Tutup Malam</option></select><br>' +
'<button onclick="savePoint(' + layerId + ')">Simpan</button> ' +
'<button onclick="cancelPoint(' + layerId + ')" style="background:#c0392b;">Batal</button>' +
'</div>';
}
layer.bindPopup(formHtml, { maxWidth: 340, autoPan: true, closeButton: true });
layer.openPopup();
}
window.savePoint = function(layerId) {
var layer = drawnItems._layers[layerId];
if (!layer) { alert('Layer tidak ditemukan'); return; }
var selectedType = document.getElementById('marker-type').value;
var fd = new FormData();
fd.append('tipe', selectedType);
fd.append('lat', layer.getLatLng().lat);
fd.append('lng', layer.getLatLng().lng);
if (selectedType === 'worship') {
var nama = document.getElementById('nama_' + layerId).value.trim();
var jenis = document.getElementById('jenis_' + layerId).value.trim();
var alamat = document.getElementById('alamat_' + layerId).value.trim();
var kontak = document.getElementById('kontak_' + layerId).value.trim();
var kegiatan = document.getElementById('kegiatan_' + layerId).value.trim();
var kapasitas = document.getElementById('kapasitas_' + layerId).value.trim();
var radius = document.getElementById('radius_' + layerId).value.trim();
if (!nama) { alert('Nama rumah ibadah harus diisi!'); return; }
fd.append('nama', nama);
fd.append('jenis', jenis);
fd.append('alamat', alamat);
fd.append('kontak', kontak);
fd.append('no_wa', kontak);
fd.append('kegiatan', kegiatan);
fd.append('kapasitas', kapasitas || 0);
fd.append('radius_meter', radius || getDefaultRadius());
} else if (selectedType === 'poor_house') {
var nama = document.getElementById('nama_' + layerId).value.trim();
var alamat = document.getElementById('alamat_' + layerId).value.trim();
var jumlah = document.getElementById('jumlah_' + layerId).value.trim();
var kondisi = document.getElementById('kondisi_' + layerId).value.trim();
var sumber = document.getElementById('sumber_' + layerId).value.trim();
var kebutuhan = document.getElementById('kebutuhan_' + layerId).value.trim();
var status = document.getElementById('status_' + layerId).value.trim();
if (!nama) { alert('Nama kepala keluarga harus diisi!'); return; }
fd.append('nama_keluarga', nama);
fd.append('alamat', alamat);
fd.append('jumlah_anggota', jumlah || 0);
fd.append('kondisi_rumah', kondisi);
fd.append('sumber_penghasilan', sumber);
fd.append('kebutuhan_prioritas', kebutuhan);
fd.append('status_bantuan', status);
} else {
var nama = document.getElementById('nama_' + layerId).value.trim();
var wa = document.getElementById('wa_' + layerId).value.trim();
var buka = document.getElementById('buka_' + layerId).value;
if (!nama || !wa) { alert('Nama dan WA harus diisi!'); return; }
fd.append('nama', nama);
fd.append('no_wa', wa);
fd.append('buka', buka);
}
fetch('save_point.php', { method: 'POST', body: fd })
.then(function(res) {
return res.text().then(function(text) {
if (!res.ok) {
throw new Error('HTTP ' + res.status + ': ' + text);
}
try {
return JSON.parse(text);
} catch (e) {
throw new Error('Invalid JSON response: ' + text);
}
});
})
.then(function(data){
if (!data.success) {
var message = data.error || 'Unknown error';
alert('Gagal menyimpan: ' + message);
console.error('SavePoint failed:', message);
return;
}
drawnItems.removeLayer(layer);
map.closePopup();
loadData();
})
.catch(function(err){
console.error('Error:', err);
alert('Gagal menyimpan data: ' + err.message);
});
};
window.cancelPoint = function(layerId) {
var layer = drawnItems._layers[layerId];
if (layer) {
drawnItems.removeLayer(layer);
map.closePopup();
}
};
function saveWorshipRadius(id, radius) {
if (typeof radius === 'undefined') {
var input = document.getElementById('radius_input_' + id);
if (!input) return;
radius = Math.min(500, Math.max(1, parseInt(input.value) || 100));
} else {
radius = Math.min(500, Math.max(1, Math.round(radius)));
}
var fd = new FormData();
fd.append('id', id);
fd.append('tipe', 'worship');
fd.append('radius_meter', radius);
fetch('update_point.php', { method: 'POST', body: fd })
.then(function() { loadData(); })
.catch(function(err) { console.error('Update radius error:', err); });
}
// =====================
// HELPERS: edit/delete single feature
// =====================
window.deleteFeature = function(id, tipe) {
if (!confirm('Hapus data ini?')) return;
fetch('delete.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: id, tipe: tipe })
})
.then(function(res){ return res.text(); })
.then(function(){
loadGambar();
})
.catch(function(err){ console.error('Delete feature error:', err); });
};
window.enableLayerEdit = function(id) {
// Enable editing for a single layer and add a Save button in the popup
var layer = null;
drawnItems.eachLayer(function(l){ if (l._id == id) layer = l; });
if (!layer) { alert('Layer tidak ditemukan'); return; }
if (layer.editing && layer.editing.enable) {
layer.editing.enable();
} else if (layer instanceof L.Marker) {
layer.dragging.enable();
}
var saveBtn = document.createElement('button');
saveBtn.innerText = 'Simpan Perubahan';
saveBtn.style.marginTop = '8px';
saveBtn.onclick = function(){
// disable editing then send update
if (layer.editing && layer.editing.disable) layer.editing.disable();
if (layer.dragging && layer.dragging.disable) layer.dragging.disable();
var geojson = layer.toGeoJSON ? layer.toGeoJSON() : null;
if (!geojson) { alert('Tidak dapat mengambil GeoJSON'); return; }
fetch('update.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: id, geojson: geojson })
})
.then(function(res){ return res.text(); })
.then(function(){ loadGambar(); })
.catch(function(err){ console.error('Save edited feature error:', err); });
};
layer.bindPopup((layer.getPopup() ? layer.getPopup().getContent() : '') + '<br>').openPopup();
// append button to popup DOM when opened
layer.once('popupopen', function(){
var px = layer.getPopup().getElement();
if (px && px.appendChild) px.appendChild(saveBtn);
});
};
// Refresh button handler
document.addEventListener('DOMContentLoaded', function(){
var btn = document.getElementById('refresh-data');
if (btn) btn.addEventListener('click', function(){ refreshAllData(); });
});
function renderWorshipMarker(d) {
var radius = d.radius_meter ? parseInt(d.radius_meter) : getDefaultRadius();
var marker = L.marker([d.latitude, d.longitude], { draggable: true, icon: iconWorship });
marker._id = d.id;
marker._tipe = 'worship';
marker._pointData = d;
var circle = L.circle([d.latitude, d.longitude], {
radius: radius,
color: 'blue',
fillOpacity: 0.1,
weight: 2
});
circle._worshipId = d.id;
worshipCircles[d.id] = circle;
circleLayer.addLayer(circle);
var handle = createWorshipResizeHandle(circle, d.id);
worshipHandles[d.id] = handle;
marker.bindPopup(
'<b>Rumah Ibadah</b><br>' +
'Nama: ' + (d.nama || '-') + '<br>' +
'Jenis: ' + (d.jenis || '-') + '<br>' +
'Alamat: ' + (d.alamat || '-') + '<br>' +
'Kontak: ' + (d.kontak || d.no_wa || '-') + '<br>' +
'Kegiatan: ' + (d.kegiatan || '-') + '<br>' +
'Kapasitas: ' + (d.kapasitas || '-') + ' orang<br><br>' +
'Radius: <input id="radius_input_' + d.id + '" type="range" min="1" max="500" value="' + radius + '" oninput="setWorshipCircleRadius(' + d.id + ', this.value, false);"> ' +
'<span id="radius_value_' + d.id + '">' + radius + '</span> m<br>' +
'<button onclick="saveWorshipRadius(' + d.id + ')" style="margin-top:8px;">Simpan Radius</button> ' +
'<button onclick="deletePoint(' + d.id + ')" style="margin-top:8px;background:#c0392b;">Hapus</button>'
);
marker.on('dragend', function(){ updatePointPosition(d.id, marker.getLatLng(), 'worship'); });
marker.addTo(worshipLayer);
pointMarkers[d.id] = marker;
}
function renderPoorMarker(d) {
var color = calculatePoorColor(d);
var marker = L.marker([d.latitude, d.longitude], { draggable: true, icon: getPointIcon('poor_house', color) });
marker._id = d.id;
marker._tipe = 'poor_house';
marker._pointData = d;
marker.bindPopup(
'<b>Rumah Penduduk Miskin</b><br>' +
'Nama Kepala Keluarga: ' + (d.nama_kepala_keluarga || '-') + '<br>' +
'Alamat: ' + (d.alamat || '-') + '<br>' +
'Jumlah Anggota: ' + (d.jumlah_anggota || '-') + '<br>' +
'Kondisi Rumah: ' + (d.kondisi_rumah || '-') + '<br>' +
'Sumber Penghasilan: ' + (d.sumber_penghasilan || '-') + '<br>' +
'Kebutuhan Prioritas: ' + (d.kebutuhan_prioritas || '-') + '<br>' +
'Status Bantuan: ' + (d.status_bantuan || '-') + '<br><br>' +
'<b>Status:</b> ' + (color === 'green' ? 'Di dalam radius' : 'Di luar radius') + '<br>' +
'<button onclick="deletePoint(' + d.id + ')" style="margin-top:8px;background:#c0392b;">Hapus</button>'
);
marker.on('dragend', function(){ updatePointPosition(d.id, marker.getLatLng(), 'poor_house'); });
marker.addTo(poorLayer);
pointMarkers[d.id] = marker;
}
function escapeHtml(str) {
return String(str || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;');
}
window.cancelEdit = function(id) {
var marker = pointMarkers[id];
if (marker) {
marker.closePopup();
}
};
window.savePointEdit = function(id) {
var marker = pointMarkers[id];
if (!marker) return;
var nama = document.getElementById('edit_nama_' + id).value.trim();
var wa = document.getElementById('edit_wa_' + id).value.trim();
var buka = document.getElementById('edit_buka_' + id).value;
if (!nama || !wa) {
alert('Nama dan WA harus diisi!');
return;
}
var fd = new FormData();
fd.append('id', id);
fd.append('tipe', 'point');
fd.append('nama', nama);
fd.append('no_wa', wa);
fd.append('buka', buka);
fetch('update_point.php', { method: 'POST', body: fd })
.then(function(res){ return res.json(); })
.then(function(data){
if (!data.success) { alert('Gagal update: ' + (data.error || 'Unknown')); return; }
loadData();
})
.catch(function(err){ console.error('Update edit error:', err); });
};
window.editPoint = function(id) {
var marker = pointMarkers[id];
if (!marker) return;
var d = marker._pointData || {};
var title = d.tipe === 'point' ? 'Edit SPBU/Data Umum' : 'Edit Data Umum';
var formHtml = '<div style="width:300px;">' +
'<b>' + title + '</b><br><br>' +
'Nama:<br><input id="edit_nama_' + id + '" type="text" value="' + escapeHtml(d.nama) + '"><br>' +
'WA:<br><input id="edit_wa_' + id + '" type="text" value="' + escapeHtml(d.no_wa) + '"><br>' +
'Waktu Operasi:<br><select id="edit_buka_' + id + '">' +
'<option value="ya"' + (d.buka_24jam === 'ya' ? ' selected' : '') + '>Buka 24 Jam</option>' +
'<option value="tidak"' + (d.buka_24jam === 'tidak' ? ' selected' : '') + '>Tutup Malam</option>' +
'</select><br>' +
'<button onclick="savePointEdit(' + id + ')">Simpan</button> ' +
'<button onclick="cancelEdit(' + id + ')" style="background:#c0392b;">Batal</button>' +
'</div>';
marker.bindPopup(formHtml, { maxWidth: 340, autoPan: true, closeButton: true }).openPopup();
};
function renderGenericPoint(d) {
var controlType = d.tipe || 'point';
var icon = getPointIcon(controlType, d.buka_24jam);
var label = controlType === 'point' ? 'SPBU / Data Umum' : 'Data Umum';
var bukaText = d.buka_24jam ? (d.buka_24jam === 'ya' ? 'Buka 24 Jam' : 'Tutup Malam') : '-';
var kontakText = d.no_wa || d.kontak || '-';
var marker = L.marker([d.latitude, d.longitude], { draggable: true, icon: icon });
marker._id = d.id;
marker._tipe = controlType;
marker._pointData = d;
marker.bindPopup(
'<b>' + label + '</b><br>' +
'Nama: ' + (d.nama || '-') + '<br>' +
'Kontak: ' + kontakText + '<br>' +
'Waktu: ' + bukaText + '<br>' +
'<button onclick="editPoint(' + d.id + ')" style="margin-top:8px;background:#f39c12;">Edit</button> ' +
'<button onclick="deletePoint(' + d.id + ')" style="margin-top:8px;background:#c0392b;">Hapus</button>'
);
marker.on('dragend', function(){ updatePointPosition(d.id, marker.getLatLng(), marker._tipe); });
marker.addTo(pointLayer);
pointMarkers[d.id] = marker;
}
window.cancelPoint = function(layerId) {
var layer = drawnItems._layers[layerId];
if (layer) {
drawnItems.removeLayer(layer);
map.closePopup();
}
};
// =====================
// LOAD GAMBAR
function loadGambar(){
drawnItems.clearLayers(); // 🔥 FIX biar tidak dobel
return fetch('get.php')
.then(res=>res.json())
.then(data=>{
L.geoJSON(data, {
onEachFeature: function(feature, layer){
layer._id = feature.properties.id;
layer._tipe = feature.properties.tipe;
// Make layer interactive so it can be clicked and edited
if (layer.setStyle) {
layer.setStyle({ interactive: true });
}
var popupContent = '';
if (feature.properties.tipe === 'jalan') {
popupContent = `<b>Jalan</b><br>Status: ${feature.properties.status}<br>Panjang: ${feature.properties.nilai} m`;
} else if (feature.properties.tipe === 'parsil') {
popupContent = `<b>Parsil</b><br>Status: ${feature.properties.status}<br>Luas: ${(feature.properties.nilai / 1000000).toFixed(2)} km²`;
}
// Add Edit / Delete buttons to popup for convenience
popupContent += '<br><br>' +
`<button onclick="enableLayerEdit(${feature.properties.id})">Edit</button> ` +
`<button onclick="deleteFeature(${feature.properties.id}, '${feature.properties.tipe}')" style="background:#c0392b;color:white;border:none;padding:4px;">Hapus</button>`;
layer.bindPopup(popupContent);
drawnItems.addLayer(layer);
},
style: function(feature) {
var status = feature.properties.status;
if (feature.properties.tipe === 'jalan') {
// Thicker lines so they are easy to click/edit/delete
if (status === 'Nasional') return { color: '#c0392b', weight: 6, opacity: 0.9 };
if (status === 'Provinsi') return { color: '#2980b9', weight: 5, opacity: 0.9 };
if (status === 'Kabupaten') return { color: '#27ae60', weight: 4, opacity: 0.9 };
return { color: 'gray', weight: 4, opacity: 0.8 };
} else if (feature.properties.tipe === 'parsil') {
// Polygons with visible fill and border
if (status === 'SHM') return { color: '#16a085', weight: 2, fillColor: '#16a085', fillOpacity: 0.4 };
if (status === 'HGB') return { color: '#f1c40f', weight: 2, fillColor: '#f1c40f', fillOpacity: 0.35 };
if (status === 'HGU') return { color: '#e67e22', weight: 2, fillColor: '#e67e22', fillOpacity: 0.35 };
if (status === 'HP') return { color: '#e74c3c', weight: 2, fillColor: '#e74c3c', fillOpacity: 0.35 };
return { color: 'gray', weight: 2, fillOpacity: 0.25 };
}
}
}).eachLayer(function(layer){
// Ensure polylines/polygons are clickable (increase tolerance)
if (layer instanceof L.Polyline && !(layer instanceof L.Polygon)) {
layer.options.smoothFactor = 1;
}
});
});
}
// =====================
// LAYER CONTROLS
// =====================
var overlayMaps = {
'Rumah Ibadah': worshipLayer,
'Rumah Penduduk Miskin': poorLayer,
'SPBU / Data Umum': pointLayer,
'Lingkaran Radius': circleLayer,
'Kecamatan': kecamatanLayer
};
L.control.layers(null, overlayMaps, { collapsed: false }).addTo(map);
// =====================
// LOAD POINT DATA
// =====================
function loadData(){
worshipLayer.clearLayers();
poorLayer.clearLayers();
pointLayer.clearLayers();
circleLayer.clearLayers();
pointMarkers = {};
worshipCircles = {};
worshipHandles = {};
return fetch('get_point.php')
.then(function(res){ return res.json(); })
.then(function(data){
data.forEach(function(d){
if (d.tipe === 'worship') {
renderWorshipMarker(d);
} else if (d.tipe === 'poor_house') {
renderPoorMarker(d);
} else {
renderGenericPoint(d);
}
});
})
.catch(function(err){ console.error('Load point error:', err); });
}
function refreshAllData() {
return Promise.all([loadData(), loadGambar()]);
}
// =====================
refreshAllData();
// =====================
});
</script>
</body>
</html>