Project Webgis2 SIG

This commit is contained in:
2026-06-11 10:38:09 +07:00
commit eb4d6b7eb0
22 changed files with 2914 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
<?php
include 'koneksi.php';
$features = [];
// ====================
// IBADAH
// ====================
$q1 = $conn->query("SELECT * FROM ibadah");
while ($row = $q1->fetch_assoc()) {
$features[] = [
"type" => "Feature",
"properties" => [
"tipe" => "ibadah",
"id" => $row['id'],
"nama" => $row['nama'],
"jenis" => $row['jenis'],
"radius" => $row['radius']
],
"geometry" => [
"type" => "Point",
"coordinates" => [(float)$row['lng'], (float)$row['lat']]
]
];
}
// ====================
// WARGA
// ====================
$q2 = $conn->query("SELECT * FROM warga");
while ($row = $q2->fetch_assoc()) {
$features[] = [
"type" => "Feature",
"properties" => [
"tipe" => "warga",
"id" => $row['id'],
"nama" => $row['nama'],
"jumlah_kk" => $row['jumlah_kk'],
"pengurus" => $row['pengurus']
],
"geometry" => [
"type" => "Point",
"coordinates" => [(float)$row['lng'], (float)$row['lat']]
]
];
}
// output GeoJSON
echo json_encode([
"type" => "FeatureCollection",
"features" => $features
]);
?>
+7
View File
@@ -0,0 +1,7 @@
<?php
include 'koneksi.php';
$id = $_GET['id'];
$conn->query("DELETE FROM ibadah WHERE id=$id");
?>
+7
View File
@@ -0,0 +1,7 @@
<?php
include 'koneksi.php';
$id = $_GET['id'];
$conn->query("DELETE FROM warga WHERE id=$id");
?>
+7
View File
@@ -0,0 +1,7 @@
<?php
$conn = new mysqli("localhost", "root", "", "sig_pontianak");
if ($conn->connect_error) {
die("Koneksi gagal: " . $conn->connect_error);
}
?>
+20
View File
@@ -0,0 +1,20 @@
<?php
include 'koneksi.php';
$data = json_decode(file_get_contents("php://input"), true);
$nama = $data['nama'];
$jenis = $data['jenis'];
$lat = $data['lat'];
$lng = $data['lng'];
$radius = $data['radius'];
$sql = "INSERT INTO ibadah (nama, jenis, lat, lng, radius)
VALUES ('$nama', '$jenis', '$lat', '$lng', '$radius')";
$conn->query($sql);
echo json_encode([
"id" => $conn->insert_id
]);
?>
+20
View File
@@ -0,0 +1,20 @@
<?php
include 'koneksi.php';
$data = json_decode(file_get_contents("php://input"), true);
$nama = $data['nama'];
$jumlah = $data['jumlah'];
$lat = $data['lat'];
$lng = $data['lng'];
$pengurus = $data['pengurus'];
$sql = "INSERT INTO warga (nama, jumlah_kk, lat, lng, pengurus)
VALUES ('$nama', '$jumlah', '$lat', '$lng', '$pengurus')";
$conn->query($sql);
echo json_encode([
"id" => $conn->insert_id
]);
?>
+18
View File
@@ -0,0 +1,18 @@
<?php
include 'koneksi.php';
$data = json_decode(file_get_contents("php://input"), true);
$id = $data['id'];
$nama = $data['nama'];
$jenis = $data['jenis'];
$radius = $data['radius'];
$sql = "UPDATE ibadah
SET nama='$nama',
jenis='$jenis',
radius='$radius'
WHERE id=$id";
$conn->query($sql);
?>
+14
View File
@@ -0,0 +1,14 @@
<?php
include 'koneksi.php';
$data = json_decode(file_get_contents("php://input"), true);
$id = $data['id'];
$pengurus = $data['pengurus'];
$sql = "UPDATE warga
SET pengurus='$pengurus'
WHERE id=$id";
$conn->query($sql);
?>
+14
View File
@@ -0,0 +1,14 @@
<?php
include 'koneksi.php';
$data = json_decode(file_get_contents("php://input"), true);
$id = $data['id'];
$radius = $data['radius'];
$sql = "UPDATE ibadah
SET radius='$radius'
WHERE id=$id";
$conn->query($sql);
?>
+16
View File
@@ -0,0 +1,16 @@
<?php
include 'koneksi.php';
$data = json_decode(file_get_contents("php://input"), true);
$id = $data['id'];
$nama = $data['nama'];
$jumlah = $data['jumlah'];
$sql = "UPDATE ibadah
SET nama='$nama',
jumlah_kk='$jumlah',
WHERE id=$id";
$conn->query($sql);
?>
+311
View File
@@ -0,0 +1,311 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Choropleth Penduduk Pontianak</title>
<!-- Leaflet -->
<link rel="stylesheet"
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<style>
html, body {
margin: 0;
height: 100%;
}
#map {
width: 100%;
height: 100vh;
}
.info {
padding: 6px 8px;
font: 14px/16px Arial, Helvetica, sans-serif;
background: white;
background: rgba(255,255,255,0.9);
box-shadow: 0 0 15px rgba(0,0,0,0.2);
border-radius: 5px;
}
.info h4 {
margin: 0 0 5px;
color: #555;
}
.legend {
line-height: 18px;
color: #555;
}
.legend i {
width: 18px;
height: 18px;
float: left;
margin-right: 8px;
opacity: 0.7;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
const map = L.map('map').setView([-0.0263, 109.3425], 11);
/*
====================================================
INFO CONTROL
====================================================
*/
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; OpenStreetMap'
}).addTo(map);
const info = L.control();
info.onAdd = function () {
this._div = L.DomUtil.create('div', 'info');
this.update();
return this._div;
};
info.update = function (props) {
this._div.innerHTML =
'<h4>Populasi Penduduk Pontianak</h4>' +
(props
? '<b>' + props.NamaDaerah + '</b><br>' +
'Populasi: ' + props.Populasi + ' jiwa'
: 'Arahkan cursor ke wilayah');
};
info.addTo(map);
/*
====================================================
WARNA CHOROPLETH
====================================================
*/
function getColor(d) {
return d > 100000 ? '#800026' :
d > 80000 ? '#BD0026' :
d > 60000 ? '#E31A1C' :
d > 40000 ? '#FC4E2A' :
d > 20000 ? '#FD8D3C' :
'#FEB24C';
}
/*
====================================================
STYLE POLYGON
====================================================
*/
function style(feature) {
return {
weight: 2,
opacity: 1,
color: 'white',
dashArray: '3',
fillOpacity: 0.7,
fillColor: getColor(feature.properties.Populasi)
};
}
/*
====================================================
HIGHLIGHT
====================================================
*/
function highlightFeature(e) {
const layer = e.target;
layer.setStyle({
weight: 4,
color: '#666',
dashArray: '',
fillOpacity: 0.9
});
layer.bringToFront();
info.update(layer.feature.properties);
}
/*
====================================================
RESET HIGHLIGHT
====================================================
*/
let geojson;
function resetHighlight(e) {
geojson.resetStyle(e.target);
info.update();
}
/*
====================================================
ZOOM TO FEATURE
====================================================
*/
function zoomToFeature(e) {
map.fitBounds(e.target.getBounds());
}
/*
====================================================
EVENT PER FEATURE
====================================================
*/
function onEachFeature(feature, layer) {
layer.on({
mouseover: highlightFeature,
mouseout: resetHighlight,
click: zoomToFeature
});
layer.bindPopup(
'<b>' + feature.properties.NamaDaerah + '</b><br>' +
'Populasi: ' + feature.properties.Populasi + ' jiwa'
);
}
/*
====================================================
LOAD GEOJSON
====================================================
*/
fetch('choropleth.json')
.then(response => response.json())
.then(data => {
console.log(data.features[0].geometry);
data.features.forEach(feature => {
const coords = feature.geometry.coordinates;
/*
POLYGON
*/
if (feature.geometry.type === "Polygon") {
feature.geometry.coordinates = coords.map(ring => {
return ring.map(coord => {
const lng = 109.263 + (coord[0] / 52000);
const lat = 0.052 + (coord[1] / 52000);
return [lng, lat];
});
});
}
/*
MULTIPOLYGON
*/
else if (feature.geometry.type === "MultiPolygon") {
feature.geometry.coordinates = coords.map(poly => {
return poly.map(ring => {
return ring.map(coord => {
const lng = 109.263 + (coord[0] / 52000);
const lat = 0.052 + (coord[1] / 52000);
return [lng, lat];
});
});
});
}
});
geojson = L.geoJson(data, {
style,
onEachFeature
}).addTo(map);
map.fitBounds(geojson.getBounds());
})
.catch(error => {
console.error(error);
});
/*
====================================================
LEGENDA
====================================================
*/
const legend = L.control({ position: 'bottomright' });
legend.onAdd = function () {
const div = L.DomUtil.create('div', 'info legend');
const grades = [0, 20000, 40000, 60000, 80000, 100000];
let labels = [];
for (let i = 0; i < grades.length; i++) {
const from = grades[i];
const to = grades[i + 1];
labels.push(
'<i style="background:' +
getColor(from + 1) +
'"></i> ' +
from +
(to ? '&ndash;' + to : '+')
);
}
div.innerHTML = labels.join('<br>');
return div;
};
legend.addTo(map);
</script>
</body>
</html>
+8
View File
File diff suppressed because one or more lines are too long
+20
View File
@@ -0,0 +1,20 @@
CREATE DATABASE sig_pontianak;
USE sig_pontianak;
CREATE TABLE ibadah (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(100),
jenis VARCHAR(50),
lat DOUBLE,
lng DOUBLE,
radius INT
);
CREATE TABLE warga (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(100),
jumlah_kk INT,
lat DOUBLE,
lng DOUBLE,
pengurus VARCHAR(100)
);
+200
View File
@@ -0,0 +1,200 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0">
<title>WebGIS Portfolio</title>
<style>
body{
margin:0;
font-family:Arial,sans-serif;
background:#f4f6f9;
}
header{
background:#1565c0;
color:white;
padding:30px;
text-align:center;
}
.container{
width:90%;
max-width:1000px;
margin:auto;
padding:20px;
}
.card{
background:white;
border-radius:10px;
padding:20px;
margin-bottom:20px;
box-shadow:0 2px 8px rgba(0,0,0,.15);
}
.card h2{
margin-top:0;
color:#1565c0;
}
.btn{
display:inline-block;
text-decoration:none;
color:white;
background:#1565c0;
padding:10px 18px;
border-radius:6px;
margin-top:10px;
}
.btn:hover{
background:#0d47a1;
}
.list-link{
display:block;
margin:8px 0;
color:#1565c0;
text-decoration:none;
}
.list-link:hover{
text-decoration:underline;
}
footer{
text-align:center;
padding:20px;
color:#666;
}
</style>
</head>
<body>
<header>
<h1>
Sistem Informasi Geografis (SIG)
</h1>
<p>
Kumpulan Tugas dan Proyek WebGIS
</p>
</header>
<div class="container">
<div class="card">
<h2>
Proyek Utama
</h2>
<p>
Sistem Pendataan Warga Kurang Mampu dan
Rumah Ibadah berbasis WebGIS.
</p>
<a
class="btn"
href="pendataanwarga2.html">
Buka Sistem
</a>
</div>
<div class="card">
<h2>
Tugas Praktikum Mingguan
</h2>
<a
class="list-link"
href="pertemuan1.html">
Input dan Show data SPBU
</a>
<a
class="list-link"
href="pertemuan2.html">
Gambar dan Simpan Jalan
</a>
<a
class="list-link"
href="pertemuan3.html">
Gambar dan Simpan Tanah
</a>
<a
class="list-link"
href="pertemuan4.html">
Layer Control SPBU
</a>
<a
class="list-link"
href="choropleth.html">
Choropleth Penduduk Pontianak
</a>
</div>
<div class="card">
<h2>
Backend Deployment
</h2>
<ul>
<li>Folder API</li>
<li>Folder Database</li>
<li>Konfigurasi XAMPP</li>
</ul>
<p>
Digunakan untuk mendukung fitur backend
saat sistem dijalankan secara lokal maupun deployment.
</p>
</div>
</div>
<footer>
WebGIS Portfolio © 2026
</footer>
</body>
</html>
+339
View File
@@ -0,0 +1,339 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8" />
<title>Login SIG Pendataan Warga Kurang Mampu</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: Arial, sans-serif;
background: linear-gradient(135deg, #1a3a5c 0%, #2e6da4 50%, #4a9fd4 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.login-wrapper {
width: 100%;
max-width: 420px;
padding: 20px;
}
/* ── Header ── */
.login-header {
text-align: center;
margin-bottom: 28px;
color: white;
}
.login-header .icon {
font-size: 52px;
display: block;
margin-bottom: 10px;
filter: drop-shadow(0 2px 6px rgba(0,0,0,0.3));
}
.login-header h1 {
font-size: 20px;
font-weight: 700;
line-height: 1.3;
letter-spacing: 0.3px;
}
.login-header p {
font-size: 13px;
opacity: 0.85;
margin-top: 4px;
}
/* ── Card ── */
.login-card {
background: white;
border-radius: 14px;
padding: 32px 28px 28px;
box-shadow: 0 20px 60px rgba(0,0,0,0.25);
}
.login-card h2 {
font-size: 18px;
color: #1a3a5c;
margin-bottom: 22px;
text-align: center;
}
/* ── Form ── */
.form-group {
margin-bottom: 18px;
}
.form-group label {
display: block;
font-size: 13px;
font-weight: 600;
color: #444;
margin-bottom: 6px;
}
.form-group .input-wrap {
position: relative;
}
.form-group .input-icon {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
font-size: 16px;
color: #888;
pointer-events: none;
}
.form-group input {
width: 100%;
padding: 11px 12px 11px 38px;
border: 1.5px solid #ddd;
border-radius: 8px;
font-size: 14px;
color: #222;
transition: border-color 0.2s, box-shadow 0.2s;
outline: none;
}
.form-group input:focus {
border-color: #2e6da4;
box-shadow: 0 0 0 3px rgba(46,109,164,0.15);
}
/* ── Role tabs ── */
.role-tabs {
display: flex;
gap: 10px;
margin-bottom: 22px;
}
.role-tab {
flex: 1;
padding: 10px 8px;
border: 2px solid #ddd;
border-radius: 8px;
background: #f9f9f9;
cursor: pointer;
text-align: center;
font-size: 13px;
font-weight: 600;
color: #666;
transition: all 0.2s;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.role-tab .tab-icon { font-size: 22px; }
.role-tab.active {
border-color: #2e6da4;
background: #eaf2fb;
color: #1a3a5c;
}
.role-tab:hover:not(.active) {
border-color: #aac4e0;
background: #f4f8fd;
}
/* ── Button ── */
.btn-login {
width: 100%;
padding: 13px;
background: linear-gradient(135deg, #1a3a5c, #2e6da4);
color: white;
border: none;
border-radius: 8px;
font-size: 15px;
font-weight: 700;
cursor: pointer;
transition: opacity 0.2s, transform 0.1s;
letter-spacing: 0.5px;
}
.btn-login:hover { opacity: 0.92; }
.btn-login:active { transform: scale(0.99); }
/* ── Error ── */
.error-msg {
display: none;
background: #fff0f0;
border: 1px solid #f5c0c0;
color: #c0392b;
border-radius: 7px;
padding: 10px 14px;
font-size: 13px;
margin-bottom: 16px;
align-items: center;
gap: 8px;
}
.error-msg.show { display: flex; }
/* ── Demo hint ── */
.demo-hint {
margin-top: 20px;
background: #f4f8fd;
border: 1px solid #d0e4f5;
border-radius: 8px;
padding: 12px 14px;
font-size: 12px;
color: #555;
}
.demo-hint strong { color: #1a3a5c; }
.demo-hint table { width: 100%; border-collapse: collapse; margin-top: 6px; }
.demo-hint td { padding: 3px 6px; }
.demo-hint td:first-child { color: #888; width: 80px; }
.demo-hint td:last-child { font-family: monospace; color: #2e6da4; font-weight: bold; }
/* ── Footer ── */
.login-footer {
text-align: center;
margin-top: 18px;
font-size: 12px;
color: rgba(255,255,255,0.6);
}
@media (max-width: 480px) {
.login-card { padding: 24px 18px 20px; }
}
</style>
</head>
<body>
<div class="login-wrapper">
<!-- Header -->
<div class="login-header">
<span class="icon">🗺️</span>
<h1>SIG Pendataan Warga Kurang Mampu</h1>
<p>Kota Pontianak, Kalimantan Barat</p>
</div>
<!-- Card -->
<div class="login-card">
<h2>Masuk ke Sistem</h2>
<!-- Role selector -->
<div class="role-tabs">
<div class="role-tab active" id="tabAdmin" onclick="pilihRole('admin')">
<span class="tab-icon">🛡️</span>
Admin
</div>
<div class="role-tab" id="tabUser" onclick="pilihRole('user')">
<span class="tab-icon">👤</span>
Pengguna
</div>
</div>
<!-- Error -->
<div class="error-msg" id="errorMsg">
<span>⚠️</span>
<span id="errorText">Username atau password salah.</span>
</div>
<!-- Form -->
<div class="form-group">
<label for="username">Username</label>
<div class="input-wrap">
<span class="input-icon">👤</span>
<input type="text" id="username" placeholder="Masukkan username"
onkeydown="if(event.key==='Enter') doLogin()">
</div>
</div>
<div class="form-group">
<label for="password">Password</label>
<div class="input-wrap">
<span class="input-icon">🔒</span>
<input type="password" id="password" placeholder="Masukkan password"
onkeydown="if(event.key==='Enter') doLogin()">
</div>
</div>
<button class="btn-login" onclick="doLogin()">Masuk</button>
<!-- Demo credentials -->
<div class="demo-hint">
<strong>Akun Demo:</strong>
<table>
<tr><td>Admin</td><td>admin / admin123</td></tr>
<tr><td>Pengguna</td><td>user / user123</td></tr>
</table>
</div>
</div>
<div class="login-footer">
&copy; 2025 Sistem Informasi Geografis Pontianak
</div>
</div>
<script>
var roleAktif = 'admin';
function pilihRole(role) {
roleAktif = role;
document.getElementById('tabAdmin').classList.toggle('active', role === 'admin');
document.getElementById('tabUser').classList.toggle('active', role === 'user');
document.getElementById('errorMsg').classList.remove('show');
}
// ── Akun yang diizinkan ──────────────────────────────────────
// Pada implementasi nyata, validasi dilakukan di server (PHP+DB).
// Di sini validasi dilakukan di klien untuk keperluan demo/prototype.
var akunValid = {
admin: { username: 'admin', password: 'admin123' },
user: { username: 'user', password: 'user123' }
};
function doLogin() {
var username = document.getElementById('username').value.trim();
var password = document.getElementById('password').value;
var akun = akunValid[roleAktif];
var errEl = document.getElementById('errorMsg');
var errTxt = document.getElementById('errorText');
// Validasi kosong
if (!username || !password) {
errTxt.textContent = 'Username dan password wajib diisi.';
errEl.classList.add('show');
return;
}
// Validasi kredensial
if (username === akun.username && password === akun.password) {
// Simpan sesi ke sessionStorage
sessionStorage.setItem('sig_role', roleAktif);
sessionStorage.setItem('sig_username', username);
sessionStorage.setItem('sig_loggedin', '1');
// Redirect ke halaman utama
window.location.href = 'pendataanwarga2.html';
} else {
errTxt.textContent = 'Username atau password salah.';
errEl.classList.add('show');
document.getElementById('password').value = '';
document.getElementById('password').focus();
}
}
// Jika sudah login, langsung redirect
if (sessionStorage.getItem('sig_loggedin') === '1') {
window.location.href = 'pendataanwarga2.html';
}
</script>
</body>
</html>
+916
View File
@@ -0,0 +1,916 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8" />
<title>SIG Pendataan Warga Kurang Mampu - Pontianak</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"/>
<style>
* { box-sizing: border-box; }
body { margin: 0; font-family: Arial; }
#map { height: 100vh; }
/* ══════════════════════════════════════
TOPBAR
══════════════════════════════════════ */
#topbar {
position: absolute;
top: 0; left: 0; right: 0;
height: 46px;
background: linear-gradient(90deg, #1a3a5c, #2e6da4);
z-index: 2000;
display: flex;
align-items: center;
padding: 0 14px;
gap: 10px;
box-shadow: 0 2px 8px rgba(0,0,0,0.25);
}
#topbar .tb-logo { font-size: 20px; }
#topbar .tb-title {
color: white;
font-size: 14px;
font-weight: 700;
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#topbar .tb-badge {
background: rgba(255,255,255,0.18);
color: white;
font-size: 11px;
font-weight: 600;
padding: 3px 9px;
border-radius: 20px;
border: 1px solid rgba(255,255,255,0.3);
display: flex;
align-items: center;
gap: 4px;
white-space: nowrap;
}
#topbar .tb-badge.admin-badge { background: rgba(46,204,113,0.3); border-color: rgba(46,204,113,0.5); }
#topbar .tb-badge.user-badge { background: rgba(255,255,255,0.15); }
#topbar .tb-user {
color: rgba(255,255,255,0.85);
font-size: 12px;
white-space: nowrap;
}
#topbar .tb-logout {
background: rgba(255,255,255,0.15);
border: 1px solid rgba(255,255,255,0.3);
color: white;
padding: 5px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 12px;
font-weight: 600;
transition: background 0.2s;
white-space: nowrap;
}
#topbar .tb-logout:hover { background: rgba(255,255,255,0.28); }
/* ══════════════════════════════════════
INFO BANNER (khusus pengguna biasa)
══════════════════════════════════════ */
#infoBanner {
position: absolute;
top: 46px; left: 0; right: 0;
background: linear-gradient(90deg, #e8f4fd, #d0eaf8);
border-bottom: 1px solid #aed6f1;
padding: 7px 14px;
font-size: 12px;
color: #1a5276;
z-index: 1500;
display: none;
align-items: center;
gap: 8px;
}
/* ══════════════════════════════════════
PANELS — offset turun untuk topbar
══════════════════════════════════════ */
.panel-offset { top: 56px; } /* 46px topbar + 10px gap */
.control-box {
position: absolute;
top: 56px;
left: 50px;
background: white;
padding: 12px;
z-index: 1000;
border-radius: 8px;
width: 240px;
box-shadow: 0 0 5px rgba(0,0,0,0.3);
}
.control-box label {
font-size: 14px;
display: block;
margin-top: 5px;
}
/* ── readonly-mode: sembunyikan control box ── */
.control-box.readonly { display: none; }
/* ══════════════════════════════════════
TOMBOL TOGGLE — posisi tetap di kiri,
tidak ikut panel buka/tutup
══════════════════════════════════════ */
.toggle-btn {
position: absolute;
left: 8px;
width: 34px;
height: 34px;
background: white;
border: none;
border-radius: 6px;
box-shadow: 0 0 5px rgba(0,0,0,0.3);
cursor: pointer;
font-size: 17px;
z-index: 1100;
display: flex;
align-items: center;
justify-content: center;
}
.toggle-btn:hover { background: #f0f4f8; }
/* Posisi vertikal tiap tombol — Admin (ada control-box) */
#toggleStatsBtn { top: 172px; }
#toggleLayerBtn { top: 214px; }
#toggleHeatmapBtn { top: 256px; }
/* Posisi vertikal — Pengguna (tidak ada control-box) */
body.readonly-mode #toggleStatsBtn { top: 66px; }
body.readonly-mode #toggleLayerBtn { top: 108px; }
body.readonly-mode #toggleHeatmapBtn { top: 150px; }
/* ══════════════════════════════════════
PANEL — muncul di samping kanan tombol
══════════════════════════════════════ */
.panel-box {
position: absolute;
left: 50px;
background: white;
padding: 12px;
border-radius: 8px;
box-shadow: 0 0 5px rgba(0,0,0,0.3);
z-index: 1000;
transition: opacity 0.2s ease, visibility 0.2s ease;
}
.panel-box.hidden {
visibility: hidden;
opacity: 0;
pointer-events: none;
}
/* Ukuran & posisi per panel — Admin */
.stats-box { top: 166px; width: 240px; font-size: 14px; max-height: 300px; overflow-y: auto; }
.layer-box { top: 208px; width: 220px; }
.heatmap-box { top: 250px; width: 220px; }
/* Posisi panel — Pengguna */
body.readonly-mode .stats-box { top: 60px; }
body.readonly-mode .layer-box { top: 102px; }
body.readonly-mode .heatmap-box { top: 144px; }
/* Tooltip */
.toggle-btn .tooltip {
position: absolute;
left: 40px; top: 50%;
transform: translateY(-50%);
background: rgba(0,0,0,0.75);
color: white;
padding: 3px 8px;
border-radius: 4px;
font-size: 11px;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.15s;
}
.toggle-btn:hover .tooltip { opacity: 1; }
.popup-btn {
padding: 4px 10px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 12px;
font-weight: 600;
margin-top: 4px;
}
.popup-btn.edit { background: #eaf2fb; color: #1a3a5c; }
.popup-btn.hapus { background: #fdecea; color: #c0392b; }
.popup-btn:hover { opacity: 0.82; }
/* ── Readonly notice di popup ── */
.popup-readonly-note {
font-size: 11px;
color: #888;
font-style: italic;
margin-top: 6px;
}
@media (max-width: 768px) {
.stats-box, .layer-box { left: 10px; width: 220px; }
#topbar .tb-title { font-size: 12px; }
}
</style>
</head>
<body>
<!-- ══════════════════════════════════════
TOPBAR
══════════════════════════════════════ -->
<div id="topbar">
<span class="tb-logo">🗺️</span>
<span class="tb-title">SIG Pendataan Warga Kurang Mampu Pontianak</span>
<span class="tb-badge" id="roleBadge">&nbsp;</span>
<span class="tb-user" id="usernameLabel"></span>
<button class="tb-logout" onclick="doLogout()">🚪 Keluar</button>
</div>
<!-- ══════════════════════════════════════
INFO BANNER (Pengguna biasa)
══════════════════════════════════════ -->
<div id="infoBanner">
&nbsp;Anda masuk sebagai <strong>Pengguna</strong>. Anda hanya dapat melihat data. Hubungi Admin untuk mengubah data.
</div>
<!-- ══════════════════════════════════════
PANEL MODE INPUT (Admin only)
══════════════════════════════════════ -->
<div class="control-box" id="controlBox">
<b>Mode Input:</b>
<label><input type="radio" name="mode" value="none" checked> Tidak ada</label>
<label><input type="radio" name="mode" value="ibadah"> Rumah Ibadah</label>
<label><input type="radio" name="mode" value="warga"> Rumah Warga</label>
<hr>
</div>
<!-- ══════════════════════════════════════
TOMBOL TOGGLE — berdiri sendiri, posisi tetap
══════════════════════════════════════ -->
<button id="toggleStatsBtn" class="toggle-btn" onclick="toggleStatistik()" >📊<span class="tooltip">Statistik Analitis</span></button>
<button id="toggleLayerBtn" class="toggle-btn" onclick="toggleLayerBox()" >🗂️<span class="tooltip">Layer Control</span></button>
<button id="toggleHeatmapBtn" class="toggle-btn" onclick="toggleHeatmapBox()" >🔥<span class="tooltip">Heatmap</span></button>
<!-- ══════════════════════════════════════
PANEL STATISTIK
══════════════════════════════════════ -->
<div class="stats-box panel-box hidden" id="statsBox">
<b>STATISTIK ANALITIS</b>
<hr>
<div id="statistikContent">Memuat statistik...</div>
</div>
<!-- ══════════════════════════════════════
PANEL LAYER CONTROL
══════════════════════════════════════ -->
<div class="layer-box panel-box hidden" id="layerBox">
<b>LAYER CONTROL</b>
<hr>
<label>
<input type="checkbox" checked id="checkAllLayer" onchange="toggleSemuaLayer(this)">
Semua Layer
</label>
<hr>
<label><input type="checkbox" checked onchange="toggleLayer(this, layerMasjid)"> Masjid</label><br>
<label><input type="checkbox" checked onchange="toggleLayer(this, layerGereja)"> Gereja</label><br>
<label><input type="checkbox" checked onchange="toggleLayer(this, layerVihara)"> Vihara</label><br>
<label><input type="checkbox" checked onchange="toggleLayer(this, layerPura)"> Pura</label>
<hr>
<b>REKOMENDASI</b><br><br>
<label><input type="checkbox" checked onchange="toggleRekomendasi(this)"> Lokasi Baru</label>
<hr>
<label><input type="checkbox" checked onchange="toggleLayer(this, layerWargaCover)"> Warga Tercover</label><br>
<label><input type="checkbox" checked onchange="toggleLayer(this, layerWargaTidakCover)"> Warga Belum Tercover</label>
</div>
<!-- ══════════════════════════════════════
PANEL HEATMAP
══════════════════════════════════════ -->
<div class="heatmap-box panel-box hidden" id="heatmapBox">
<b>HEATMAP</b>
<hr>
<label><input type="radio" name="heatmapOption" onchange="pilihHeatmap('semua')"> Semua Warga</label><br>
<label><input type="radio" name="heatmapOption" onchange="pilihHeatmap('cover')"> Warga Tercover</label><br>
<label><input type="radio" name="heatmapOption" onchange="pilihHeatmap('tidakcover')"> Warga Belum Tercover</label><br><br>
<label><input type="radio" name="heatmapOption" onchange="pilihHeatmap('none')" checked> Nonaktifkan Heatmap</label>
</div>
<div id="map"></div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet.heat/dist/leaflet-heat.js"></script>
<script>
// ══════════════════════════════════════
// AUTENTIKASI — cek sesi
// ══════════════════════════════════════
var ROLE = sessionStorage.getItem('sig_role');
var USERNAME = sessionStorage.getItem('sig_username');
var LOGGEDIN = sessionStorage.getItem('sig_loggedin');
var IS_ADMIN = (ROLE === 'admin');
if (LOGGEDIN !== '1') {
// Belum login → redirect ke halaman login
window.location.href = 'login.html';
}
function doLogout() {
sessionStorage.clear();
window.location.href = 'login.html';
}
// ── Tampilkan info user di topbar ──────────────────────────────
document.getElementById('usernameLabel').textContent = USERNAME || '';
var badge = document.getElementById('roleBadge');
if (IS_ADMIN) {
badge.innerHTML = '🛡️ &nbsp;Admin';
badge.classList.add('admin-badge');
} else {
badge.innerHTML = '👤 &nbsp;Pengguna';
badge.classList.add('user-badge');
}
// ── Tampilkan info banner untuk pengguna biasa ─────────────────
if (!IS_ADMIN) {
document.getElementById('infoBanner').style.display = 'flex';
}
// ── Atur posisi panel (ada/tidaknya control-box) ───────────────
if (!IS_ADMIN) {
// Sembunyikan panel Mode Input
document.getElementById('controlBox').classList.add('readonly');
document.body.classList.add('readonly-mode');
// Geser panel lain ke atas
document.getElementById('statsBox').classList.add('readonly-pos');
document.getElementById('layerBox').classList.add('readonly-pos');
document.getElementById('heatmapBox').classList.add('readonly-pos');
}
// ══════════════════════════════════════
// INISIALISASI PETA
// ══════════════════════════════════════
var map = L.map('map').setView([-0.0263, 109.3425], 13);
var ibadahLayers = [];
var dataIbadah = [];
var dataWarga = [];
var heatmapLayer;
var wargaMarkers = {};
var layerRekomendasi = L.layerGroup().addTo(map);
var layerMasjid = L.layerGroup().addTo(map);
var layerGereja = L.layerGroup().addTo(map);
var layerVihara = L.layerGroup().addTo(map);
var layerPura = L.layerGroup().addTo(map);
var layerWargaCover = L.layerGroup().addTo(map);
var layerWargaTidakCover = L.layerGroup().addTo(map);
var heatmapSemua, heatmapCover, heatmapTidakCover;
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; OpenStreetMap'
}).addTo(map);
// ══════════════════════════════════════
// HELPER FUNGSI IKON
// ══════════════════════════════════════
function getMode() {
return document.querySelector('input[name="mode"]:checked').value;
}
function getColorByJenis(jenis) {
if (jenis === 'Masjid') return 'green';
if (jenis === 'Gereja') return 'blue';
if (jenis === 'Vihara') return 'orange';
if (jenis === 'Pura') return 'yellow';
return 'gray';
}
function getIconByJenis(jenis) {
return new L.Icon({
iconUrl: `https://maps.google.com/mapfiles/ms/icons/${getColorByJenis(jenis)}-dot.png`,
iconSize: [32, 32], iconAnchor: [16, 32]
});
}
function getWargaIcon(tercover = true) {
return new L.Icon({
iconUrl: `https://maps.google.com/mapfiles/ms/icons/${tercover ? 'red' : 'purple'}-dot.png`,
iconSize: [32, 32], iconAnchor: [16, 32]
});
}
// ══════════════════════════════════════
// POPUP BUILDER — konten beda per role
// ══════════════════════════════════════
function popupIbadah(nama, jenis, leafletId, radius, dbId) {
var sliderHtml = IS_ADMIN
? `<input type="range" min="100" max="2000" value="${radius}"
oninput="updateRadius(this.value, ${leafletId}, ${dbId})"><br><br>
<button class="popup-btn edit" onclick="editIbadah(${dbId}, ${JSON.stringify(nama)}, ${JSON.stringify(jenis)}, ${radius})">✏️ Edit</button>
<button class="popup-btn hapus" onclick="hapusIbadah(${dbId})">🗑 Hapus</button>`
: `<em class="popup-readonly-note">🔒 Hanya Admin yang dapat mengubah data</em>`;
return `<b>${nama}</b><br>
Jenis: ${jenis}<br>
Radius: <span id="rVal-${leafletId}">${radius}</span> m<br>
${sliderHtml}`;
}
function popupWarga(nama, jumlah, pengurus, tercover, dbId) {
var aksiHtml = IS_ADMIN
? `<button class="popup-btn edit" onclick="editWarga(${dbId}, '${nama.replace(/'/g,"\\'")}', ${jumlah})">✏️ Edit</button>
<button class="popup-btn hapus" onclick="hapusWarga(${dbId})">🗑 Hapus</button>`
: `<em class="popup-readonly-note">🔒 Hanya Admin yang dapat mengubah data</em>`;
return `<b>Warga Kurang Mampu</b><br>
Nama KK: ${nama}<br>
Jumlah KK: ${jumlah}<br>
Pengurus: ${pengurus}
${!tercover ? "<br><b style='color:red'>⚠ Belum Tercover</b>" : ''}<br><br>
${aksiHtml}`;
}
// ══════════════════════════════════════
// KLIK PETA — hanya Admin
// ══════════════════════════════════════
map.on('click', function(e) {
if (!IS_ADMIN) return; // Pengguna biasa tidak bisa klik input
var mode = getMode();
if (mode === 'none') { alert('Pilih mode input terlebih dahulu!'); return; }
if (mode === 'ibadah') {
L.popup().setLatLng(e.latlng).setContent(`
<b>Input Rumah Ibadah</b><br>
Nama:<br><input type="text" id="namaIbadah"><br><br>
Jenis:<br>
<select id="popupJenis">
<option value="Masjid">Masjid</option>
<option value="Gereja">Gereja</option>
<option value="Vihara">Vihara</option>
<option value="Pura">Pura</option>
</select><br><br>
<button onclick="simpanIbadah(${e.latlng.lat}, ${e.latlng.lng})">Simpan</button>
`).openOn(map);
}
if (mode === 'warga') {
L.popup().setLatLng(e.latlng).setContent(`
<b>Input Data Warga</b><br>
Nama Kepala KK:<br><input type="text" id="namaKK"><br><br>
Jumlah KK:<br><input type="number" id="jumlahKK"><br><br>
<button onclick="simpanWarga(${e.latlng.lat}, ${e.latlng.lng})">Simpan</button>
`).openOn(map);
}
});
// ══════════════════════════════════════
// SIMPAN IBADAH
// ══════════════════════════════════════
function simpanIbadah(lat, lng) {
var nama = document.getElementById('namaIbadah').value;
var jenis = document.getElementById('popupJenis').value;
if (!nama.trim()) { alert('Nama wajib diisi'); return; }
var color = getColorByJenis(jenis);
var icon = getIconByJenis(jenis);
var radius = 500;
var latlng = [lat, lng];
var targetLayer = { Masjid: layerMasjid, Gereja: layerGereja, Vihara: layerVihara, Pura: layerPura }[jenis] || layerMasjid;
var circle = L.circle(latlng, { radius, color, fillColor: color, fillOpacity: 0.3 }).addTo(targetLayer);
var marker = L.marker(latlng, { icon }).addTo(targetLayer);
var lid = circle._leaflet_id;
ibadahLayers[lid] = circle;
fetch('api/simpan_ibadah.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ nama, jenis, lat, lng, radius })
})
.then(r => r.json())
.then(result => {
marker.bindPopup(popupIbadah(nama, jenis, lid, radius, result.id)).openPopup();
});
dataIbadah.push({ nama, jenis, lat, lng, circle });
updateStatistik();
map.closePopup();
}
// ══════════════════════════════════════
// SIMPAN WARGA
// ══════════════════════════════════════
function simpanWarga(lat, lng) {
var nama = document.getElementById('namaKK').value;
var jumlah = document.getElementById('jumlahKK').value;
if (!nama.trim()) { alert('Nama wajib diisi'); return; }
if (!jumlah || jumlah <= 0) { alert('Jumlah KK tidak valid'); return; }
var pengurus = cariPengurus(lat, lng);
var infoPengurus = pengurus ? `${pengurus.nama} (${pengurus.jenis})` : 'Tidak tercover';
var tercover = cekTercover(lat, lng);
fetch('api/simpan_warga.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ nama, jumlah, lat, lng, pengurus: infoPengurus })
})
.then(r => r.json())
.then(result => {
var dbId = result.id;
dataWarga.push({ id: dbId, nama, jumlah, lat, lng });
updateStatistik();
buatRekomendasiLokasi();
buatHeatmap();
var targetLayer = tercover ? layerWargaCover : layerWargaTidakCover;
var marker = L.marker([lat, lng], { icon: getWargaIcon(tercover) }).addTo(targetLayer);
wargaMarkers[dbId] = marker;
marker.bindPopup(popupWarga(nama, jumlah, infoPengurus, tercover, dbId));
});
map.closePopup();
}
// ══════════════════════════════════════
// EDIT & HAPUS — hanya dipanggil admin
// ══════════════════════════════════════
function editWarga(id, namaLama, jumlahLama) {
if (!IS_ADMIN) return;
var namaBaru = prompt('Nama Kepala KK:', namaLama); if (!namaBaru) return;
var jumlahBaru = prompt('Jumlah KK:', jumlahLama); if (!jumlahBaru) return;
fetch('api/update_warga.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id, nama: namaBaru, jumlah: jumlahBaru })
}).then(() => { alert('Data warga berhasil diupdate'); location.reload(); });
}
function editIbadah(id, namaLama, jenisLama, radiusLama) {
if (!IS_ADMIN) return;
var namaBaru = prompt('Nama Rumah Ibadah:', namaLama); if (!namaBaru) return;
var jenisBaru = prompt('Jenis (Masjid/Gereja/Vihara/Pura):', jenisLama); if (!jenisBaru) return;
var radiusBaru = prompt('Radius:', radiusLama); if (!radiusBaru) return;
fetch('api/update_ibadah.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id, nama: namaBaru, jenis: jenisBaru, radius: radiusBaru })
}).then(() => { alert('Data rumah ibadah berhasil diupdate'); location.reload(); });
}
function hapusIbadah(id) {
if (!IS_ADMIN) return;
if (!confirm('Hapus data ini?')) return;
fetch('api/hapus_ibadah.php?id=' + id).then(() => location.reload());
}
function hapusWarga(id) {
if (!IS_ADMIN) return;
if (!confirm('Hapus data ini?')) return;
fetch('api/hapus_warga.php?id=' + id).then(() => location.reload());
}
// ══════════════════════════════════════
// UPDATE RADIUS — hanya admin
// ══════════════════════════════════════
function updateRadius(val, leafletId, dbId) {
if (!IS_ADMIN) return;
var circle = ibadahLayers[leafletId];
if (circle) circle.setRadius(Number(val));
var text = document.getElementById('rVal-' + leafletId);
if (text) text.innerHTML = val;
fetch('api/update_radius.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: dbId, radius: Number(val) })
});
updatePengurusWarga();
updateStatistik();
}
// ══════════════════════════════════════
// LOAD DATA (baca saja — semua role)
// ══════════════════════════════════════
window.onload = function() { loadData(); };
function loadData() {
fetch('api/get_data.php')
.then(r => r.json())
.then(data => {
L.geoJSON(data, {
pointToLayer: function(feature, latlng) {
if (feature.properties.tipe === 'ibadah') {
var jenis = feature.properties.jenis;
var color = getColorByJenis(jenis);
var targetLayer = { Masjid: layerMasjid, Gereja: layerGereja, Vihara: layerVihara, Pura: layerPura }[jenis] || layerMasjid;
var marker = L.marker(latlng, { icon: getIconByJenis(jenis) }).addTo(targetLayer);
var circle = L.circle(latlng, {
radius: feature.properties.radius,
color, fillColor: color, fillOpacity: 0.3
}).addTo(targetLayer);
var lid = circle._leaflet_id;
ibadahLayers[lid] = circle;
marker.bindPopup(
popupIbadah(
feature.properties.nama,
jenis,
lid,
feature.properties.radius,
feature.properties.id
)
);
dataIbadah.push({
nama: feature.properties.nama,
jenis,
lat: latlng.lat,
lng: latlng.lng,
circle
});
return marker;
}
if (feature.properties.tipe === 'warga') {
var tercover = feature.properties.pengurus !== 'Tidak tercover';
var targetLayer = tercover ? layerWargaCover : layerWargaTidakCover;
var marker = L.marker(latlng, { icon: getWargaIcon(tercover) }).addTo(targetLayer);
wargaMarkers[feature.properties.id] = marker;
marker.bindPopup(
popupWarga(
feature.properties.nama,
feature.properties.jumlah_kk,
feature.properties.pengurus,
tercover,
feature.properties.id
)
);
dataWarga.push({
id: feature.properties.id,
nama: feature.properties.nama,
jumlah: feature.properties.jumlah_kk,
lat: latlng.lat,
lng: latlng.lng
});
return marker;
}
}
}).addTo(map);
updateStatistik();
buatRekomendasiLokasi();
});
}
// ══════════════════════════════════════
// ANALISIS SPASIAL
// ══════════════════════════════════════
function cariPengurus(lat, lng) {
var terdekat = null, jarakMin = Infinity;
dataIbadah.forEach(item => {
if (!item.circle) return;
var jarak = map.distance([lat, lng], item.circle.getLatLng());
if (jarak <= item.circle.getRadius() && jarak < jarakMin) {
jarakMin = jarak; terdekat = item;
}
});
return terdekat;
}
function cekTercover(lat, lng) {
return dataIbadah.some(ibadah => {
if (!ibadah.circle) return false;
return map.distance([lat, lng], ibadah.circle.getLatLng()) <= ibadah.circle.getRadius();
});
}
function updatePengurusWarga() {
dataWarga.forEach(warga => {
var pengurusBaru = 'Tidak tercover';
dataIbadah.forEach(ibadah => {
if (!ibadah.circle) return;
var jarak = map.distance([warga.lat, warga.lng], ibadah.circle.getLatLng());
if (jarak <= ibadah.circle.getRadius()) {
pengurusBaru = `${ibadah.nama} (${ibadah.jenis})`;
}
});
fetch('api/update_pengurus.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: warga.id, pengurus: pengurusBaru })
});
var marker = wargaMarkers[warga.id];
if (!marker) return;
var tercover = pengurusBaru !== 'Tidak tercover';
marker.setIcon(getWargaIcon(tercover));
layerWargaCover.removeLayer(marker);
layerWargaTidakCover.removeLayer(marker);
(tercover ? layerWargaCover : layerWargaTidakCover).addLayer(marker);
marker.setPopupContent(
popupWarga(warga.nama, warga.jumlah, pengurusBaru, tercover, warga.id)
);
});
buatRekomendasiLokasi();
buatHeatmap();
}
// ══════════════════════════════════════
// STATISTIK
// ══════════════════════════════════════
function updateStatistik() {
var totalWarga = dataWarga.length;
var totalKK = dataWarga.reduce((s, w) => s + Number(w.jumlah || 0), 0);
var counts = { Masjid: 0, Gereja: 0, Vihara: 0, Pura: 0 };
dataIbadah.forEach(i => { if (counts[i.jenis] !== undefined) counts[i.jenis]++; });
var ranking = dataIbadah
.filter(i => i.circle)
.map(i => ({
nama: i.nama,
jenis: i.jenis,
total: dataWarga.filter(w => map.distance([w.lat, w.lng], i.circle.getLatLng()) <= i.circle.getRadius()).length
}))
.sort((a, b) => b.total - a.total);
var belum = dataWarga
.filter(w => !cekTercover(w.lat, w.lng))
.map(w => w.nama);
var html = `
<b>Total Warga Kurang Mampu:</b><br>${totalWarga}<br><br>
<b>Total KK:</b><br>${totalKK}<br><br>
<b>Total Rumah Ibadah:</b><br>
Masjid: ${counts.Masjid}<br>
Gereja: ${counts.Gereja}<br>
Vihara: ${counts.Vihara}<br>
Pura: ${counts.Pura}<br><br>
<b>Cakupan Warga Terbanyak:</b><br>
`;
ranking.forEach((item, i) => { html += `${i + 1}. ${item.nama} (${item.total} warga)<br>`; });
html += `<br><b>Warga Belum Tercover:</b><br>`;
html += belum.length === 0
? 'Semua warga sudah tercover'
: belum.map(n => `- ${n}`).join('<br>');
document.getElementById('statistikContent').innerHTML = html;
}
// ══════════════════════════════════════
// HEATMAP
// ══════════════════════════════════════
function buatHeatmap() {
var dataSemua = [], dataCover = [], dataTidakCover = [];
dataWarga.forEach(w => {
var pt = [w.lat, w.lng, (w.jumlah || 1) * 2];
dataSemua.push(pt);
(cekTercover(w.lat, w.lng) ? dataCover : dataTidakCover).push(pt);
});
var mk = (data, grad, r = 45, b = 18, mo = 0.5) =>
L.heatLayer(data, { radius: r, blur: b, maxZoom: 18, minOpacity: mo, gradient: grad });
[heatmapSemua, heatmapCover, heatmapTidakCover].forEach(h => { if (h) map.removeLayer(h); });
heatmapSemua = mk(dataSemua, { 0.2:'#d6ecff', 0.4:'#a8d8ff', 0.6:'#73c2fb', 0.8:'#3399ff', 1:'#0066cc' });
heatmapCover = mk(dataCover, { 0.2:'#b3ffb3', 0.4:'#66ff66', 0.6:'#00cc00', 0.8:'#009900', 1:'#006600' });
heatmapTidakCover = mk(dataTidakCover, { 0.2:'#ffd6d6', 0.4:'#ff8080', 0.6:'#ff3333', 0.8:'#e60000', 1:'#800000' }, 50, 20, 0.6);
}
function pilihHeatmap(jenis) {
buatHeatmap();
[heatmapSemua, heatmapCover, heatmapTidakCover].forEach(h => { if (h) map.removeLayer(h); });
var target = { semua: heatmapSemua, cover: heatmapCover, tidakcover: heatmapTidakCover }[jenis];
if (target) map.addLayer(target);
}
// ══════════════════════════════════════
// REKOMENDASI LOKASI
// ══════════════════════════════════════
function buatRekomendasiLokasi() {
layerRekomendasi.clearLayers();
var belum = dataWarga.filter(w => !cekTercover(w.lat, w.lng));
if (belum.length === 0) return;
var sumLat = belum.reduce((s, w) => s + w.lat, 0);
var sumLng = belum.reduce((s, w) => s + w.lng, 0);
var totalKK = belum.reduce((s, w) => s + (Number(w.jumlah) || 1), 0);
var centerLat = sumLat / belum.length;
var centerLng = sumLng / belum.length;
var rekIcon = new L.Icon({
iconUrl: 'https://maps.google.com/mapfiles/kml/paddle/wht-circle.png',
iconSize: [32, 32], iconAnchor: [16, 32]
});
L.marker([centerLat, centerLng], { icon: rekIcon })
.bindPopup(`<b>Rekomendasi Lokasi Baru</b><hr>
Jumlah Warga Belum Tercover: <b>${totalKK}</b><br><br>
Titik rekomendasi ini merupakan pusat area warga yang belum tercover rumah ibadah.`)
.addTo(layerRekomendasi);
}
// ══════════════════════════════════════
// TOGGLE PANEL
// ══════════════════════════════════════
function toggleStatistik() {
var s = document.getElementById('statsBox');
var l = document.getElementById('layerBox');
var h = document.getElementById('heatmapBox');
var isOpen = !s.classList.contains('hidden');
s.classList.toggle('hidden');
if (!isOpen) { l.classList.add('hidden'); h.classList.add('hidden'); }
}
function toggleLayerBox() {
var l = document.getElementById('layerBox');
var s = document.getElementById('statsBox');
var h = document.getElementById('heatmapBox');
var isOpen = !l.classList.contains('hidden');
l.classList.toggle('hidden');
if (!isOpen) { s.classList.add('hidden'); h.classList.add('hidden'); }
}
function toggleHeatmapBox() {
var h = document.getElementById('heatmapBox');
var s = document.getElementById('statsBox');
var l = document.getElementById('layerBox');
var isOpen = !h.classList.contains('hidden');
h.classList.toggle('hidden');
if (!isOpen) { s.classList.add('hidden'); l.classList.add('hidden'); }
}
function toggleLayer(checkbox, layer) {
checkbox.checked ? map.addLayer(layer) : map.removeLayer(layer);
}
function toggleSemuaLayer(checkbox) {
document.querySelectorAll('#layerBox input[type="checkbox"]').forEach(cb => cb.checked = checkbox.checked);
[layerMasjid, layerGereja, layerVihara, layerPura,
layerWargaCover, layerWargaTidakCover, layerRekomendasi,
heatmapSemua, heatmapCover, heatmapTidakCover]
.forEach(layer => { if (!layer) return; checkbox.checked ? map.addLayer(layer) : map.removeLayer(layer); });
}
function toggleRekomendasi(checkbox) {
checkbox.checked ? map.addLayer(layerRekomendasi) : map.removeLayer(layerRekomendasi);
}
</script>
</body>
</html>
+193
View File
@@ -0,0 +1,193 @@
<!DOCTYPE html>
<html lang="en">
<head>
<base target="_top">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Quick Start - Leaflet</title>
<link rel="shortcut icon" type="image/x-icon" href="docs/images/favicon.ico" />
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<style>
html, body {
height: 100%;
margin: 0;
}
.leaflet-container {
height: 400px;
width: 600px;
max-width: 100%;
max-height: 100%;
}
</style>
</head>
<body>
<div id="map" style="width: 1000px; height: 600px;"></div>
<script>
const map = L.map('map').setView([-0.059502, 109.3462785], 16);
const tiles = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
const iconDefault = new L.Icon.Default();
const iconHijau = L.icon({
iconUrl: "https://maps.google.com/mapfiles/ms/icons/green-dot.png",
iconSize: [25, 35],
iconAnchor: [12, 35]
});
const iconMerah = L.icon({
iconUrl: "https://maps.google.com/mapfiles/ms/icons/red-dot.png",
iconSize: [25, 35],
iconAnchor: [12, 35]
});
const marker = L.marker([-0.059502, 109.34626578]).addTo(map)
.bindPopup('<b>Hello world!</b><br />I am a popup.').openPopup();
const circle = L.circle([-0.059502, 109.346544546], {
color: 'red',
fillColor: '#f03',
fillOpacity: 0.5,
radius: 500
}).addTo(map).bindPopup('I am a circle.');
const polygon = L.polygon([
[-0.0595334, 109.346224],
[-0.0577872, 109.34999],
[-0.059872, 109.3476675]
]).addTo(map).bindPopup('I am a polygon.');
const popup = L.popup()
.setLatLng([-0.059502, 109.3462785])
.setContent('I am a standalone popup.')
.openOn(map);
function onMapClick(e) {
const lat = e.latlng.lat;
const lng = e.latlng.lng;
const form = `
<form onsubmit="simpanData(event, ${lat}, ${lng})">
<label>Nama:</label><br>
<input type="text" id="nama" required><br><br>
<label>Nomor:</label><br>
<input type="text" id="no"><br><br>
<label>Buka 24 Jam:</label><br>
<select id="24jam">
<option value="Ya">Ya</option>
<option value="Tidak">Tidak</option>
</select><br><br>
<button type="submit">Save</button>
</form>
`;
popup
.setLatLng(e.latlng)
.setContent(form)
.openOn(map);
}
map.on('click', onMapClick);
function simpanData(event, lat, lng) {
event.preventDefault();
const nama = document.getElementById("nama").value;
const nomor = document.getElementById("no").value;
const status = document.getElementById("24jam").value;
const dataBaru = { nama, nomor, status, lat, lng };
let data = JSON.parse(localStorage.getItem("titik")) || [];
data.push(dataBaru);
localStorage.setItem("titik", JSON.stringify(data));
alert("Data berhasil disimpan!");
map.closePopup();
}
let data = JSON.parse(localStorage.getItem("titik")) || [];
data.forEach((d, index) => {
let iconDipakai;
if (d.status === "Ya") {
iconDipakai = iconHijau;
} else {
iconDipakai = iconMerah;
}
const marker = L.marker([d.lat, d.lng], {
icon: iconDipakai,
draggable: true
}).addTo(map);
marker.on('dragend', function (e) {
const posisiBaru = marker.getLatLng();
updateKoordinat(index, posisiBaru.lat, posisiBaru.lng);
});
marker.bindPopup(`
<b>${d.nama}</b><br>
${d.nomor}<br>
Buka 24 jam?:
<span style="color:${d.status === 'Ya' ? 'green' : 'red'}">
${d.status}
</span><br>
<button onclick="hapusData(${index})">Hapus</button>
`);
});
function hapusData(index) {
let data = JSON.parse(localStorage.getItem("titik")) || [];
data.splice(index, 1);
localStorage.setItem("titik", JSON.stringify(data));
alert("Data berhasil dihapus!");
location.reload();
}
function updateKoordinat(index, latBaru, lngBaru) {
let data = JSON.parse(localStorage.getItem("titik")) || [];
data[index].lat = latBaru;
data[index].lng = lngBaru;
localStorage.setItem("titik", JSON.stringify(data));
alert("Koordinat berhasil diupdate!");
}
</script>
</body>
</html>
+236
View File
@@ -0,0 +1,236 @@
<!DOCTYPE html>
<html lang="en">
<head>
<base target="_top">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Quick Start - Leaflet</title>
<link rel="shortcut icon" type="image/x-icon" href="docs/images/favicon.ico" />
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<style>
html, body {
height: 100%;
margin: 0;
}
.leaflet-container {
height: 400px;
width: 600px;
max-width: 100%;
max-height: 100%;
}
</style>
</head>
<body>
<div id="control">
<button onclick="aktifkanMode()">Gambar Jalan</button>
<button onclick="simpanPolyline()">Simpan Jalan</button>
<button onclick="resetPolyline()">Reset</button>
</div>
<div id="map" style="width: 1000px; height: 600px;"></div>
<script>
const map = L.map('map').setView([-0.059502, 109.3462785], 16);
const tiles = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
let modeGambar = false;
let titikPolyline = [];
let polylineSementara = null;
let titikMarkers = [];
function aktifkanMode() {
modeGambar = true;
}
function resetPolyline() {
titikPolyline = [];
if (polylineSementara) {
map.removeLayer(polylineSementara);
polylineSementara = null;
}
titikMarkers.forEach(m => map.removeLayer(m));
titikMarkers = [];
}
const marker = L.marker([-0.059502, 109.34626578]).addTo(map)
.bindPopup('<b>Hello world!</b><br />I am a popup.').openPopup();
const circle = L.circle([-0.059502, 109.346544546], {
color: 'red',
fillColor: '#f03',
fillOpacity: 0.5,
radius: 500
}).addTo(map).bindPopup('I am a circle.');
const polygon = L.polygon([
[-0.0595334, 109.346224],
[-0.0577872, 109.34999],
[-0.059872, 109.3476675]
]).addTo(map).bindPopup('I am a polygon.');
const popup = L.popup()
.setLatLng([-0.059502, 109.3462785])
.setContent('I am a standalone popup.')
.openOn(map);
map.on('click', function(e) {
if (!modeGambar) return;
const latlng = e.latlng;
titikPolyline.push([latlng.lat, latlng.lng]);
const markerTitik = L.circleMarker([latlng.lat, latlng.lng], {
radius: 5,
color: 'black',
fillColor: 'yellow',
fillOpacity: 1
}).addTo(map);
titikMarkers.push(markerTitik);
if (polylineSementara) {
map.removeLayer(polylineSementara);
}
polylineSementara = L.polyline(titikPolyline, {
color: 'blue'
}).addTo(map);
});
function simpanPolyline() {
if (titikPolyline.length < 2) {
alert("Minimal 2 titik!");
return;
}
const form = `
<form onsubmit="prosesSimpanPolyline(event)">
<label>Nama Jalan:</label><br>
<input type="text" id="namaJalan" required><br><br>
<label>Status Jalan:</label><br>
<select id="statusJalan">
<option value="Nasional">Nasional</option>
<option value="Provinsi">Provinsi</option>
<option value="Kabupaten">Kabupaten</option>
</select><br><br>
<button type="submit">Simpan</button>
</form>
`;
L.popup()
.setLatLng(titikPolyline[titikPolyline.length - 1])
.setContent(form)
.openOn(map);
}
function hitungPanjang(koordinat) {
let total = 0;
for (let i = 0; i < koordinat.length - 1; i++) {
let p1 = L.latLng(koordinat[i][0], koordinat[i][1]);
let p2 = L.latLng(koordinat[i + 1][0], koordinat[i + 1][1]);
total += map.distance(p1, p2);
}
return total;
}
function prosesSimpanPolyline(event) {
event.preventDefault();
let nama = document.getElementById("namaJalan").value;
let status = document.getElementById("statusJalan").value;
let panjang = hitungPanjang(titikPolyline);
const dataBaru = {
nama: nama,
koordinat: titikPolyline,
status: status,
panjang: panjang
};
let data = JSON.parse(localStorage.getItem("jalan")) || [];
data.push(dataBaru);
localStorage.setItem("jalan", JSON.stringify(data));
alert("Jalan berhasil disimpan!");
map.closePopup();
resetPolyline();
modeGambar = false;
loadData();
}
function hapusJalan(index) {
let data = JSON.parse(localStorage.getItem("jalan")) || [];
data.splice(index, 1);
localStorage.setItem("jalan", JSON.stringify(data));
alert("Jalan dihapus!");
loadData();
}
function loadData() {
map.eachLayer(layer => {
if (layer instanceof L.Polyline && !(layer instanceof L.Polygon)) {
map.removeLayer(layer);
}
});
let dataJalan = JSON.parse(localStorage.getItem("jalan")) || [];
dataJalan.forEach((j, index) => {
let warna;
if (j.status === "Nasional") {
warna = "red";
} else if (j.status === "Provinsi") {
warna = "blue";
} else {
warna = "green";
}
let panjangM = j.panjang.toFixed(2);
L.polyline(j.koordinat, {
color: warna,
weight: 5
}).addTo(map).bindPopup(`
<b>${j.nama}</b><br>
<b>Status:</b> ${j.status}<br><br>
Panjang: ${panjangM} m<br><br>
<button onclick="hapusJalan(${index})">Hapus</button>
`);
});
}
loadData();
</script>
</body>
</html>
+245
View File
@@ -0,0 +1,245 @@
<!DOCTYPE html>
<html lang="en">
<head>
<base target="_top">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Quick Start - Leaflet</title>
<link rel="shortcut icon" type="image/x-icon" href="docs/images/favicon.ico" />
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet-draw/dist/leaflet.draw.css">
<script src="https://unpkg.com/leaflet-draw/dist/leaflet.draw.js"></script>
<style>
html, body {
height: 100%;
margin: 0;
}
.leaflet-container {
height: 400px;
width: 600px;
max-width: 100%;
max-height: 100%;
}
</style>
</head>
<body>
<div id="control">
<button onclick="aktifkanMode()">Gambar Tanah</button>
<button onclick="simpanPolygon()">Simpan Tanah</button>
<button onclick="resetPolygon()">Reset</button>
</div>
<div id="map" style="width: 1000px; height: 600px;"></div>
<script>
const map = L.map('map').setView([-0.059502, 109.3462785], 16);
const tiles = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
let modeGambar = false;
let titikPolygon = [];
let polygonSementara = null;
let titikMarkers = [];
let layerTanah = L.layerGroup().addTo(map);
function aktifkanMode() {
modeGambar = true;
}
function resetPolygon() {
titikPolygon = [];
if (polygonSementara) {
map.removeLayer(polygonSementara);
polygonSementara = null;
}
titikMarkers.forEach(m => map.removeLayer(m));
titikMarkers = [];
}
const marker = L.marker([-0.059502, 109.34626578]).addTo(map)
.bindPopup('<b>Hello world!</b><br />I am a popup.').openPopup();
const circle = L.circle([-0.059502, 109.346544546], {
color: 'red',
fillColor: '#f03',
fillOpacity: 0.5,
radius: 500
}).addTo(map).bindPopup('I am a circle.');
const polygon = L.polygon([
[-0.0595334, 109.346224],
[-0.0577872, 109.34999],
[-0.059872, 109.3476675]
]).addTo(map).bindPopup('I am a polygon.');
const popup = L.popup()
.setLatLng([-0.059502, 109.3462785])
.setContent('I am a standalone popup.')
.openOn(map);
map.on('click', function(e) {
if (!modeGambar) return;
const latlng = e.latlng;
titikPolygon.push([latlng.lat, latlng.lng]);
const markerTitik = L.circleMarker([latlng.lat, latlng.lng], {
radius: 5,
color: 'black',
fillColor: 'yellow',
fillOpacity: 1
}).addTo(map);
titikMarkers.push(markerTitik);
if (polygonSementara) {
map.removeLayer(polygonSementara);
}
polygonSementara = L.polygon(titikPolygon, {
color: 'blue',
fillOpacity: 0.3
}).addTo(map);
});
function simpanPolygon() {
if (titikPolygon.length < 3) {
alert("Minimal 3 titik!");
return;
}
const form = `
<form onsubmit="prosesSimpanPolygon(event)">
<label>Nama:</label><br>
<input type="text" id="namaTanah" required><br><br>
<label>Status Tanah:</label><br>
<select id="statusTanah">
<option value="SHM">Sertifikat Hak Milik (SHM)</option>
<option value="HGB">Sertifikat Hak Guna Bangunan (HGB)</option>
<option value="HGU">Sertifikat Hak Guna Usaha (HGU)</option>
<option value="HP">Sertifikat Hak Pakai (HP)</option>
</select><br><br>
<button type="submit">Simpan</button>
</form>
`;
L.popup()
.setLatLng(titikPolygon[titikPolygon.length - 1])
.setContent(form)
.openOn(map);
}
function hitungLuas(koordinat) {
return L.GeometryUtil.geodesicArea(
koordinat.map(k => L.latLng(k[0], k[1]))
);
}
function prosesSimpanPolygon(event) {
event.preventDefault();
try {
let nama = document.getElementById("namaTanah").value;
let status = document.getElementById("statusTanah").value;
let luas = hitungLuas(titikPolygon);
const dataBaru = {
nama: nama,
koordinat: [...titikPolygon],
status: status,
luas: luas
};
let data = JSON.parse(localStorage.getItem("tanah")) || [];
data.push(dataBaru);
localStorage.setItem("tanah", JSON.stringify(data));
alert("Tanah berhasil disimpan!");
map.closePopup();
resetPolygon();
modeGambar = false;
loadData();
} catch (error) {
console.error(error);
alert("Terjadi error: " + error.message);
}
}
function hapusTanah(index) {
let data = JSON.parse(localStorage.getItem("tanah")) || [];
data.splice(index, 1);
localStorage.setItem("tanah", JSON.stringify(data));
alert("Tanah dihapus!");
loadData();
}
function loadData() {
layerTanah.clearLayers();
let dataJalan = JSON.parse(localStorage.getItem("tanah")) || [];
dataJalan.forEach((t, index) => {
let warna;
if (t.status === "SHM") {
warna = "red";
} else if (t.status === "HGB") {
warna = "blue";
} else if (t.status === "HGU") {
warna = "green";
} else {
warna = "orange";
}
let luasM = t.luas.toLocaleString('id-ID', {
maximumFractionDigits: 2
});
let poly = L.polygon(t.koordinat, {
fillOpacity: 0.4,
color: warna
});
poly.bindPopup(`
<b>${t.nama}</b><br>
<b>Status:</b> ${t.status}<br><br>
Luas: ${luasM} m²<br><br>
<button onclick="hapusTanah(${index})">Hapus</button>
`);
layerTanah.addLayer(poly);
});
}
loadData();
</script>
</body>
</html>
+213
View File
@@ -0,0 +1,213 @@
<!DOCTYPE html>
<html lang="en">
<head>
<base target="_top">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Quick Start - Leaflet</title>
<link rel="shortcut icon" type="image/x-icon" href="docs/images/favicon.ico" />
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<style>
html, body {
height: 100%;
margin: 0;
}
.leaflet-container {
height: 400px;
width: 600px;
max-width: 100%;
max-height: 100%;
}
</style>
</head>
<body>
<div id="map" style="width: 1000px; height: 600px;"></div>
<script>
const map = L.map('map').setView([-0.059502, 109.3462785], 16);
// Base Maps
const osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; OpenStreetMap'
});
const satellite = L.tileLayer(
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
{
attribution: 'Tiles &copy; Esri'
}
);
// Layer default
osm.addTo(map);
const iconDefault = new L.Icon.Default();
const iconHijau = L.icon({
iconUrl: "https://maps.google.com/mapfiles/ms/icons/green-dot.png",
iconSize: [25, 35],
iconAnchor: [12, 35]
});
const iconMerah = L.icon({
iconUrl: "https://maps.google.com/mapfiles/ms/icons/red-dot.png",
iconSize: [25, 35],
iconAnchor: [12, 35]
});
// Layer Group SPBU
const spbu24Jam = L.layerGroup();
const spbuTidak24Jam = L.layerGroup();
const popup = L.popup();
function onMapClick(e) {
const lat = e.latlng.lat;
const lng = e.latlng.lng;
const form = `
<form onsubmit="simpanData(event, ${lat}, ${lng})">
<label>Nama:</label><br>
<input type="text" id="nama" required><br><br>
<label>Nomor:</label><br>
<input type="text" id="no"><br><br>
<label>Buka 24 Jam:</label><br>
<select id="24jam">
<option value="Ya">Ya</option>
<option value="Tidak">Tidak</option>
</select><br><br>
<button type="submit">Save</button>
</form>
`;
popup
.setLatLng(e.latlng)
.setContent(form)
.openOn(map);
}
map.on('click', onMapClick);
function simpanData(event, lat, lng) {
event.preventDefault();
const nama = document.getElementById("nama").value;
const nomor = document.getElementById("no").value;
const status = document.getElementById("24jam").value;
const dataBaru = { nama, nomor, status, lat, lng };
let data = JSON.parse(localStorage.getItem("titik")) || [];
data.push(dataBaru);
localStorage.setItem("titik", JSON.stringify(data));
alert("Data berhasil disimpan!");
map.closePopup();
}
let data = JSON.parse(localStorage.getItem("titik")) || [];
data.forEach((d, index) => {
let iconDipakai;
if (d.status === "Ya") {
iconDipakai = iconHijau;
} else {
iconDipakai = iconMerah;
}
const marker = L.marker([d.lat, d.lng], {
icon: iconDipakai,
draggable: true
});
if (d.status === "Ya") {
spbu24Jam.addLayer(marker);
} else {
spbuTidak24Jam.addLayer(marker);
}
marker.on('dragend', function (e) {
const posisiBaru = marker.getLatLng();
updateKoordinat(index, posisiBaru.lat, posisiBaru.lng);
});
marker.bindPopup(`
<b>${d.nama}</b><br>
${d.nomor}<br>
Buka 24 jam?:
<span style="color:${d.status === 'Ya' ? 'green' : 'red'}">
${d.status}
</span><br>
<button onclick="hapusData(${index})">Hapus</button>
`);
});
function hapusData(index) {
let data = JSON.parse(localStorage.getItem("titik")) || [];
data.splice(index, 1);
localStorage.setItem("titik", JSON.stringify(data));
alert("Data berhasil dihapus!");
location.reload();
}
function updateKoordinat(index, latBaru, lngBaru) {
let data = JSON.parse(localStorage.getItem("titik")) || [];
data[index].lat = latBaru;
data[index].lng = lngBaru;
localStorage.setItem("titik", JSON.stringify(data));
alert("Koordinat berhasil diupdate!");
}
// Base Maps
const baseMaps = {
"OpenStreetMap": osm,
"Satellite": satellite
};
// Overlay Maps
const overlayMaps = {
"SPBU 24 Jam": spbu24Jam,
"SPBU Tidak 24 Jam": spbuTidak24Jam
};
// Tampilkan default
spbu24Jam.addTo(map);
spbuTidak24Jam.addTo(map);
// Layer Control
L.control.layers(baseMaps, overlayMaps, {
collapsed: false
}).addTo(map);
</script>
</body>
</html>
+1
View File
File diff suppressed because one or more lines are too long
+54
View File
File diff suppressed because one or more lines are too long