Upload files to "/"

This commit is contained in:
2026-06-11 12:24:44 +00:00
parent 694fd32a10
commit 17800461cd
5 changed files with 166 additions and 775 deletions
+9
View File
@@ -0,0 +1,9 @@
FROM php:8.2-apache
RUN docker-php-ext-install mysqli pdo pdo_mysql
COPY . /var/www/html/
RUN chown -R www-data:www-data /var/www/html
EXPOSE 80
+47
View File
@@ -0,0 +1,47 @@
CREATE TABLE IF NOT EXISTS spbu (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(150) NOT NULL,
hp VARCHAR(50) DEFAULT NULL,
buka TINYINT(1) NOT NULL DEFAULT 1,
lat DECIMAL(12,8) NOT NULL,
lng DECIMAL(12,8) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS jalan (
id INT AUTO_INCREMENT PRIMARY KEY,
status VARCHAR(100) NOT NULL,
panjang DECIMAL(12,2) DEFAULT 0,
koordinat LONGTEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS tanah (
id INT AUTO_INCREMENT PRIMARY KEY,
status VARCHAR(100) NOT NULL,
luas DECIMAL(12,2) DEFAULT 0,
koordinat LONGTEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS populasi_kecamatan (
id INT AUTO_INCREMENT PRIMARY KEY,
kecamatan VARCHAR(100) NOT NULL UNIQUE,
populasi INT NOT NULL DEFAULT 0,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO spbu (nama, hp, buka, lat, lng) VALUES
('SPBU Ahmad Yani', '081234567890', 1, -0.05586000, 109.34725000),
('SPBU Imam Bonjol', '081234567891', 0, -0.03372000, 109.34201000),
('SPBU Sungai Raya Dalam', '081234567892', 1, -0.07043000, 109.37850000)
ON DUPLICATE KEY UPDATE nama=VALUES(nama);
INSERT INTO populasi_kecamatan (kecamatan, populasi) VALUES
('Pontianak Barat', 150000),
('Pontianak Kota', 130000),
('Pontianak Selatan', 95000),
('Pontianak Tenggara', 53000),
('Pontianak Timur', 104000),
('Pontianak Utara', 128000)
ON DUPLICATE KEY UPDATE populasi=VALUES(populasi);
+44 -760
View File
@@ -1,760 +1,44 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="id"> <html lang="id">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<title>WebGIS Kota Pontianak - Pemetaan SPBU, Jalan, dan Tanah</title> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /> <title>Landing Page Tugas SIG - Pertemuan 1-4</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.css" /> <style>
<style> *{box-sizing:border-box;margin:0;padding:0}
* { margin: 0; padding: 0; box-sizing: border-box; } body{font-family:Inter,Segoe UI,Arial,sans-serif;background:#f5f7fb;color:#1f2937;min-height:100vh}
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } .hero{padding:52px 22px 28px;background:linear-gradient(135deg,#0f766e,#0ea5e9);color:white}
#map { height: 100vh; width: 100%; } .wrap{max-width:1080px;margin:auto}
.badge{display:inline-block;background:rgba(255,255,255,.18);border:1px solid rgba(255,255,255,.3);padding:8px 12px;border-radius:999px;margin-bottom:18px;font-size:14px}
.leaflet-popup-content { min-width: 280px; } h1{font-size:clamp(30px,5vw,54px);line-height:1.05;margin-bottom:14px}
.popup-form { min-width: 260px; padding: 5px; } .hero p{max-width:760px;font-size:17px;line-height:1.7;opacity:.95}
.popup-form h4 { margin: 0 0 12px 0; color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 5px; } main{max-width:1080px;margin:-18px auto 40px;padding:0 22px}
.popup-form input, .popup-form select { width: 100%; margin-bottom: 12px; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; font-size: 14px; } .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:18px}
.popup-form button { width: 100%; background: #27ae60; color: white; border: none; padding: 10px; cursor: pointer; border-radius: 4px; font-weight: bold; font-size: 14px; } .card{background:white;border-radius:20px;padding:22px;border:1px solid #e5e7eb;box-shadow:0 12px 30px rgba(15,23,42,.08);display:flex;flex-direction:column;min-height:250px}
.popup-form button:hover { background: #219150; } .icon{width:48px;height:48px;border-radius:14px;display:grid;place-items:center;background:#ecfeff;font-size:24px;margin-bottom:15px}
.popup-form p { margin: 8px 0; font-size: 12px; color: #666; text-align: center; background: #f8f9fa; padding: 5px; border-radius: 4px; } .card h2{font-size:20px;margin-bottom:8px;color:#0f172a}
.card p{font-size:14px;color:#64748b;line-height:1.6;flex:1}
.legend { position: absolute; bottom: 20px; left: 20px; background: white; padding: 10px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); z-index: 1000; font-size: 12px; pointer-events: none; } a.btn{display:inline-block;text-decoration:none;background:#0f766e;color:white;padding:11px 14px;border-radius:12px;font-weight:700;text-align:center;margin-top:18px}
.legend span { display: inline-block; width: 20px; height: 20px; margin-right: 5px; vertical-align: middle; } a.btn:hover{background:#0d5f59}
.legend .spbu-icon { background: none; width: 20px; height: 20px; display: inline-flex; align-items: center; justify-content: center; font-size: 16px; } .note{background:white;border:1px solid #e5e7eb;border-radius:16px;padding:16px;margin-top:20px;color:#475569;line-height:1.6;font-size:14px}
</style>
.population-panel { position: absolute; bottom: 20px; right: 20px; background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); z-index: 900; width: 280px; max-height: 400px; overflow-y: auto; font-size: 13px; } </head>
.population-panel h3 { margin: 0 0 10px 0; color: #2c3e50; text-align: center; border-bottom: 2px solid #3498db; padding-bottom: 5px; } <body>
.population-item { padding: 8px; margin-bottom: 8px; background: #f8f9fa; border-radius: 6px; cursor: pointer; transition: all 0.2s; } <section class="hero">
.population-item:hover { background: #e9ecef; transform: translateX(-3px); } <div class="wrap">
.population-item strong { color: #2c3e50; display: block; } <div class="badge">Mata Kuliah SIG • Pertemuan 1-4</div>
.population-item .pop-value { color: #27ae60; font-weight: bold; font-size: 14px; } <h1>Kumpulan Tugas WebGIS</h1>
.total-pop { background: #2c3e50; color: white; padding: 10px; border-radius: 6px; text-align: center; margin-top: 10px; } <p>Landing page ini menampilkan seluruh tugas WebGIS yang dikerjakan pada pertemuan kelas. Klik salah satu kartu untuk membuka project sesuai topik.</p>
.total-pop span { font-size: 18px; font-weight: bold; color: #f1c40f; } </div>
</section>
.map-selector { position: absolute; top: 20px; left: 20px; background: white; padding: 10px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); z-index: 1000; font-size: 12px; } <main>
.map-selector select { padding: 5px 10px; border-radius: 4px; border: 1px solid #ddd; margin-left: 5px; cursor: pointer; } <div class="grid">
<article class="card"><div class="icon"></div><h2>Tugas 1</h2><p>WebGIS pemetaan lokasi SPBU di Kota Pontianak menggunakan marker Leaflet.</p><a class="btn" href="Tugas1/">Buka Tugas 1</a></article>
.toast-notification { position: fixed; bottom: 30px; right: 30px; background: #2c3e50; color: white; padding: 12px 20px; border-radius: 8px; z-index: 2000; animation: fadeOut 3s ease forwards; } <article class="card"><div class="icon">🛣️</div><h2>Tugas 2</h2><p>WebGIS manajemen data jalan berbentuk polyline dan data tanah berbentuk polygon.</p><a class="btn" href="Tugas2/">Buka Tugas 2</a></article>
@keyframes fadeOut { 0% { opacity: 1; } 70% { opacity: 1; } 100% { opacity: 0; visibility: hidden; } } <article class="card"><div class="icon">🗂️</div><h2>Tugas 3</h2><p>Layer Groups dan Layers Control untuk menampilkan kategori SPBU buka 24 jam dan tidak 24 jam.</p><a class="btn" href="Tugas3/">Buka Tugas 3</a></article>
<article class="card"><div class="icon">🏙️</div><h2>Tugas 4</h2><p>WebGIS GeoJSON kecamatan Kota Pontianak yang menampilkan nama kecamatan dan jumlah populasi.</p><a class="btn" href="Tugas4/">Buka Tugas 4</a></article>
.loading { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,0.8); color: white; padding: 15px 30px; border-radius: 8px; z-index: 3000; } </div>
.form-group { margin-bottom: 10px; } <div class="note"><b>Catatan:</b> Deploy cukup satu resource aplikasi di Coolify. Tugas dapat diakses melalui subfolder <code>/Tugas1/</code>, <code>/Tugas2/</code>, <code>/Tugas3/</code>, dan <code>/Tugas4/</code>.</div>
.form-group label { display: block; margin-bottom: 5px; font-weight: bold; font-size: 13px; } </main>
.btn-hapus { background: #e74c3c !important; width: 100% !important; } </body>
.btn-hapus:hover { background: #c0392b !important; } </html>
.instruction-box {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.8);
color: white;
padding: 8px 15px;
border-radius: 20px;
font-size: 12px;
z-index: 1000;
pointer-events: none;
white-space: nowrap;
}
.spbu-marker {
background: none;
border: none;
}
.leaflet-control-layers {
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
}
/* Styling untuk Layer Control */
.leaflet-control-layers {
max-width: 240px !important;
font-size: 13px !important;
z-index: 1010 !important;
pointer-events: auto !important;
}
.leaflet-control-layers-list {
display: block !important;
pointer-events: auto !important;
}
.leaflet-control-layers-list label {
display: block !important;
margin-bottom: 8px !important;
cursor: pointer !important;
user-select: none;
padding: 4px 2px !important;
transition: all 0.2s ease;
pointer-events: auto !important;
}
.leaflet-control-layers-list label:hover {
background-color: #f0f0f0 !important;
border-radius: 3px;
}
/* PENTING: Pastikan checkbox FULLY VISIBLE */
.leaflet-control-layers-list input[type="checkbox"],
.leaflet-control-layers-list input[type="radio"] {
display: inline-block !important;
visibility: visible !important;
opacity: 1 !important;
width: 16px !important;
height: 16px !important;
margin-right: 8px !important;
margin-left: 0 !important;
cursor: pointer !important;
accent-color: #27ae60 !important;
vertical-align: middle !important;
background: white !important;
border: 1px solid #999 !important;
padding: 0 !important;
}
.leaflet-control-layers-list input:checked {
accent-color: #27ae60 !important;
}
.leaflet-control-layers-overlays {
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid #ddd;
}
</style>
</head>
<body>
<div id="map"></div>
<div class="map-selector">
<label>🗺️ Pilih Peta:</label>
<select id="mapSelector">
<option value="street">🌍 Street Map</option>
<option value="satellite">🛰️ Satellite</option>
<option value="osm">🗺️ OpenStreetMap</option>
</select>
</div>
<div class="population-panel" id="populationPanel">
<h3>📊 POPULASI PER KECAMATAN</h3>
<div id="populationList"><p style="text-align:center; color:#999;">Memuat data...</p></div>
</div>
<div class="legend">
<strong>Legenda</strong><br>
<span class="spbu-icon"></span> SPBU Buka 24 Jam<br>
<span class="spbu-icon" style="filter: grayscale(0.5);"></span> SPBU Tutup Malam<br>
<span style="background: #e74c3c; height: 3px; display: inline-block;"></span> Jalan Nasional<br>
<span style="background: #f39c12; height: 3px; display: inline-block;"></span> Jalan Provinsi<br>
<span style="background: #3498db; height: 3px; display: inline-block;"></span> Jalan Kota<br>
<span style="background: #e67e22;"></span> Bidang Tanah<br>
<span style="background: #2c3e50; border: 1px dashed #2c3e50;"></span> Batas Kecamatan
</div>
<div class="instruction-box" id="instructionBox">
💡 Petunjuk: Untuk menggambar, klik titik-titik di peta. Selesai = DOUBLE CLICK atau klik tombol ✅
</div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.js"></script>
<script src="https://unpkg.com/leaflet-geometryutil@0.3.0/src/leaflet.geometryutil.js"></script>
<script>
// ==================== INISIALISASI ====================
var map = L.map('map').setView([-0.0263, 109.3425], 12);
// Base Maps
var baseMaps = {
street: L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
attribution: '&copy; OSM & CartoDB', subdomains: 'abcd', maxZoom: 19
}),
satellite: L.tileLayer('https://{s}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', {
subdomains: ['mt0', 'mt1', 'mt2', 'mt3'], attribution: '&copy; Google Maps', maxZoom: 20
}),
osm: L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; OSM', maxZoom: 19
})
};
baseMaps.street.addTo(map);
document.getElementById('mapSelector').addEventListener('change', function(e) {
for (var key in baseMaps) map.removeLayer(baseMaps[key]);
baseMaps[e.target.value].addTo(map);
});
// ==================== LAYER GROUPS ====================
// Layer group untuk SPBU Buka 24 Jam
var spbuBukaLayer = L.layerGroup();
// Layer group untuk SPBU Tutup Malam
var spbuTutupLayer = L.layerGroup();
// Layer group untuk Jalan
var jalanLayer = L.layerGroup();
// Layer group untuk Tanah
var tanahLayer = L.layerGroup();
// Layer group untuk Batas Kecamatan
var kecamatanLayer = L.layerGroup();
// Tambahkan layer ke map (hanya layer yang ditampilkan default)
spbuBukaLayer.addTo(map); // ✅ SPBU 24 Jam ditampilkan default
// spbuTutupLayer.addTo(map); // ❌ SPBU Tutup Malam TIDAK ditampilkan default
jalanLayer.addTo(map);
tanahLayer.addTo(map);
kecamatanLayer.addTo(map);
// Layer group untuk data yang digambar (drawing)
var drawnItems = new L.FeatureGroup();
map.addLayer(drawnItems);
// ==================== LAYER CONTROL ====================
var overlayMaps = {
"⛽ SPBU Buka 24 Jam": spbuBukaLayer,
"🌙 SPBU Tutup Malam": spbuTutupLayer,
"🛣️ Jalan": jalanLayer,
"🏞️ Bidang Tanah": tanahLayer,
"🏛️ Batas Kecamatan": kecamatanLayer
};
// Tambahkan Layer Control ke map dengan opsi lebih baik
var layerControl = L.control.layers(null, overlayMaps, {
collapsed: false,
position: 'topright'
}).addTo(map);
// Tambahkan tooltip ke layer control dengan element event listener
map.on('baselayerchange', function() {
console.log('Layer control initialized');
});
// Pastikan checkboxes visible ketika layer control fully loaded
window.addEventListener('load', function() {
var layerControlElement = document.querySelector('.leaflet-control-layers');
if (layerControlElement) {
// Add tooltip ke semua label
var labels = layerControlElement.querySelectorAll('label');
labels.forEach(function(label, index) {
var inputs = label.querySelectorAll('input');
if (inputs.length > 0) {
var input = inputs[0];
// Tampilkan checkbox explicitly
input.style.display = 'inline-block';
input.style.visibility = 'visible';
input.style.opacity = '1';
// Tambahkan tooltip berdasarkan text content
var text = label.textContent || '';
if (text.includes('Buka 24')) {
label.title = 'Tampilkan/Sembunyikan SPBU yang Buka 24 Jam';
} else if (text.includes('Tutup')) {
label.title = 'Tampilkan/Sembunyikan SPBU yang Tutup Malam';
} else if (text.includes('Jalan')) {
label.title = 'Tampilkan/Sembunyikan Data Jalan';
} else if (text.includes('Tanah')) {
label.title = 'Tampilkan/Sembunyikan Data Bidang Tanah';
} else if (text.includes('Kecamatan')) {
label.title = 'Tampilkan/Sembunyikan Batas-batas Kecamatan';
}
}
});
}
});
// Data populasi
var populasiData = {
"Pontianak Timur": 0, "Pontianak Selatan": 0, "Pontianak Utara": 0,
"Pontianak Barat": 0, "Pontianak Kota": 0, "Pontianak Tenggara": 0
};
// Variabel pending data
window.pendingJalan = null;
window.pendingTanah = null;
function showToast(message) {
var toast = document.createElement('div');
toast.className = 'toast-notification';
toast.innerHTML = message;
document.body.appendChild(toast);
setTimeout(function() { toast.remove(); }, 3000);
}
function formatNumber(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
}
// ==================== MEMBUAT IKON SPBU ====================
function createSpbuIcon(isOpen) {
var color = isOpen ? '#27ae60' : '#e74c3c';
var svgHtml = '<div style="position: relative;">' +
'<svg width="32" height="42" viewBox="0 0 24 36" fill="none" xmlns="http://www.w3.org/2000/svg">' +
'<path d="M12 0C5.37 0 0 5.37 0 12c0 9 12 24 12 24s12-15 12-24c0-6.63-5.37-12-12-12z" fill="' + color + '" stroke="#fff" stroke-width="1.5"/>' +
'<circle cx="12" cy="12" r="5" fill="white" stroke="#333" stroke-width="0.5"/>' +
'<text x="12" y="15" text-anchor="middle" font-size="9" font-weight="bold" fill="' + color + '" font-family="Arial">⛽</text>' +
'</svg>' +
'</div>';
return L.divIcon({
className: 'spbu-marker',
html: svgHtml,
iconSize: [32, 42],
iconAnchor: [16, 42],
popupAnchor: [0, -38]
});
}
// Ikon default untuk draw marker
var defaultSpbuIcon = createSpbuIcon(true);
// ==================== POPULASI & LOAD GEOJSON KECAMATAN ====================
fetch('Admin_Kecamatan.json')
.then(function(response) { return response.json(); })
.then(function(data) {
data.features.forEach(function(feature) {
var namaKec = feature.properties.Ket || "Wilayah";
var populasi = feature.properties.Populasi || 0;
if (populasiData.hasOwnProperty(namaKec)) {
populasiData[namaKec] = populasi;
}
});
updatePopulationPanel();
// Tampilkan ke peta - tambahkan ke kecamatanLayer
L.geoJSON(data, {
style: { color: "#2c3e50", weight: 2.5, fillOpacity: 0.15, dashArray: '5,5', fillColor: "#3498db" },
onEachFeature: function(feature, layer) {
var namaKec = feature.properties.Ket || "Wilayah";
var pop = populasiData[namaKec] || 0;
var luasKm2 = feature.properties.Plan_Area / 1000000;
var kepadatan = luasKm2 > 0 ? (pop / luasKm2).toFixed(0) : 0;
layer.bindPopup('<b>🏛️ KECAMATAN ' + namaKec + '</b><br>' +
'<b>Luas:</b> ' + luasKm2.toFixed(2) + ' km²<br>' +
'<b>👥 Populasi:</b> ' + formatNumber(pop) + ' jiwa<br>' +
'<b>📈 Kepadatan:</b> ' + kepadatan + ' jiwa/km²');
}
}).addTo(kecamatanLayer);
fetch('api.php?action=get_populasi')
.then(function(r) { return r.json(); })
.then(function(dbData) {
if (dbData.status === 'ok' && dbData.data && dbData.data.length > 0) {
var updated = false;
dbData.data.forEach(function(item) {
if (populasiData.hasOwnProperty(item.kecamatan)) {
populasiData[item.kecamatan] = parseInt(item.populasi);
updated = true;
}
});
if (updated) {
updatePopulationPanel();
console.log("Populasi diupdate dari database");
}
}
})
.catch(function(err) { console.error("Gagal load populasi dari DB:", err); });
})
.catch(function(err) { console.error("Gagal load GeoJSON:", err); });
function simpanPopulasi(kecamatan, populasi) {
fetch('api.php?action=update_populasi', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ kecamatan: kecamatan, populasi: populasi })
})
.then(function(r) { return r.json(); })
.then(function(res) {
if(res.status === 'ok') {
populasiData[kecamatan] = populasi;
updatePopulationPanel();
showToast('✅ Populasi ' + kecamatan + ' diupdate menjadi ' + formatNumber(populasi) + ' jiwa');
}
});
}
function updatePopulationPanel() {
var panel = document.getElementById('populationList');
var total = 0;
var html = '';
var kecamatans = Object.keys(populasiData).sort();
for (var i = 0; i < kecamatans.length; i++) {
var kec = kecamatans[i];
var pop = populasiData[kec] || 0;
total += pop;
html += '<div class="population-item" onclick="showEditPopup(\'' + kec + '\', ' + pop + ')">' +
'<strong>🏘️ ' + kec + '</strong>' +
'<div class="pop-value">👥 ' + formatNumber(pop) + ' jiwa</div>' +
'</div>';
}
html += '<div class="total-pop">📊 TOTAL POPULASI<br><span>' + formatNumber(total) + ' jiwa</span></div>';
panel.innerHTML = html;
}
function showEditPopup(kecamatan, currentPop) {
var form = '<div style="min-width:250px;padding:10px">' +
'<h4 style="margin:0 0 10px 0">✏️ Edit Populasi</h4>' +
'<b>Kecamatan:</b> ' + kecamatan + '<br><br>' +
'<label>Jumlah Penduduk:</label>' +
'<input type="number" id="edit_populasi" value="' + currentPop + '" min="0" style="width:100%;padding:8px;margin:8px 0;border:1px solid #ddd;border-radius:4px">' +
'<button onclick="savePopulationEdit(\'' + kecamatan + '\')" style="width:100%;background:#27ae60;color:white;border:none;padding:8px;border-radius:4px;cursor:pointer">💾 Simpan</button>' +
'</div>';
L.popup().setLatLng(map.getCenter()).setContent(form).openOn(map);
}
function savePopulationEdit(kecamatan) {
var newPop = parseInt(document.getElementById('edit_populasi')?.value) || 0;
map.closePopup();
simpanPopulasi(kecamatan, newPop);
}
// ==================== HAPUS DATA ====================
function hapusData(type, id) {
if (confirm('Apakah Anda yakin ingin menghapus data ini?')) {
fetch('api.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'delete_' + type, id: id })
})
.then(function(r) { return r.json(); })
.then(function(res) {
if(res.status === 'ok') {
showToast('✅ Data berhasil dihapus!');
loadData();
map.closePopup();
} else {
alert('❌ Gagal hapus: ' + (res.msg || "Cek koneksi database"));
}
})
.catch(function(err) {
alert('❌ Error: ' + err.message);
});
}
}
// ==================== SIMPAN SPBU ====================
function simpanSPBU(lat, lng) {
var nama = document.getElementById('nama_spbu').value;
var hp = document.getElementById('hp_spbu').value;
var buka = document.getElementById('status_spbu').value;
if (!nama) {
alert("Nama SPBU harus diisi!");
return;
}
fetch('api.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'add_spbu', nama: nama, hp: hp, buka: parseInt(buka), lat: lat, lng: lng
})
})
.then(function(r) { return r.json(); })
.then(function(res) {
if(res.status === 'ok') {
showToast("✅ SPBU berhasil disimpan!");
map.closePopup();
loadData();
} else {
alert("❌ Gagal simpan: " + (res.msg || "Cek koneksi database"));
}
})
.catch(function(err) {
alert("❌ Error: " + err.message);
});
}
// ==================== SIMPAN JALAN ====================
window.simpanJalanBaru = function() {
console.log("simpanJalanBaru dipanggil");
var statusSelect = document.getElementById('status_jalan');
if (!statusSelect) {
alert("Error: Form tidak ditemukan!");
return;
}
var status = statusSelect.value;
if (!window.pendingJalan) {
alert("Data koordinat tidak ditemukan! Silakan gambar ulang jalan.");
return;
}
var coords = window.pendingJalan.coords;
var distance = window.pendingJalan.distance;
var cleanCoords = [];
for (var i = 0; i < coords.length; i++) {
cleanCoords.push([coords[i].lat, coords[i].lng]);
}
console.log("Menyimpan jalan - titik: " + cleanCoords.length);
fetch('api.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'add_jalan', status: status, panjang: distance, koordinat: JSON.stringify(cleanCoords)
})
})
.then(function(r) { return r.json(); })
.then(function(res) {
if(res.status === 'ok') {
showToast("✅ Jalan berhasil disimpan!");
map.closePopup();
loadData();
window.pendingJalan = null;
} else {
alert("❌ Gagal simpan: " + (res.msg || "Cek koneksi database"));
}
})
.catch(function(err) {
alert("❌ Error: " + err.message);
});
};
// ==================== SIMPAN TANAH ====================
window.simpanTanahBaru = function() {
console.log("simpanTanahBaru dipanggil");
var statusSelect = document.getElementById('status_tanah');
if (!statusSelect) {
alert("Error: Form tidak ditemukan!");
return;
}
var status = statusSelect.value;
if (!window.pendingTanah) {
alert("Data koordinat tidak ditemukan! Silakan gambar ulang bidang tanah.");
return;
}
var coords = window.pendingTanah.coords;
var area = window.pendingTanah.area;
var cleanCoords = [];
for (var i = 0; i < coords.length; i++) {
cleanCoords.push([coords[i].lat, coords[i].lng]);
}
if (cleanCoords.length > 0) {
var first = cleanCoords[0];
var last = cleanCoords[cleanCoords.length - 1];
if (first[0] !== last[0] || first[1] !== last[1]) {
cleanCoords.push(first);
}
}
console.log("Menyimpan tanah - titik: " + cleanCoords.length);
fetch('api.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'add_tanah', status: status, luas: area, koordinat: JSON.stringify([cleanCoords])
})
})
.then(function(r) { return r.json(); })
.then(function(res) {
if(res.status === 'ok') {
showToast("✅ Bidang tanah berhasil disimpan!");
map.closePopup();
loadData();
window.pendingTanah = null;
} else {
alert("❌ Gagal simpan: " + (res.msg || "Cek koneksi database"));
}
})
.catch(function(err) {
alert("❌ Error: " + err.message);
});
};
// ==================== LOAD SEMUA DATA ====================
function loadData() {
console.log("Memuat data...");
// Kosongkan semua layer groups
spbuBukaLayer.clearLayers();
spbuTutupLayer.clearLayers();
jalanLayer.clearLayers();
tanahLayer.clearLayers();
// Load SPBU dan pisahkan berdasarkan status buka/tutup
fetch('api.php?action=get_spbu')
.then(function(r) { return r.json(); })
.then(function(data) {
if (Array.isArray(data)) {
data.forEach(function(item) {
var isOpen = (item.buka == 1);
var icon = createSpbuIcon(isOpen);
var marker = L.marker([parseFloat(item.lat), parseFloat(item.lng)], {
icon: icon
});
marker.bindPopup('<b>⛽ SPBU</b><br><b>Nama:</b> ' + item.nama + '<br><b>HP:</b> ' + item.hp + '<br><b>Status:</b> ' + (isOpen ? '✅ Buka 24 Jam' : '🌙 Tutup Malam') + '<hr><button class="btn-hapus" onclick="hapusData(\'spbu\', ' + item.id + ')">🗑️ Hapus SPBU</button>');
if (isOpen) {
marker.addTo(spbuBukaLayer);
} else {
marker.addTo(spbuTutupLayer);
}
});
}
})
.catch(function(err) { console.error("Gagal load SPBU:", err); });
// Load Jalan
fetch('api.php?action=get_jalan')
.then(function(r) { return r.json(); })
.then(function(data) {
if (Array.isArray(data)) {
data.forEach(function(item) {
try {
var coords = JSON.parse(item.koordinat);
var latlngs = [];
for (var i = 0; i < coords.length; i++) {
if (Array.isArray(coords[i]) && coords[i].length >= 2) {
latlngs.push([coords[i][0], coords[i][1]]);
}
}
if (latlngs.length >= 2) {
var warnaJalan = '#3498db';
if (item.status === 'Nasional') {
warnaJalan = '#e74c3c';
} else if (item.status === 'Provinsi') {
warnaJalan = '#f39c12';
}
var polyline = L.polyline(latlngs, { color: warnaJalan, weight: 4, opacity: 0.8 });
polyline.bindPopup('<b>🛣️ JALAN</b><br><b>Status:</b> ' + item.status + '<br><b>Panjang:</b> ' + parseFloat(item.panjang).toFixed(2) + ' meter<hr><button class="btn-hapus" onclick="hapusData(\'jalan\', ' + item.id + ')">🗑️ Hapus Jalan</button>');
polyline.addTo(jalanLayer);
}
} catch(e) { console.error("Error jalan:", item.id, e); }
});
}
})
.catch(function(err) { console.error("Gagal load Jalan:", err); });
// Load Tanah
fetch('api.php?action=get_tanah')
.then(function(r) { return r.json(); })
.then(function(data) {
if (Array.isArray(data)) {
data.forEach(function(item) {
try {
var coords = JSON.parse(item.koordinat);
var rings = [];
if (Array.isArray(coords) && coords.length > 0) {
if (coords.length > 0 && Array.isArray(coords[0]) && typeof coords[0][0] === 'number') {
rings = [coords];
} else {
rings = coords;
}
}
var latlngs = [];
for (var r = 0; r < rings.length; r++) {
var ring = rings[r];
var ringLatLngs = [];
for (var i = 0; i < ring.length; i++) {
if (Array.isArray(ring[i]) && ring[i].length >= 2) {
ringLatLngs.push([ring[i][0], ring[i][1]]);
}
}
if (ringLatLngs.length >= 3) latlngs.push(ringLatLngs);
}
if (latlngs.length > 0 && latlngs[0].length >= 3) {
var polygon = L.polygon(latlngs, { color: '#e67e22', weight: 2, fillOpacity: 0.4, fillColor: '#f39c12' });
polygon.bindPopup('<b>🏞️ BIDANG TANAH</b><br><b>Status:</b> ' + item.status + '<br><b>Luas:</b> ' + parseFloat(item.luas).toFixed(2) + ' m²<hr><button class="btn-hapus" onclick="hapusData(\'tanah\', ' + item.id + ')">🗑️ Hapus Tanah</button>');
polygon.addTo(tanahLayer);
}
} catch(e) { console.error("Error tanah:", item.id, e); }
});
}
})
.catch(function(err) { console.error("Gagal load Tanah:", err); });
}
// ==================== DRAW CONTROL ====================
var drawControl = new L.Control.Draw({
edit: { featureGroup: drawnItems, remove: false },
draw: {
polyline: {
shapeOptions: { color: '#3498db', weight: 4 },
metric: true,
showLength: true
},
polygon: {
shapeOptions: { color: '#e67e22', weight: 2 },
metric: true,
showArea: true
},
marker: {
icon: defaultSpbuIcon
},
circle: false,
rectangle: false,
circlemarker: false
}
});
map.addControl(drawControl);
// ==================== EVENT DRAW CREATED ====================
map.on(L.Draw.Event.CREATED, function(e) {
var layer = e.layer;
var type = e.layerType;
console.log("Event CREATED: " + type);
if (type === 'marker') {
var latlng = layer.getLatLng();
var form = '<div class="popup-form">' +
'<h4>📌 Input SPBU Baru</h4>' +
'<input type="text" id="nama_spbu" placeholder="Nama SPBU" required>' +
'<input type="text" id="hp_spbu" placeholder="Nomor Telepon" required>' +
'<select id="status_spbu"><option value="1">✅ Buka 24 Jam</option><option value="0">🌙 Tutup Malam</option></select>' +
'<button onclick="simpanSPBU(' + latlng.lat + ', ' + latlng.lng + ')">💾 Simpan SPBU</button>' +
'</div>';
layer.bindPopup(form).openPopup();
drawnItems.addLayer(layer);
}
else if (type === 'polyline') {
var coords = layer.getLatLngs();
var distance = 0;
for (var i = 0; i < coords.length - 1; i++) {
distance += coords[i].distanceTo(coords[i + 1]);
}
window.pendingJalan = { coords: coords, distance: distance };
console.log("Jalan siap disimpan, panjang: " + distance.toFixed(2) + "m");
var form = '<div class="popup-form">' +
'<h4>🛣️ Input Jalan Baru</h4>' +
'<div class="form-group"><label>Status Jalan:</label>' +
'<select id="status_jalan"><option value="Nasional">Jalan Nasional</option><option value="Provinsi">Jalan Provinsi</option><option value="Kota">Jalan Kota</option></select></div>' +
'<p>📏 Panjang: ' + distance.toFixed(2) + ' meter</p>' +
'<button onclick="window.simpanJalanBaru()">💾 Simpan Jalan</button>' +
'</div>';
layer.bindPopup(form).openPopup();
drawnItems.addLayer(layer);
}
else if (type === 'polygon') {
var latlngs = layer.getLatLngs()[0];
var area = L.GeometryUtil.geodesicArea(latlngs);
window.pendingTanah = { coords: latlngs, area: area };
console.log("Tanah siap disimpan, luas: " + area.toFixed(2) + "m²");
var form = '<div class="popup-form">' +
'<h4>🏞️ Input Bidang Tanah</h4>' +
'<div class="form-group"><label>Status Tanah:</label>' +
'<select id="status_tanah"><option value="SHM">SHM (Sertifikat Hak Milik)</option><option value="HGB">HGB (Hak Guna Bangunan)</option><option value="HGU">HGU (Hak Guna Usaha)</option></select></div>' +
'<p>📐 Luas: ' + area.toFixed(2) + ' m²</p>' +
'<button onclick="window.simpanTanahBaru()">💾 Simpan Tanah</button>' +
'</div>';
layer.bindPopup(form).openPopup();
drawnItems.addLayer(layer);
}
});
// ==================== LOAD DATA SAAT HALAMAN DIBUKA ====================
loadData();
</script>
</body>
</html>
+47
View File
@@ -0,0 +1,47 @@
<?php
set_time_limit(300);
$host = getenv('DB_HOST') ?: 'localhost';
$user = getenv('DB_USER') ?: 'root';
$pass = getenv('DB_PASS') ?: (getenv('DB_PASSWORD') ?: '');
$db = getenv('DB_NAME') ?: 'webgis';
$port = (int)(getenv('DB_PORT') ?: 3306);
echo "<h2>Install Database Pertemuan 1-2-3-4</h2>";
echo "<p>Target koneksi: <b>" . htmlspecialchars($host) . ":" . htmlspecialchars((string)$port) . "</b>, database: <b>" . htmlspecialchars($db) . "</b>, user: <b>" . htmlspecialchars($user) . "</b></p>";
$conn = new mysqli($host, $user, $pass, $db, $port);
if ($conn->connect_error) {
die("<p style='color:red'>Koneksi gagal: " . htmlspecialchars($conn->connect_error) . "</p>");
}
$sqlFile = __DIR__ . '/database.sql';
if (!file_exists($sqlFile)) {
die("<p style='color:red'>File database.sql tidak ditemukan.</p>");
}
$sql = file_get_contents($sqlFile);
$sql = preg_replace('/CREATE\s+DATABASE\s+IF\s+NOT\s+EXISTS\s+`?[^`\s;]+`?.*?;/is', '', $sql);
$sql = preg_replace('/CREATE\s+DATABASE\s+`?[^`\s;]+`?.*?;/is', '', $sql);
$sql = preg_replace('/USE\s+`?[^`\s;]+`?\s*;/is', '', $sql);
if ($conn->multi_query($sql)) {
do {
if ($result = $conn->store_result()) {
$result->free();
}
} while ($conn->more_results() && $conn->next_result());
if ($conn->errno) {
echo "<p style='color:red'>Ada error saat import: " . htmlspecialchars($conn->error) . "</p>";
} else {
echo "<p style='color:green;font-weight:bold'>Database berhasil diimport.</p>";
echo "<p>Setelah berhasil, hapus file <code>install_database.php</code> dari repo lalu redeploy.</p>";
}
} else {
echo "<p style='color:red'>Import gagal: " . htmlspecialchars($conn->error) . "</p>";
}
$conn->close();
?>
+19 -15
View File
@@ -1,15 +1,19 @@
<?php <?php
$host = 'localhost'; $host = getenv('DB_HOST') ?: 'localhost';
$user = 'root'; $user = getenv('DB_USER') ?: 'root';
$password = ''; $password = getenv('DB_PASS') ?: (getenv('DB_PASSWORD') ?: '');
$database = 'webgis'; $database = getenv('DB_NAME') ?: 'webgis';
$port = (int)(getenv('DB_PORT') ?: 3306);
$conn = new mysqli($host, $user, $password, $database);
$conn = new mysqli($host, $user, $password, $database, $port);
if ($conn->connect_error) {
die(json_encode(['status' => 'error', 'msg' => 'Koneksi database gagal: ' . $conn->connect_error])); if ($conn->connect_error) {
} http_response_code(500);
die(json_encode([
// Set charset ke UTF-8 'status' => 'error',
$conn->set_charset("utf8"); 'msg' => 'Koneksi database gagal: ' . $conn->connect_error
?> ]));
}
$conn->set_charset('utf8mb4');
?>