348 lines
20 KiB
PHP
348 lines
20 KiB
PHP
<?php
|
|
include 'koneksi.php';
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="id">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>WebGIS Pro - Pontianak</title>
|
|
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
|
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css"/>
|
|
|
|
<style>
|
|
body { font-family: 'Plus Jakarta Sans', sans-serif; background-color: #0b132b; margin: 0; overflow: hidden; color: #fff; }
|
|
#map { height: 100vh; width: 100%; z-index: 1; }
|
|
|
|
/* --- SIDEBAR MENU MODERN DARK BLUE --- */
|
|
.sidebar-menu {
|
|
position: absolute; top: 20px; left: 20px;
|
|
z-index: 1000; width: 260px;
|
|
background: rgba(14, 23, 44, 0.85);
|
|
backdrop-filter: blur(15px);
|
|
-webkit-backdrop-filter: blur(15px);
|
|
padding: 24px; border-radius: 20px;
|
|
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.6);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
}
|
|
.sidebar-title { font-size: 0.75rem; font-weight: 800; color: #5bc0be; text-transform: uppercase; letter-spacing: 2px; text-shadow: 0 0 10px rgba(91,192,190,0.3); cursor: pointer; display: flex; justify-content: space-between; align-items: center; margin-bottom: 0; transition: color 0.3s;}
|
|
.sidebar-title:hover { color: #6fffe9; }
|
|
.btn-mode-container { display: flex; flex-direction: column; gap: 12px; margin-top: 20px; }
|
|
|
|
.sidebar-menu.collapsed .btn-mode-container { display: none; margin-top: 0; }
|
|
#sidebar-icon { transition: transform 0.3s; font-size: 1rem;}
|
|
.sidebar-menu:not(.collapsed) #sidebar-icon { transform: rotate(180deg); }
|
|
|
|
.btn-mode {
|
|
border-radius: 12px; border: 1px solid rgba(255,255,255,0.05); padding: 14px 18px;
|
|
font-weight: 600; font-size: 0.9rem; text-align: left;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); color: #e0e1dd; background: rgba(255,255,255,0.03);
|
|
display: flex; align-items: center; gap: 14px; cursor: pointer;
|
|
}
|
|
.btn-mode i { font-size: 1.2rem; transition: transform 0.3s ease; }
|
|
.btn-mode:hover { background: rgba(58, 80, 107, 0.4); transform: translateX(5px); border-color: rgba(91, 192, 190, 0.3); }
|
|
.btn-mode:hover i { transform: scale(1.1); }
|
|
.btn-mode.active { background: linear-gradient(135deg, #1c2541, #3a506b); color: #ffffff; border-color: #5bc0be; box-shadow: 0 4px 15px rgba(91, 192, 190, 0.4); }
|
|
.btn-mode.active i { color: #6fffe9 !important; }
|
|
|
|
/* --- POPUP STYLING DARK THEME --- */
|
|
.leaflet-popup-content-wrapper { background: rgba(20, 30, 56, 0.95); backdrop-filter: blur(15px); border: 1px solid rgba(255,255,255,0.1); border-radius: 16px; padding: 0; overflow: hidden; box-shadow: 0 15px 35px rgba(0,0,0,0.5) !important; color: #fff;}
|
|
.leaflet-popup-tip { background: rgba(20, 30, 56, 0.95) !important; border: 1px solid rgba(255,255,255,0.1); border-top: none; border-left: none; box-shadow: 15px 15px 35px rgba(0,0,0,0.5); }
|
|
.leaflet-popup-content { margin: 0 !important; width: 320px !important; }
|
|
|
|
.leaflet-popup-close-button {
|
|
color: #fff !important; top: 12px !important; right: 12px !important;
|
|
font-size: 20px !important; font-weight: bold; z-index: 1002; transition: color 0.3s;
|
|
}
|
|
.leaflet-popup-close-button:hover { color: #5bc0be !important; }
|
|
|
|
.popup-header { background: linear-gradient(135deg, #0b132b, #1c2541); border-bottom: 1px solid rgba(255,255,255,0.1); color: #6fffe9; padding: 18px 45px 18px 20px; font-weight: 800; font-size: 0.85rem; text-transform: uppercase; position: relative; letter-spacing: 1px; }
|
|
.popup-body { padding: 20px; background: transparent; }
|
|
|
|
.form-label { font-size: 0.7rem; font-weight: 700; color: #5bc0be; text-transform: uppercase; margin-bottom: 8px; display: block; letter-spacing: 1px; }
|
|
.form-control-modern { border: 1px solid rgba(255,255,255,0.1); background: rgba(0,0,0,0.2); border-radius: 10px; padding: 12px; font-size: 0.85rem; margin-bottom: 16px; width: 100%; color: #fff; transition: border-color 0.3s; }
|
|
.form-control-modern:focus { outline: none; border-color: #5bc0be; background: rgba(0,0,0,0.4); }
|
|
.form-control-modern::placeholder { color: rgba(255,255,255,0.4); }
|
|
.form-control-modern.bg-light { background: rgba(255,255,255,0.05) !important; color: #a0aab2; border-color: rgba(255,255,255,0.05); }
|
|
|
|
/* SPBU Status Toggle */
|
|
.status-toggle { display: flex; gap: 10px; margin-bottom: 18px; }
|
|
.status-toggle input { display: none; }
|
|
.status-toggle label { flex: 1; padding: 12px; text-align: center; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); border-radius: 10px; cursor: pointer; font-size: 0.75rem; font-weight: 700; color: #a0aab2; transition: all 0.3s; }
|
|
.status-toggle input:checked + label.lab-ya { background: rgba(91, 192, 190, 0.2); color: #6fffe9; border-color: #5bc0be; box-shadow: 0 4px 10px rgba(91, 192, 190, 0.1); }
|
|
.status-toggle input:checked + label.lab-tidak { background: rgba(220, 53, 69, 0.2); color: #ff6b6b; border-color: #dc3545; box-shadow: 0 4px 10px rgba(220, 53, 69, 0.1); }
|
|
|
|
.btn-wa { background: linear-gradient(135deg, #1c2541, #3a506b); color: #6fffe9 !important; border: 1px solid #5bc0be; border-radius: 12px; font-weight: 700; padding: 12px; text-decoration: none; display: flex; align-items: center; justify-content: center; width: 100%; transition: all 0.3s; }
|
|
.btn-wa:hover { background: #5bc0be; color: #0b132b !important; box-shadow: 0 5px 15px rgba(91,192,190,0.3); }
|
|
.btn-delete-icon { width: 48px; height: 48px; background: rgba(220,53,69,0.1); border: 1px solid #dc3545; color: #ff6b6b; border-radius: 12px; display: flex; align-items: center; justify-content: center; text-decoration: none; transition: all 0.3s; }
|
|
.btn-delete-icon:hover { background: #dc3545; color: white; box-shadow: 0 5px 15px rgba(220,53,69,0.3); }
|
|
|
|
.btn-primary { background: linear-gradient(135deg, #5bc0be, #3a506b) !important; border: none !important; color: white !important; transition: transform 0.3s, box-shadow 0.3s; }
|
|
.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(91,192,190,0.4); }
|
|
.btn-success { background: linear-gradient(135deg, #27ae60, #1e8449) !important; border: none !important; color: white !important; transition: transform 0.3s, box-shadow 0.3s; }
|
|
.btn-success:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(39,174,96,0.4); }
|
|
.btn-danger { background: linear-gradient(135deg, #dc3545, #900a18) !important; border: none !important; color: white !important; transition: transform 0.3s, box-shadow 0.3s; }
|
|
.btn-danger:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(220,53,69,0.4); }
|
|
|
|
.badge.bg-primary { background: rgba(91, 192, 190, 0.2) !important; color: #6fffe9 !important; border: 1px solid #5bc0be; }
|
|
.badge.bg-success { background: rgba(39, 174, 96, 0.2) !important; color: #2ecc71 !important; border: 1px solid #27ae60; }
|
|
.badge.bg-danger { background: rgba(220, 53, 69, 0.2) !important; color: #ff6b6b !important; border: 1px solid #dc3545; }
|
|
|
|
.text-primary { color: #5bc0be !important; }
|
|
.text-success { color: #6fffe9 !important; }
|
|
.text-danger { color: #ff6b6b !important; }
|
|
|
|
select option { background: #0b132b; color: #fff; }
|
|
h6 { color: #fff; }
|
|
.text-dark-blue { color: #5bc0be; }
|
|
.small { color: #a0aab2; }
|
|
|
|
/* Layers Control Styling */
|
|
.leaflet-control-layers {
|
|
background: rgba(14, 23, 44, 0.85) !important;
|
|
backdrop-filter: blur(15px);
|
|
border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
|
border-radius: 12px !important;
|
|
color: #e0e1dd;
|
|
padding: 10px;
|
|
}
|
|
.leaflet-control-layers label {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 0.85rem;
|
|
margin-bottom: 5px;
|
|
cursor: pointer;
|
|
}
|
|
.leaflet-control-layers-separator {
|
|
border-top: 1px solid rgba(255, 255, 255, 0.1) !important;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="sidebar-menu collapsed" id="sidebar-menu">
|
|
<div class="sidebar-title" onclick="toggleSidebar()">
|
|
<span><i class="bi bi-list"></i> Menu Navigasi</span>
|
|
<i class="bi bi-chevron-down" id="sidebar-icon"></i>
|
|
</div>
|
|
<div class="btn-mode-container">
|
|
<button class="btn-mode active" id="mode-view" onclick="changeMode('view')">
|
|
<i class="bi bi-layers-half"></i> Mode Lihat
|
|
</button>
|
|
<button class="btn-mode" id="mode-spbu" onclick="changeMode('spbu')">
|
|
<i class="bi bi-geo-alt-fill text-success"></i> Kelola SPBU
|
|
</button>
|
|
<button class="btn-mode" id="mode-jalan" onclick="changeMode('jalan')">
|
|
<i class="bi bi-slash-lg text-danger"></i> Kelola Jalan
|
|
</button>
|
|
<button class="btn-mode" id="mode-parsil" onclick="changeMode('parsil')">
|
|
<i class="bi bi-square-fill text-primary"></i> Kelola Tanah
|
|
</button>
|
|
</div>
|
|
</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.rawgit.com/makinacorpus/Leaflet.GeometryUtil/master/dist/leaflet.geometryutil.js"></script>
|
|
|
|
<script>
|
|
var currentMode = 'view';
|
|
var map = L.map('map', { zoomControl: false }).setView([-0.0263, 109.3425], 13);
|
|
L.control.zoom({ position: 'bottomright' }).addTo(map);
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
|
|
|
|
var drawnItems = new L.FeatureGroup().addTo(map);
|
|
var drawJalan = new L.Draw.Polyline(map, { shapeOptions: { color: '#dc3545', weight: 5 } });
|
|
var drawParsil = new L.Draw.Polygon(map, { shapeOptions: { color: '#198754', fillOpacity: 0.4 } });
|
|
|
|
function toggleSidebar() {
|
|
document.getElementById('sidebar-menu').classList.toggle('collapsed');
|
|
}
|
|
|
|
function changeMode(mode) {
|
|
currentMode = mode;
|
|
document.querySelectorAll('.btn-mode').forEach(b => b.classList.remove('active'));
|
|
document.getElementById('mode-' + mode).classList.add('active');
|
|
|
|
drawJalan.disable();
|
|
drawParsil.disable();
|
|
|
|
if(mode === 'jalan') drawJalan.enable();
|
|
if(mode === 'parsil') drawParsil.enable();
|
|
}
|
|
|
|
// --- READ DATA ---
|
|
const colorJ = (s) => s=='Nasional'?'#ff0000':s=='Provinsi'?'#ffae00':'#0000ff';
|
|
const colorP = (s) => s=='SHM'?'#27ae60':s=='HGB'?'#f1c40f':s=='HGU'?'#e67e22':'#9b59b6';
|
|
|
|
// Load Jalan
|
|
<?php
|
|
$qJ = mysqli_query($conn, "SELECT * FROM jalan");
|
|
while($j = mysqli_fetch_assoc($qJ)) { ?>
|
|
L.geoJSON(<?= $j['geojson'] ?>, {
|
|
style: { color: colorJ('<?= $j['status_jalan'] ?>'), weight: 5 }
|
|
}).bindPopup(`
|
|
<div class="popup-header">DATA JALAN</div>
|
|
<div class="popup-body">
|
|
<h6 class="fw-bold mb-1"><?= $j['nama_jalan'] ?></h6>
|
|
<span class="badge bg-primary mb-2"><?= $j['status_jalan'] ?></span>
|
|
<p class="small mb-2">Panjang: <?= $j['panjang_m'] ?> m</p>
|
|
<a href="hapus_jalan.php?id=<?= $j['id'] ?>" class="btn btn-danger btn-sm w-100" onclick="return confirm('Hapus?')">HAPUS</a>
|
|
</div>
|
|
`).addTo(map);
|
|
<?php } ?>
|
|
|
|
// Load Parsil
|
|
<?php
|
|
$qP = mysqli_query($conn, "SELECT * FROM parsil");
|
|
while($p = mysqli_fetch_assoc($qP)) { ?>
|
|
L.geoJSON(<?= $p['geojson'] ?>, {
|
|
style: { color: colorP('<?= $p['status_milik'] ?>'), fillOpacity: 0.5 }
|
|
}).bindPopup(`
|
|
<div class="popup-header">DATA TANAH</div>
|
|
<div class="popup-body">
|
|
<h6 class="fw-bold mb-1">ID: <?= $p['id_parsil'] ?></h6>
|
|
<span class="badge bg-success mb-2"><?= $p['status_milik'] ?></span>
|
|
<p class="small mb-2">Luas: <?= $p['luas_m2'] ?> m²</p>
|
|
<a href="hapus_parsil.php?id=<?= $p['id'] ?>" class="btn btn-danger btn-sm w-100" onclick="return confirm('Hapus?')">HAPUS</a>
|
|
</div>
|
|
`).addTo(map);
|
|
<?php } ?>
|
|
|
|
// --- LAYER GROUPS SPBU ---
|
|
var spbu24Jam = L.layerGroup().addTo(map);
|
|
var spbuTidak24Jam = L.layerGroup().addTo(map);
|
|
|
|
var overlays = {
|
|
"<span style='color:#2ecc71; font-weight:bold;'><i class='bi bi-geo-alt-fill'></i> SPBU Penuh (24 Jam)</span>": spbu24Jam,
|
|
"<span style='color:#ff6b6b; font-weight:bold;'><i class='bi bi-geo-alt-fill'></i> SPBU Terbatas</span>": spbuTidak24Jam
|
|
};
|
|
L.control.layers(null, overlays, {collapsed: false, position: 'topright'}).addTo(map);
|
|
|
|
// Load SPBU dengan Fitur Drag (Geser) Aktif
|
|
<?php
|
|
$qS = mysqli_query($conn, "SELECT * FROM spbu");
|
|
while($s = mysqli_fetch_assoc($qS)) { ?>
|
|
var marker = L.marker([<?= $s['latitude'] ?>, <?= $s['longitude'] ?>], {
|
|
// Aktifkan draggable agar bisa digeser
|
|
draggable: true,
|
|
icon: L.icon({
|
|
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-<?= $s['is_24jam']=='Ya'?'green':'red' ?>.png',
|
|
iconSize: [25, 41],
|
|
iconAnchor: [12, 41],
|
|
popupAnchor: [1, -34]
|
|
})
|
|
});
|
|
|
|
// Masukkan marker ke dalam layer group yang sesuai
|
|
<?php if ($s['is_24jam'] == 'Ya') { ?>
|
|
marker.addTo(spbu24Jam);
|
|
<?php } else { ?>
|
|
marker.addTo(spbuTidak24Jam);
|
|
<?php } ?>
|
|
|
|
// Konten Popup (Tetap sama)
|
|
marker.bindPopup(`
|
|
<div class="popup-header">INFORMASI SPBU</div>
|
|
<div class="popup-body">
|
|
<h6 class="fw-bold mb-1"><?= strtoupper($s['nama_spbu']) ?></h6>
|
|
<span class="badge <?= $s['is_24jam']=='Ya'?'bg-success':'bg-danger' ?> w-100 mb-3 py-2"><?= $s['is_24jam']=='Ya'?'24 JAM':'TERBATAS' ?></span>
|
|
<div class="d-flex gap-2">
|
|
<a href="https://wa.me/<?= preg_replace('/[^0-9]/','',$s['no_wa']) ?>" target="_blank" class="btn-wa">WHATSAPP</a>
|
|
<a href="hapus_spbu.php?id=<?= $s['id'] ?>" class="btn-delete-icon" onclick="return confirm('Hapus?')"><i class="bi bi-trash3-fill"></i></a>
|
|
</div>
|
|
</div>
|
|
`);
|
|
|
|
// --- FUNGSI UPDATE POSISI SAAT DIGESER ---
|
|
marker.on('dragend', function(e) {
|
|
var latlng = e.target.getLatLng();
|
|
var formData = new FormData();
|
|
formData.append('id', "<?= $s['id'] ?>");
|
|
formData.append('lat', latlng.lat);
|
|
formData.append('lng', latlng.lng);
|
|
|
|
// Kirim perubahan ke file update_posisi.php
|
|
fetch('update_posisi.php', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(response => response.text())
|
|
.then(data => {
|
|
console.log('Posisi diperbarui:', latlng.lat, latlng.lng);
|
|
})
|
|
.catch(error => console.error('Gagal update:', error));
|
|
});
|
|
<?php } ?>
|
|
|
|
// --- CREATE DATA ---
|
|
|
|
map.on('click', function(e) {
|
|
if(currentMode !== 'spbu') return;
|
|
var content = `
|
|
<div class="popup-header">TAMBAH SPBU</div>
|
|
<div class="popup-body">
|
|
<form action="simpan_spbu.php" method="POST">
|
|
<label class="form-label">Nama SPBU</label><input type="text" name="nama" class="form-control-modern" required>
|
|
<label class="form-label">WhatsApp</label><input type="text" name="wa" class="form-control-modern" required>
|
|
<label class="form-label">Status</label>
|
|
<div class="status-toggle">
|
|
<input type="radio" name="aktif" id="ya" value="Ya" checked><label for="ya" class="lab-ya">24 Jam</label>
|
|
<input type="radio" name="aktif" id="tidak" value="Tidak"><label for="tidak" class="lab-tidak">Terbatas</label>
|
|
</div>
|
|
<input type="hidden" name="lat" value="${e.latlng.lat}"><input type="hidden" name="lng" value="${e.latlng.lng}">
|
|
<button type="submit" class="btn btn-primary w-100 py-2 fw-bold" style="border-radius:10px;">SIMPAN</button>
|
|
</form>
|
|
</div>`;
|
|
L.popup().setLatLng(e.latlng).setContent(content).openOn(map);
|
|
});
|
|
|
|
map.on(L.Draw.Event.CREATED, function (e) {
|
|
var layer = e.layer;
|
|
var type = e.layerType;
|
|
var geojson = JSON.stringify(layer.toGeoJSON().geometry);
|
|
drawnItems.addLayer(layer);
|
|
|
|
if (type === 'polyline') {
|
|
var d = 0, ll = layer.getLatLngs();
|
|
for (var i=0; i<ll.length-1; i++) d += ll[i].distanceTo(ll[i+1]);
|
|
var popup = `
|
|
<div class="popup-header">SIMPAN JALAN BARU</div>
|
|
<div class="popup-body">
|
|
<form action="simpan_jalan.php" method="POST">
|
|
<label class="form-label">Nama Jalan</label><input type="text" name="nama_jalan" class="form-control-modern" required>
|
|
<label class="form-label">Status</label>
|
|
<select name="status" class="form-control-modern"><option value="Nasional">Nasional</option><option value="Provinsi">Provinsi</option><option value="Kabupaten">Kabupaten</option></select>
|
|
<label class="form-label">Panjang (m)</label><input type="text" name="panjang" value="${d.toFixed(2)}" class="form-control-modern bg-light" readonly>
|
|
<input type="hidden" name="geojson" value='${geojson}'>
|
|
<button type="submit" class="btn btn-primary w-100 py-2 fw-bold">SIMPAN JALAN</button>
|
|
</form>
|
|
</div>`;
|
|
layer.bindPopup(popup, {minWidth: 300}).openPopup();
|
|
} else if (type === 'polygon') {
|
|
var area = L.GeometryUtil.geodesicArea(layer.getLatLngs()[0]);
|
|
var popup = `
|
|
<div class="popup-header">SIMPAN TANAH BARU</div>
|
|
<div class="popup-body">
|
|
<form action="simpan_parsil.php" method="POST">
|
|
<label class="form-label">ID Parsil</label><input type="text" name="id_parsil" class="form-control-modern" required>
|
|
<label class="form-label">Jenis Hak</label>
|
|
<select name="status" class="form-control-modern"><option value="SHM">SHM</option><option value="HGB">HGB</option><option value="HGU">HGU</option><option value="HP">HP</option></select>
|
|
<label class="form-label">Luas (m²)</label><input type="text" name="luas" value="${area.toFixed(2)}" class="form-control-modern bg-light" readonly>
|
|
<input type="hidden" name="geojson" value='${geojson}'>
|
|
<button type="submit" class="btn btn-success w-100 py-2 fw-bold">SIMPAN TANAH</button>
|
|
</form>
|
|
</div>`;
|
|
layer.bindPopup(popup, {minWidth: 300}).openPopup();
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|