Project Webgis2 SIG
This commit is contained in:
@@ -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
|
||||
]);
|
||||
?>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
include 'koneksi.php';
|
||||
|
||||
$id = $_GET['id'];
|
||||
|
||||
$conn->query("DELETE FROM ibadah WHERE id=$id");
|
||||
?>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
include 'koneksi.php';
|
||||
|
||||
$id = $_GET['id'];
|
||||
|
||||
$conn->query("DELETE FROM warga WHERE id=$id");
|
||||
?>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
$conn = new mysqli("localhost", "root", "", "sig_pontianak");
|
||||
|
||||
if ($conn->connect_error) {
|
||||
die("Koneksi gagal: " . $conn->connect_error);
|
||||
}
|
||||
?>
|
||||
@@ -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
|
||||
]);
|
||||
?>
|
||||
@@ -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
|
||||
]);
|
||||
?>
|
||||
@@ -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);
|
||||
?>
|
||||
@@ -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);
|
||||
?>
|
||||
@@ -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);
|
||||
?>
|
||||
@@ -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
@@ -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: '© 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 ? '–' + to : '+')
|
||||
);
|
||||
}
|
||||
|
||||
div.innerHTML = labels.join('<br>');
|
||||
|
||||
return div;
|
||||
};
|
||||
|
||||
legend.addTo(map);
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
@@ -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
@@ -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
@@ -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">
|
||||
© 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>
|
||||
@@ -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">⬤ –</span>
|
||||
<span class="tb-user" id="usernameLabel"></span>
|
||||
<button class="tb-logout" onclick="doLogout()">🚪 Keluar</button>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════════════
|
||||
INFO BANNER (Pengguna biasa)
|
||||
══════════════════════════════════════ -->
|
||||
<div id="infoBanner">
|
||||
ℹ️ 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 = '🛡️ Admin';
|
||||
badge.classList.add('admin-badge');
|
||||
} else {
|
||||
badge.innerHTML = '👤 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: '© 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
@@ -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: '© <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
@@ -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: '© <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
@@ -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: '© <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
@@ -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: '© OpenStreetMap'
|
||||
});
|
||||
|
||||
const satellite = L.tileLayer(
|
||||
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
|
||||
{
|
||||
attribution: 'Tiles © 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>
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user