Inital Commit: Landing Page & Folder Tugas-Tugas
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
FROM php:8.2-apache
|
||||
|
||||
RUN docker-php-ext-install mysqli pdo pdo_mysql
|
||||
|
||||
COPY . /var/www/html/
|
||||
|
||||
RUN chown -R www-data:www-data /var/www/html/
|
||||
|
||||
EXPOSE 80
|
||||
@@ -0,0 +1,120 @@
|
||||
html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
background-color: #ecf0f5;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#map {
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
font-family: sans-serif;
|
||||
line-height: 1.6;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.btn-delete {
|
||||
background-color: #ff4d4d;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
width: 48%;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
background-color: #ffc107;
|
||||
color: black;
|
||||
border: none;
|
||||
padding: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
width: 48%;
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
margin-bottom: 5px;
|
||||
padding: 5px;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
width: 320px;
|
||||
min-width: 320px;
|
||||
height: 100%;
|
||||
background-color: #ffffff;
|
||||
border-right: 1px solid #ccc;
|
||||
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#sidebar h2 {
|
||||
margin-top: 0;
|
||||
font-size: 1.4rem;
|
||||
color: #2c3e50;
|
||||
border-bottom: 2px solid #3498db;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
#sidebar h4 {
|
||||
margin: 15px 0 5px 0;
|
||||
color: #34495e;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.search-container button {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 12px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.search-container button:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
$koneksi = mysqli_connect("localhost", "root", "", "webgis");
|
||||
|
||||
if (!$koneksi) {
|
||||
die("Koneksi gagal: " . mysqli_connect_error());
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,289 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Tugas WebGIS - Alvin Andrianto Ciputra</title>
|
||||
|
||||
<!-- Google Fonts: Inter -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- FontAwesome untuk Ikon -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #2980b9;
|
||||
--secondary-color: #8e44ad;
|
||||
--bg-color: #f4f7f6;
|
||||
--text-main: #2c3e50;
|
||||
--text-muted: #7f8c8d;
|
||||
--card-bg: #ffffff;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: var(--bg-color);
|
||||
background-image: radial-gradient(#d5d8dc 1px, transparent 1px);
|
||||
background-size: 20px 20px;
|
||||
color: var(--text-main);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 1100px;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 50px;
|
||||
animation: fadeInDown 0.8s ease-out;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 800;
|
||||
margin-bottom: 10px;
|
||||
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.header h4 {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-muted);
|
||||
margin-top: 0;
|
||||
margin-bottom: 30px;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.profile-card {
|
||||
display: inline-block;
|
||||
background-color: var(--card-bg);
|
||||
padding: 20px 40px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.05);
|
||||
border-top: 4px solid var(--primary-color);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.cards {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 25px;
|
||||
animation: fadeInUp 0.8s ease-out;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card {
|
||||
flex: 1 1 300px;
|
||||
max-width: 340px;
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 16px;
|
||||
padding: 30px 25px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.04);
|
||||
text-align: center;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
.card.tugas-besar {
|
||||
max-width: 600px;
|
||||
border: 2px solid var(--secondary-color);
|
||||
}
|
||||
|
||||
.card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-8px);
|
||||
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.card:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
font-size: 2.5rem;
|
||||
color: var(--primary-color);
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.task-number {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
color: var(--secondary-color);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.task-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-main);
|
||||
margin-bottom: 25px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.btn-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 12px 24px;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn-link:hover {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-link i {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-link:hover i {
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
@keyframes fadeInDown {
|
||||
from { opacity: 0; transform: translateY(-20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: auto;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>Manajemen Data Spasial Kota Pontianak</h1>
|
||||
<h4>Kumpulan Tugas SIG</h4>
|
||||
|
||||
<div class="profile-card">
|
||||
<table>
|
||||
<tr>
|
||||
<td><strong>Nama</strong></td>
|
||||
<td>:</td>
|
||||
<td><strong>Alvin Andrianto Ciputra</strong></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><strong>NIM</strong></td>
|
||||
<td>:</td>
|
||||
<td><strong>D1041231074</strong></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><strong>Kelas</strong></td>
|
||||
<td>:</td>
|
||||
<td><strong>SIG C</strong></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cards">
|
||||
<div class="card">
|
||||
<div class="card-icon">
|
||||
<i class="fas fa-map-marker-alt"></i>
|
||||
</div>
|
||||
<div class="task-number">Tugas 01</div>
|
||||
<div class="task-title">Manajemen Data SPBU (Titik / Point)</div>
|
||||
<a href="tugas_01/index.php" class="btn-link">
|
||||
Buka Proyek <i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-icon">
|
||||
<i class="fas fa-route"></i>
|
||||
</div>
|
||||
<div class="task-number">Tugas 02</div>
|
||||
<div class="task-title">Manajemen Data Jalan (Polyline) & Parsil (Polygon)</div>
|
||||
<a href="tugas_02/index.php" class="btn-link">
|
||||
Buka Proyek <i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-icon">
|
||||
<i class="fas fa-layer-group"></i>
|
||||
</div>
|
||||
<div class="task-number">Tugas 03</div>
|
||||
<div class="task-title">Penerapan Layer Groups & Layers Controls</div>
|
||||
<a href="tugas_03/index.php" class="btn-link">
|
||||
Buka Proyek <i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card tugas-besar">
|
||||
<div class="card-icon" style="color: #8e44ad;">
|
||||
<i class="fas fa-globe-asia"></i>
|
||||
</div>
|
||||
<div class="task-number" style="color: #8e44ad;">Tugas Besar </div>
|
||||
<div class="task-title">WebGIS Poverty Mapping</div>
|
||||
<a href="proverty_mapping/index.php" class="btn-link" style="background-color: #8e44ad; color: white;">
|
||||
Buka Proyek <i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
© 2026 Alvin Andrianto Ciputra - Teknik Informatika UNTAN
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
include "../config/koneksi.php";
|
||||
|
||||
$q = mysqli_query($koneksi, "SELECT * FROM jalan");
|
||||
$features = [];
|
||||
|
||||
while($row = mysqli_fetch_assoc($q)) {
|
||||
// GeoJSON menggunakan format [longitude, latitude]
|
||||
// Kita perlu membalik urutan jika di database tersimpan sebagai [lat, lng]
|
||||
$raw_coords = json_decode($row['koordinat']);
|
||||
$formatted_coords = [];
|
||||
foreach($raw_coords as $c) {
|
||||
$formatted_coords[] = [$c->lng, $c->lat];
|
||||
}
|
||||
|
||||
$features[] = [
|
||||
"type" => "Feature",
|
||||
"geometry" => [
|
||||
"type" => "LineString",
|
||||
"coordinates" => $formatted_coords
|
||||
],
|
||||
"properties" => [
|
||||
"id" => $row['id'],
|
||||
"nama" => $row['nama_jalan'],
|
||||
"status" => $row['status_jalan'],
|
||||
"panjang" => $row['panjang_jalan']
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
$geojson = ["type" => "FeatureCollection", "features" => $features];
|
||||
echo json_encode($geojson);
|
||||
?>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
include "../config/koneksi.php";
|
||||
|
||||
$id = $_GET['id'];
|
||||
$query = "DELETE FROM jalan WHERE id = '$id'";
|
||||
if(mysqli_query($koneksi, $query)) {
|
||||
echo json_encode(['status' => 'success']);
|
||||
} else {
|
||||
echo json_encode(['status' => 'error']);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
include "../config/koneksi.php";
|
||||
|
||||
$query = "INSERT INTO jalan (nama_jalan, status_jalan, panjang_jalan, koordinat) VALUES
|
||||
('".$_POST['nama']."', '".$_POST['status']."', '".$_POST['panjang']."', '".$_POST['koordinat']."')";
|
||||
mysqli_query($koneksi, $query);
|
||||
?>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
include "../config/koneksi.php";
|
||||
|
||||
$id = $_POST['id'];
|
||||
$nama = $_POST['nama'];
|
||||
$status = $_POST['status'];
|
||||
|
||||
$query = "UPDATE jalan SET nama_jalan='$nama', status_jalan='$status' WHERE id='$id'";
|
||||
if(mysqli_query($koneksi, $query)) {
|
||||
echo json_encode(['status' => 'success']);
|
||||
} else {
|
||||
echo json_encode(['status' => 'error']);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
include "../config/koneksi.php";
|
||||
|
||||
// Set header agar browser tahu ini adalah file JSON
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$q = mysqli_query($koneksi, "SELECT * FROM parsil");
|
||||
$features = [];
|
||||
|
||||
while($row = mysqli_fetch_assoc($q)) {
|
||||
$raw_coords = json_decode($row['koordinat']);
|
||||
$formatted_coords = [];
|
||||
|
||||
// GeoJSON Polygon membutuhkan koordinat dalam array 3 lapis: [[[lng, lat], [lng, lat]]]
|
||||
// Kita ambil dari koordinat mentah dan balik urutannya ke [longitude, latitude]
|
||||
$polygon_ring = [];
|
||||
foreach($raw_coords as $c) {
|
||||
$polygon_ring[] = [(float)$c->lng, (float)$c->lat];
|
||||
}
|
||||
|
||||
// Pastikan poligon tertutup (titik pertama sama dengan titik terakhir)
|
||||
if (!empty($polygon_ring) && ($polygon_ring[0] !== end($polygon_ring))) {
|
||||
$polygon_ring[] = $polygon_ring[0];
|
||||
}
|
||||
|
||||
$features[] = [
|
||||
"type" => "Feature",
|
||||
"geometry" => [
|
||||
"type" => "Polygon",
|
||||
"coordinates" => [$polygon_ring] // Array bersarang untuk Polygon
|
||||
],
|
||||
"properties" => [
|
||||
"id" => $row['id'],
|
||||
"pemilik" => $row['nama_pemilik'],
|
||||
"status" => $row['status_kepemilikan'],
|
||||
"luas" => $row['luas_tanah']
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
$geojson = [
|
||||
"type" => "FeatureCollection",
|
||||
"features" => $features
|
||||
];
|
||||
|
||||
echo json_encode($geojson);
|
||||
?>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
include "../config/koneksi.php";
|
||||
|
||||
$id = $_GET['id'];
|
||||
$query = "DELETE FROM parsil WHERE id = '$id'";
|
||||
if(mysqli_query($koneksi, $query)) {
|
||||
echo json_encode(['status' => 'success']);
|
||||
} else {
|
||||
echo json_encode(['status' => 'error']);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
include "../config/koneksi.php";
|
||||
|
||||
$query = "INSERT INTO parsil (nama_pemilik, status_kepemilikan, luas_tanah, koordinat) VALUES
|
||||
('".$_POST['pemilik']."', '".$_POST['status']."', '".$_POST['luas']."', '".$_POST['koordinat']."')";
|
||||
mysqli_query($koneksi, $query);
|
||||
?>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
include "../config/koneksi.php";
|
||||
|
||||
$id = $_POST['id'];
|
||||
$pemilik = $_POST['pemilik'];
|
||||
$status = $_POST['status'];
|
||||
|
||||
$query = "UPDATE parsil SET nama_pemilik='$pemilik', status_kepemilikan='$status' WHERE id='$id'";
|
||||
if(mysqli_query($koneksi, $query)) {
|
||||
echo json_encode(['status' => 'success']);
|
||||
} else {
|
||||
echo json_encode(['status' => 'error']);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
include "../config/koneksi.php";
|
||||
|
||||
$id = $_POST['id'];
|
||||
$type = $_POST['type']; // Akan berisi 'jalan' atau 'parsil'
|
||||
$nilai = $_POST['nilai']; // Akan berisi panjang (meter) atau luas (meter persegi) baru
|
||||
$koordinat = $_POST['koordinat'];
|
||||
|
||||
if ($type === 'jalan') {
|
||||
$query = "UPDATE jalan SET panjang_jalan='$nilai', koordinat='$koordinat' WHERE id='$id'";
|
||||
} else if ($type === 'parsil') {
|
||||
$query = "UPDATE parsil SET luas_tanah='$nilai', koordinat='$koordinat' WHERE id='$id'";
|
||||
}
|
||||
|
||||
if (mysqli_query($koneksi, $query)) {
|
||||
echo json_encode(['status' => 'success']);
|
||||
} else {
|
||||
echo json_encode(['status' => 'error', 'message' => mysqli_error($koneksi)]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
include "../config/koneksi.php";
|
||||
|
||||
$id = $_GET['id'];
|
||||
$query = "DELETE FROM spbu WHERE id = '$id'";
|
||||
|
||||
if(mysqli_query($koneksi, $query)) {
|
||||
echo json_encode(['status' => 'success']);
|
||||
} else {
|
||||
echo json_encode(['status' => 'error']);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
include "../config/koneksi.php";
|
||||
|
||||
$nama = $_POST['nama_spbu'];
|
||||
$wa = $_POST['no_wa'];
|
||||
$buka = $_POST['buka_24'];
|
||||
$lat = $_POST['latitude'];
|
||||
$lng = $_POST['longitude'];
|
||||
|
||||
$query = "INSERT INTO spbu (nama_spbu, no_wa, buka_24, latitude, longitude)
|
||||
VALUES ('$nama', '$wa', '$buka', '$lat', '$lng')";
|
||||
|
||||
if(mysqli_query($koneksi, $query)) {
|
||||
echo json_encode(['status' => 'success', 'id' => mysqli_insert_id($koneksi)]);
|
||||
} else {
|
||||
echo json_encode(['status' => 'error', 'message' => mysqli_error($koneksi)]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
include "../congif/koneksi.php";
|
||||
|
||||
$id = $_POST['id'];
|
||||
$lat = $_POST['lat'];
|
||||
$lng = $_POST['lng'];
|
||||
mysqli_query($koneksi, "UPDATE spbu SET latitude='$lat', longitude='$lng' WHERE id='$id'");
|
||||
?>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
include "../config/koneksi.php";
|
||||
|
||||
$id = $_POST['id'];
|
||||
$nama = $_POST['nama'];
|
||||
$wa = $_POST['wa'];
|
||||
$buka = $_POST['buka'];
|
||||
|
||||
$query = "UPDATE spbu SET nama_spbu='$nama', no_wa='$wa', buka_24='$buka' WHERE id='$id'";
|
||||
if(mysqli_query($koneksi, $query)) {
|
||||
echo json_encode(['status' => 'success']);
|
||||
} else {
|
||||
echo json_encode(['status' => 'error']);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
include "../config/koneksi.php";
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>WebGIS Manajemen Spasial Kota Pontianak</title>
|
||||
|
||||
<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>
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css" />
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/leaflet-geodesic"></script>
|
||||
|
||||
<link rel="stylesheet" href="../assets/css/style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="app-container">
|
||||
<div id="sidebar">
|
||||
<h2>WebGIS Manajemen Spasial Kota Pontianak</h2>
|
||||
<h4>Tugas 01 SIG: Manajemen Data Point (SPBU)</h4>
|
||||
|
||||
<h4>Pencarian Data</h4>
|
||||
<div class="search-container">
|
||||
<input type="text" id="searchInput" placeholder="Cari SPBU ..."
|
||||
onkeypress="handleEnter(event)">
|
||||
<button onclick="cariData()">Cari Lokasi</button>
|
||||
</div>
|
||||
|
||||
<h4>Tambah SPBU (Point Marker):</h4>
|
||||
<div id="drawControlWrapper"></div>
|
||||
</div>
|
||||
|
||||
<div id="map"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const DATA_SPBU = <?php
|
||||
$query = mysqli_query($koneksi, "SELECT id, nama_spbu as nama, no_wa as wa, buka_24 as buka, latitude as lat, longitude as lng FROM spbu");
|
||||
$data = [];
|
||||
if($query) {
|
||||
while ($row = mysqli_fetch_assoc($query)) {
|
||||
$row['lat'] = (float) $row['lat'];
|
||||
$row['lng'] = (float) $row['lng'];
|
||||
$data[] = $row;
|
||||
}
|
||||
}
|
||||
echo json_encode($data);
|
||||
?>;
|
||||
</script>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,217 @@
|
||||
let tempLat, tempLng;
|
||||
const map = L.map('map').setView([-0.0263, 109.3425], 13);
|
||||
|
||||
const baseOsm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors'
|
||||
}).addTo(map);
|
||||
|
||||
const greenIcon = new L.Icon({
|
||||
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png',
|
||||
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
|
||||
iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41]
|
||||
});
|
||||
|
||||
const redIcon = new L.Icon({
|
||||
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
|
||||
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
|
||||
iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41]
|
||||
});
|
||||
|
||||
const layerSPBU = L.layerGroup().addTo(map);
|
||||
const drawnItems = new L.FeatureGroup().addTo(map);
|
||||
|
||||
let searchableLayers = [];
|
||||
|
||||
// Setup Leaflet Draw Toolbar
|
||||
const drawControl = new L.Control.Draw({
|
||||
draw: {
|
||||
marker: { icon: redIcon },
|
||||
circle: false, rectangle: false, circlemarker: false, polyline: false, polygon: false
|
||||
},
|
||||
edit: { featureGroup: drawnItems }
|
||||
}).addTo(map);
|
||||
|
||||
// Memindahkan UI Draw Toolbar dari peta ke dalam Sidebar
|
||||
document.getElementById('drawControlWrapper').appendChild(drawControl.getContainer());
|
||||
|
||||
// FUNGSI RENDER DATA SPBU
|
||||
function createSPBUMarker(data) {
|
||||
const statusBuka = data.buka.trim();
|
||||
const markerIcon = (statusBuka === 'Ya') ? redIcon : greenIcon;
|
||||
const marker = L.marker([data.lat, data.lng], { icon: markerIcon, draggable: true });
|
||||
|
||||
function updatePopupContent(id, nama, wa, buka, lat, lng, leafletId) {
|
||||
return `<div class="popup-content">
|
||||
<b>${nama}</b><br>WA: ${wa}<br>Buka 24: ${buka}<br>
|
||||
Lat: ${lat.toFixed(6)}<br>Lng: ${lng.toFixed(6)}<br>
|
||||
<div class="button-group">
|
||||
<button class="btn-edit" onclick="editData(${id}, '${nama}', '${wa}', '${buka}')">Edit</button>
|
||||
<button class="btn-delete" onclick="deleteMarker(${id}, ${leafletId})">Hapus</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
marker.bindPopup(updatePopupContent(data.id, data.nama, data.wa, data.buka, data.lat, data.lng, marker._leaflet_id));
|
||||
|
||||
marker.on('dragend', async function (e) {
|
||||
const newLatLng = marker.getLatLng();
|
||||
const fd = new FormData();
|
||||
fd.append('id', data.id); fd.append('lat', newLatLng.lat); fd.append('lng', newLatLng.lng);
|
||||
await fetch('../spbu/update_koordinat.php', { method: 'POST', body: fd });
|
||||
marker.setPopupContent(updatePopupContent(data.id, data.nama, data.wa, data.buka, newLatLng.lat, newLatLng.lng, marker._leaflet_id));
|
||||
});
|
||||
marker.addTo(layerSPBU);
|
||||
|
||||
searchableLayers.push({ nama: data.nama, layer: marker, tipe: 'titik' });
|
||||
}
|
||||
|
||||
// Mengeksekusi variabel global DATA_SPBU yang dilempar dari index.php
|
||||
if (typeof DATA_SPBU !== 'undefined') {
|
||||
DATA_SPBU.forEach(spbu => createSPBUMarker(spbu));
|
||||
}
|
||||
|
||||
// EVENT HANDLING LEAFLET DRAW
|
||||
map.on(L.Draw.Event.CREATED, async function (e) {
|
||||
const layer = e.layer;
|
||||
const latlng = layer.getLatLng();
|
||||
tempLat = latlng.lat; tempLng = latlng.lng;
|
||||
|
||||
const formContent = `<div class="popup-content">
|
||||
<h4>Tambah SPBU Baru</h4>
|
||||
<input type="text" id="input_nama" placeholder="Nama SPBU">
|
||||
<input type="text" id="input_wa" placeholder="No WA">
|
||||
<select id="input_buka">
|
||||
<option value="Ya">Buka 24 Jam</option>
|
||||
<option value="Tidak">Tidak 24 Jam</option>
|
||||
</select>
|
||||
<button class="btn-save" onclick="simpanData()">Simpan</button>
|
||||
</div>`;
|
||||
L.popup().setLatLng(latlng).setContent(formContent).openOn(map);
|
||||
map.removeLayer(layer);
|
||||
});
|
||||
|
||||
// CRUD BACKEND (FETCH API)
|
||||
window.simpanData = async function() {
|
||||
const formData = new FormData();
|
||||
formData.append('nama_spbu', document.getElementById('input_nama').value);
|
||||
formData.append('no_wa', document.getElementById('input_wa').value);
|
||||
formData.append('buka_24', document.getElementById('input_buka').value);
|
||||
formData.append('latitude', tempLat); formData.append('longitude', tempLng);
|
||||
|
||||
const response = await fetch('../spbu/tambah_spbu.php', { method: 'POST', body: formData });
|
||||
if ((await response.json()).status === 'success') location.reload();
|
||||
}
|
||||
|
||||
window.updateData = async function(id) {
|
||||
const formData = new FormData();
|
||||
formData.append('id', id);
|
||||
formData.append('nama', document.getElementById('edit_nama').value);
|
||||
formData.append('wa', document.getElementById('edit_wa').value);
|
||||
formData.append('buka', document.getElementById('edit_buka').value);
|
||||
|
||||
const response = await fetch('../spbu/update_spbu.php', { method: 'POST', body: formData });
|
||||
if ((await response.json()).status === 'success') location.reload();
|
||||
}
|
||||
|
||||
window.deleteMarker = async function(id, leafletId) {
|
||||
if (confirm("Hapus data?")) {
|
||||
const response = await fetch(`../spbu/hapus_spbu.php?id=${id}`);
|
||||
if ((await response.json()).status === 'success') location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
window.editData = function(id, nama, wa, buka) {
|
||||
const editForm = `<div class="popup-content">
|
||||
<h4>Edit SPBU</h4>
|
||||
<input type="text" id="edit_nama" value="${nama}">
|
||||
<input type="text" id="edit_wa" value="${wa}">
|
||||
<select id="edit_buka">
|
||||
<option value="Ya" ${buka === 'Ya' ? 'selected' : ''}>Buka 24 Jam</option>
|
||||
<option value="Tidak" ${buka === 'Tidak' ? 'selected' : ''}>Tidak 24 Jam</option>
|
||||
</select>
|
||||
<button class="btn-save" onclick="updateData(${id})">Update Data</button>
|
||||
</div>`;
|
||||
map.closePopup();
|
||||
L.popup().setLatLng(map.getCenter()).setContent(editForm).openOn(map);
|
||||
}
|
||||
|
||||
// FUNGSI PENCARIAN DATA
|
||||
window.cariData = function() {
|
||||
const keyword = document.getElementById('searchInput').value.toLowerCase().trim();
|
||||
if (!keyword) { alert("Masukkan kata kunci pencarian!"); return; }
|
||||
|
||||
let found = false;
|
||||
for (let item of searchableLayers) {
|
||||
if (item.nama && item.nama.toLowerCase().includes(keyword)) {
|
||||
found = true;
|
||||
if (item.tipe === 'titik') { map.flyTo(item.layer.getLatLng(), 17); }
|
||||
else { map.flyToBounds(item.layer.getBounds(), { maxZoom: 17 }); }
|
||||
item.layer.openPopup();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) alert("Data tidak ditemukan! Pastikan nama SPBU sudah benar.");
|
||||
}
|
||||
|
||||
window.handleEnter = function(e) {
|
||||
if (e.key === 'Enter') cariData();
|
||||
}
|
||||
|
||||
// MUAT BASEMAP KECAMATAN
|
||||
async function loadBasemapPontianak() {
|
||||
const adminGrp = L.layerGroup().addTo(map);
|
||||
|
||||
async function fetchGeoJSON(url) {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error(`File tidak ditemukan: ${url}`);
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
function getColorKecamatan(nama) {
|
||||
if (!nama) return "#cccccc";
|
||||
const n = nama.toUpperCase();
|
||||
if (n.includes("BARAT")) return "#e41a1c";
|
||||
if (n.includes("KOTA")) return "#377eb8";
|
||||
if (n.includes("SELATAN")) return "#4daf4a";
|
||||
if (n.includes("TENGGARA")) return "#984ea3";
|
||||
if (n.includes("TIMUR")) return "#ff7f00";
|
||||
if (n.includes("UTARA")) return "#f1c40f";
|
||||
return "#a65628";
|
||||
}
|
||||
|
||||
try {
|
||||
const dataAdmin = await fetchGeoJSON('../assets/data/Admin_Kecamatan.json');
|
||||
L.geoJSON(dataAdmin, {
|
||||
style: function (feature) {
|
||||
const namaKecamatan = feature.properties.Ket || "";
|
||||
return { color: "black", weight: 2, fillOpacity: 0.2, fillColor:getColorKecamatan(namaKecamatan) };
|
||||
},
|
||||
onEachFeature: function (feature, layer) {
|
||||
const namaKecamatan = feature.properties.Ket || "Tidak Diketahui";
|
||||
const jumlahPenduduk = feature.properties.penduduk || 0;
|
||||
const formatPenduduk = new Intl.NumberFormat('id-ID').format(jumlahPenduduk);
|
||||
|
||||
layer.bindPopup(`
|
||||
<div style="text-align: center;">
|
||||
<b>Kecamatan ${namaKecamatan}</b><br>
|
||||
<hr style="margin: 5px 0;">
|
||||
Jumlah Penduduk: <b>${formatPenduduk} Jiwa</b>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
}).addTo(adminGrp);
|
||||
|
||||
} catch (e) {
|
||||
console.error("Gagal memuat peta kecamatan:", e.message);
|
||||
}
|
||||
|
||||
const baseLayers = { "OpenStreetMap": baseOsm };
|
||||
const overlays = {
|
||||
"Batas Kecamatan": adminGrp,
|
||||
"SPBU (Titik)": layerSPBU,
|
||||
"Alat Digitasi": drawnItems
|
||||
};
|
||||
L.control.layers(baseLayers, overlays).addTo(map);
|
||||
}
|
||||
|
||||
loadBasemapPontianak();
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
include "../config/koneksi.php";
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>WebGIS Manajemen Spasial Kota Pontianak</title>
|
||||
|
||||
<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>
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css" />
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/leaflet-geodesic"></script>
|
||||
|
||||
<link rel="stylesheet" href="../assets/css/style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="app-container">
|
||||
<div id="sidebar">
|
||||
<h2>WebGIS Manajemen Spasial Kota Pontianak</h2>
|
||||
<h4>Tugas 02 SIG: Manajemen Data Polyline (Jalan) & Polygon (Parsil)</h4>
|
||||
|
||||
<h4>Pencarian Data</h4>
|
||||
<div class="search-container">
|
||||
<input type="text" id="searchInput" placeholder="Cari SPBU, Jalan, Parsil..."
|
||||
onkeypress="handleEnter(event)">
|
||||
<button onclick="cariData()">Cari Lokasi</button>
|
||||
</div>
|
||||
|
||||
<h4>Alat Digitasi</h4>
|
||||
<div id="drawControlWrapper"></div>
|
||||
</div>
|
||||
|
||||
<div id="map"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const DATA_SPBU = <?php
|
||||
// Pemanggilan data dari PHP (Backend) sebelum file JS dimuat
|
||||
$query = mysqli_query($koneksi, "SELECT id, nama_spbu as nama, no_wa as wa, buka_24 as buka, latitude as lat, longitude as lng FROM spbu");
|
||||
$data = [];
|
||||
if ($query) {
|
||||
while ($row = mysqli_fetch_assoc($query)) {
|
||||
$row['lat'] = (float) $row['lat'];
|
||||
$row['lng'] = (float) $row['lng'];
|
||||
$data[] = $row;
|
||||
}
|
||||
}
|
||||
echo json_encode($data);
|
||||
?>;
|
||||
</script>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,390 @@
|
||||
let tempLat, tempLng;
|
||||
const map = L.map('map').setView([-0.0263, 109.3425], 13);
|
||||
|
||||
const baseOsm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors'
|
||||
}).addTo(map);
|
||||
|
||||
const greenIcon = new L.Icon({
|
||||
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png',
|
||||
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
|
||||
iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41]
|
||||
});
|
||||
|
||||
const redIcon = new L.Icon({
|
||||
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
|
||||
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
|
||||
iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41]
|
||||
});
|
||||
|
||||
const layerSPBU = L.layerGroup().addTo(map);
|
||||
const drawnItems = new L.FeatureGroup().addTo(map);
|
||||
|
||||
let searchableLayers = [];
|
||||
|
||||
// SETUP LEAFLET DRAW TOOLBAR
|
||||
const drawControl = new L.Control.Draw({
|
||||
draw: {
|
||||
polyline: { shapeOptions: { color: 'blue' } },
|
||||
polygon: { shapeOptions: { color: 'orange' } },
|
||||
marker: { icon: redIcon },
|
||||
circle: false, rectangle: false, circlemarker: false
|
||||
},
|
||||
edit: { featureGroup: drawnItems }
|
||||
}).addTo(map);
|
||||
|
||||
document.getElementById('drawControlWrapper').appendChild(drawControl.getContainer());
|
||||
|
||||
// FUNGSI RENDER DATA SPBU (POINT)
|
||||
function createSPBUMarker(data) {
|
||||
const statusBuka = data.buka.trim();
|
||||
const markerIcon = (statusBuka === 'Ya') ? greenIcon : redIcon;
|
||||
const marker = L.marker([data.lat, data.lng], { icon: markerIcon, draggable: true });
|
||||
|
||||
function updatePopupContent(id, nama, wa, buka, lat, lng, leafletId) {
|
||||
return `<div class="popup-content">
|
||||
<b>${nama}</b><br>WA: ${wa}<br>Buka 24: ${buka}<br>
|
||||
Lat: ${lat.toFixed(6)}<br>Lng: ${lng.toFixed(6)}<br>
|
||||
<div class="button-group">
|
||||
<button class="btn-edit" onclick="editData(${id}, '${nama}', '${wa}', '${buka}')">Edit</button>
|
||||
<button class="btn-delete" onclick="deleteMarker(${id}, ${leafletId})">Hapus</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
marker.bindPopup(updatePopupContent(data.id, data.nama, data.wa, data.buka, data.lat, data.lng, marker._leaflet_id));
|
||||
marker.on('dragend', async function (e) {
|
||||
const newLatLng = marker.getLatLng();
|
||||
const fd = new FormData();
|
||||
fd.append('id', data.id); fd.append('lat', newLatLng.lat); fd.append('lng', newLatLng.lng);
|
||||
await fetch('../spbu/update_koordinat.php', { method: 'POST', body: fd });
|
||||
marker.setPopupContent(updatePopupContent(data.id, data.nama, data.wa, data.buka, newLatLng.lat, newLatLng.lng, marker._leaflet_id));
|
||||
});
|
||||
marker.addTo(layerSPBU);
|
||||
|
||||
searchableLayers.push({ nama: data.nama, layer: marker, tipe: 'titik' });
|
||||
}
|
||||
|
||||
if (typeof DATA_SPBU !== 'undefined') {
|
||||
DATA_SPBU.forEach(spbu => createSPBUMarker(spbu));
|
||||
}
|
||||
|
||||
// EVENT HANDLING LEAFLET DRAW (CREATED & EDITED)
|
||||
map.on(L.Draw.Event.CREATED, async function (e) {
|
||||
const layer = e.layer;
|
||||
const type = e.layerType;
|
||||
if (type === 'polyline') {
|
||||
const coords = layer.getLatLngs();
|
||||
let panjang = 0;
|
||||
for (let i = 0; i < coords.length - 1; i++) { panjang += coords[i].distanceTo(coords[i + 1]); }
|
||||
const nama = prompt("Nama Jalan:");
|
||||
const status = prompt("Status (Nasional/Provinsi/Kabupaten):");
|
||||
if (nama && status) {
|
||||
const fd = new FormData();
|
||||
fd.append('nama', nama); fd.append('status', status); fd.append('panjang', panjang.toFixed(2)); fd.append('koordinat', JSON.stringify(coords));
|
||||
await fetch('../jalan/tambah_jalan.php', { method: 'POST', body: fd }); location.reload();
|
||||
}
|
||||
} else if (type === 'polygon') {
|
||||
const coords = layer.getLatLngs()[0];
|
||||
const luas = L.GeometryUtil.geodesicArea(coords);
|
||||
const pemilik = prompt("Nama Pemilik:");
|
||||
const status = prompt("Status (SHM/HGB/HGU/HP):");
|
||||
if (pemilik && status) {
|
||||
const fd = new FormData();
|
||||
fd.append('pemilik', pemilik); fd.append('status', status); fd.append('luas', luas.toFixed(2)); fd.append('koordinat', JSON.stringify(coords));
|
||||
await fetch('../parsil/tambah_parsil.php', { method: 'POST', body: fd }); location.reload();
|
||||
}
|
||||
} else if (type === 'marker') {
|
||||
const latlng = layer.getLatLng();
|
||||
tempLat = latlng.lat; tempLng = latlng.lng;
|
||||
const formContent = `<div class="popup-content">
|
||||
<h4>Tambah SPBU Baru</h4>
|
||||
<input type="text" id="input_nama" placeholder="Nama SPBU">
|
||||
<input type="text" id="input_wa" placeholder="No WA">
|
||||
<select id="input_buka">
|
||||
<option value="Ya">Buka 24 Jam</option>
|
||||
<option value="Tidak">Tidak 24 Jam</option>
|
||||
</select>
|
||||
<button class="btn-save" onclick="simpanData()">Simpan</button>
|
||||
</div>`;
|
||||
L.popup().setLatLng(latlng).setContent(formContent).openOn(map);
|
||||
map.removeLayer(layer);
|
||||
}
|
||||
});
|
||||
|
||||
map.on(L.Draw.Event.EDITED, async function (e) {
|
||||
const layers = e.layers;
|
||||
layers.eachLayer(async function (layer) {
|
||||
const id = layer.db_id;
|
||||
const type = layer.db_type;
|
||||
|
||||
if (id && type) {
|
||||
let coords_str = '';
|
||||
let nilai_baru = 0;
|
||||
|
||||
if (type === 'jalan') {
|
||||
const coords = layer.getLatLngs();
|
||||
let panjang = 0;
|
||||
for (let i = 0; i < coords.length - 1; i++) { panjang += coords[i].distanceTo(coords[i + 1]); }
|
||||
nilai_baru = panjang.toFixed(2);
|
||||
coords_str = JSON.stringify(coords);
|
||||
} else if (type === 'parsil') {
|
||||
const coords = layer.getLatLngs()[0];
|
||||
nilai_baru = L.GeometryUtil.geodesicArea(coords).toFixed(2);
|
||||
coords_str = JSON.stringify(coords);
|
||||
}
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append('id', id); fd.append('type', type); fd.append('nilai', nilai_baru); fd.append('koordinat', coords_str);
|
||||
await fetch('../shared/update_geometri.php', { method: 'POST', body: fd });
|
||||
}
|
||||
});
|
||||
alert("Perubahan garis/bidang berhasil disimpan!");
|
||||
location.reload();
|
||||
});
|
||||
|
||||
// CRUD SPBU
|
||||
window.simpanData = async function() {
|
||||
const formData = new FormData();
|
||||
formData.append('nama_spbu', document.getElementById('input_nama').value);
|
||||
formData.append('no_wa', document.getElementById('input_wa').value);
|
||||
formData.append('buka_24', document.getElementById('input_buka').value);
|
||||
formData.append('latitude', tempLat); formData.append('longitude', tempLng);
|
||||
const response = await fetch('../spbu/tambah_spbu.php', { method: 'POST', body: formData });
|
||||
if ((await response.json()).status === 'success') location.reload();
|
||||
}
|
||||
|
||||
window.updateData = async function(id) {
|
||||
const formData = new FormData();
|
||||
formData.append('id', id);
|
||||
formData.append('nama', document.getElementById('edit_nama').value);
|
||||
formData.append('wa', document.getElementById('edit_wa').value);
|
||||
formData.append('buka', document.getElementById('edit_buka').value);
|
||||
const response = await fetch('../spbu/update_spbu.php', { method: 'POST', body: formData });
|
||||
if ((await response.json()).status === 'success') location.reload();
|
||||
}
|
||||
|
||||
window.deleteMarker = async function(id, leafletId) {
|
||||
if (confirm("Hapus data?")) {
|
||||
const response = await fetch(`../spbu/hapus_spbu.php?id=${id}`);
|
||||
if ((await response.json()).status === 'success') location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
window.editData = function(id, nama, wa, buka) {
|
||||
const editForm = `<div class="popup-content">
|
||||
<h4>Edit SPBU</h4>
|
||||
<input type="text" id="edit_nama" value="${nama}">
|
||||
<input type="text" id="edit_wa" value="${wa}">
|
||||
<select id="edit_buka">
|
||||
<option value="Ya" ${buka === 'Ya' ? 'selected' : ''}>Buka 24 Jam</option>
|
||||
<option value="Tidak" ${buka === 'Tidak' ? 'selected' : ''}>Tidak 24 Jam</option>
|
||||
</select>
|
||||
<button class="btn-save" onclick="updateData(${id})">Update Data</button>
|
||||
</div>`;
|
||||
map.closePopup();
|
||||
L.popup().setLatLng(map.getCenter()).setContent(editForm).openOn(map);
|
||||
}
|
||||
|
||||
// CRUD JALAN (POLYLINE)
|
||||
window.editDataJalan = function(id, nama, status) {
|
||||
const editForm = `<div class="popup-content">
|
||||
<h4>Edit Jalan</h4>
|
||||
<input type="text" id="edit_nama_jalan" value="${nama}">
|
||||
<select id="edit_status_jalan">
|
||||
<option value="Nasional" ${status === 'Nasional' ? 'selected' : ''}>Nasional</option>
|
||||
<option value="Provinsi" ${status === 'Provinsi' ? 'selected' : ''}>Provinsi</option>
|
||||
<option value="Kabupaten" ${status === 'Kabupaten' ? 'selected' : ''}>Kabupaten</option>
|
||||
</select>
|
||||
<button class="btn-save" onclick="updateDataJalan(${id})">Update Jalan</button>
|
||||
</div>`;
|
||||
map.closePopup();
|
||||
L.popup().setLatLng(map.getCenter()).setContent(editForm).openOn(map);
|
||||
}
|
||||
|
||||
window.updateDataJalan = async function(id) {
|
||||
const fd = new FormData();
|
||||
fd.append('id', id);
|
||||
fd.append('nama', document.getElementById('edit_nama_jalan').value);
|
||||
fd.append('status', document.getElementById('edit_status_jalan').value);
|
||||
const response = await fetch('../jalan/update_jalan.php', { method: 'POST', body: fd });
|
||||
if ((await response.json()).status === 'success') location.reload();
|
||||
}
|
||||
|
||||
window.deleteJalan = async function(id) {
|
||||
if (confirm("Hapus data jalan ini?")) {
|
||||
const response = await fetch(`../jalan/hapus_jalan.php?id=${id}`);
|
||||
if ((await response.json()).status === 'success') location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
// CRUD PARSIL (POLYGON)
|
||||
window.editDataParsil = function(id, pemilik, status) {
|
||||
const editForm = `<div class="popup-content">
|
||||
<h4>Edit Parsil</h4>
|
||||
<input type="text" id="edit_pemilik_parsil" value="${pemilik}">
|
||||
<select id="edit_status_parsil">
|
||||
<option value="SHM" ${status === 'SHM' ? 'selected' : ''}>SHM</option>
|
||||
<option value="HGB" ${status === 'HGB' ? 'selected' : ''}>HGB</option>
|
||||
<option value="HGU" ${status === 'HGU' ? 'selected' : ''}>HGU</option>
|
||||
<option value="HP" ${status === 'HP' ? 'selected' : ''}>HP</option>
|
||||
</select>
|
||||
<button class="btn-save" onclick="updateDataParsil(${id})">Update Parsil</button>
|
||||
</div>`;
|
||||
map.closePopup();
|
||||
L.popup().setLatLng(map.getCenter()).setContent(editForm).openOn(map);
|
||||
}
|
||||
|
||||
window.updateDataParsil = async function(id) {
|
||||
const fd = new FormData();
|
||||
fd.append('id', id);
|
||||
fd.append('pemilik', document.getElementById('edit_pemilik_parsil').value);
|
||||
fd.append('status', document.getElementById('edit_status_parsil').value);
|
||||
const response = await fetch('../parsil/update_parsil.php', { method: 'POST', body: fd });
|
||||
if ((await response.json()).status === 'success') location.reload();
|
||||
}
|
||||
|
||||
window.deleteParsil = async function(id) {
|
||||
if (confirm("Hapus data parsil ini?")) {
|
||||
const response = await fetch(`../parsil/hapus_parsil.php?id=${id}`);
|
||||
if ((await response.json()).status === 'success') location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
// RENDER SPASIAL (LOAD DARI DB)
|
||||
function getColorJalan(status) { return status === 'Nasional' ? 'red' : status === 'Provinsi' ? 'blue' : 'green'; }
|
||||
function getColorParsil(status) {
|
||||
const colors = { 'SHM': '#8B4513', 'HGB': '#FFD700', 'HGU': '#ADFF2F', 'HP': '#87CEEB' };
|
||||
return colors[status] || '#CCCCCC';
|
||||
}
|
||||
|
||||
async function loadSpasial() {
|
||||
const resJ = await fetch('../jalan/get_jalan.php');
|
||||
const dataJ = await resJ.json();
|
||||
L.geoJSON(dataJ, {
|
||||
style: function (f) { return { color: getColorJalan(f.properties.status), weight: 4 }; },
|
||||
onEachFeature: function (f, layer) {
|
||||
layer.bindPopup(`
|
||||
<div class="popup-content">
|
||||
<b>${f.properties.nama}</b><br>
|
||||
Status: ${f.properties.status}<br>
|
||||
Panjang: ${f.properties.panjang} m<br>
|
||||
<div class="button-group">
|
||||
<button class="btn-edit" onclick="editDataJalan(${f.properties.id}, '${f.properties.nama}', '${f.properties.status}')">Edit</button>
|
||||
<button class="btn-delete" onclick="deleteJalan(${f.properties.id})">Hapus</button>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
layer.db_id = f.properties.id;
|
||||
layer.db_type = 'jalan';
|
||||
drawnItems.addLayer(layer);
|
||||
searchableLayers.push({ nama: f.properties.nama, layer: layer, tipe: 'garis' });
|
||||
}
|
||||
});
|
||||
|
||||
const resP = await fetch('../parsil/get_parsil.php');
|
||||
const dataP = await resP.json();
|
||||
L.geoJSON(dataP, {
|
||||
style: function (f) {
|
||||
return { fillColor: getColorParsil(f.properties.status), fillOpacity: 0.6, color: "black", weight: 1 };
|
||||
},
|
||||
onEachFeature: function (f, layer) {
|
||||
layer.bindPopup(`
|
||||
<div class="popup-content">
|
||||
<b>Pemilik: ${f.properties.pemilik}</b><br>
|
||||
Status: ${f.properties.status}<br>
|
||||
Luas: ${f.properties.luas} m²<br>
|
||||
<div class="button-group">
|
||||
<button class="btn-edit" onclick="editDataParsil(${f.properties.id}, '${f.properties.pemilik}', '${f.properties.status}')">Edit</button>
|
||||
<button class="btn-delete" onclick="deleteParsil(${f.properties.id})">Hapus</button>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
layer.db_id = f.properties.id;
|
||||
layer.db_type = 'parsil';
|
||||
drawnItems.addLayer(layer);
|
||||
searchableLayers.push({ nama: f.properties.pemilik, layer: layer, tipe: 'bidang' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// LOAD BASEMAP KECAMATAN
|
||||
async function loadBasemapPontianak() {
|
||||
const adminGrp = L.layerGroup().addTo(map);
|
||||
|
||||
async function fetchGeoJSON(url) {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error(`File tidak ditemukan: ${url}`);
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
function getColorKecamatan(nama) {
|
||||
if (!nama) return "#cccccc";
|
||||
const n = nama.toUpperCase();
|
||||
if (n.includes("BARAT")) return "#e41a1c";
|
||||
if (n.includes("KOTA")) return "#377eb8";
|
||||
if (n.includes("SELATAN")) return "#4daf4a";
|
||||
if (n.includes("TENGGARA")) return "#984ea3";
|
||||
if (n.includes("TIMUR")) return "#ff7f00";
|
||||
if (n.includes("UTARA")) return "#f1c40f";
|
||||
return "#a65628";
|
||||
}
|
||||
|
||||
try {
|
||||
const dataAdmin = await fetchGeoJSON('../assets/data/Admin_Kecamatan.json');
|
||||
L.geoJSON(dataAdmin, {
|
||||
style: function (feature) {
|
||||
const namaKecamatan = feature.properties.Ket || "";
|
||||
return { color: "black", weight: 2, fillOpacity: 0.2, fillColor:getColorKecamatan(namaKecamatan) };
|
||||
},
|
||||
onEachFeature: function (feature, layer) {
|
||||
const namaKecamatan = feature.properties.Ket || "Tidak Diketahui";
|
||||
const jumlahPenduduk = feature.properties.penduduk || 0;
|
||||
const formatPenduduk = new Intl.NumberFormat('id-ID').format(jumlahPenduduk);
|
||||
|
||||
layer.bindPopup(`
|
||||
<div style="text-align: center;">
|
||||
<b>Kecamatan ${namaKecamatan}</b><br>
|
||||
<hr style="margin: 5px 0;">
|
||||
Jumlah Penduduk: <b>${formatPenduduk} Jiwa</b>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
}).addTo(adminGrp);
|
||||
} catch (e) {
|
||||
console.error("Gagal memuat peta kecamatan:", e.message);
|
||||
}
|
||||
|
||||
const baseLayers = { "OpenStreetMap": baseOsm };
|
||||
const overlays = {
|
||||
"Batas Kecamatan": adminGrp,
|
||||
"SPBU (Titik)": layerSPBU,
|
||||
"Jalan & Tanah (Mode Edit)": drawnItems
|
||||
};
|
||||
L.control.layers(baseLayers, overlays).addTo(map);
|
||||
}
|
||||
|
||||
// FUNGSI PENCARIAN DATA
|
||||
window.cariData = function() {
|
||||
const keyword = document.getElementById('searchInput').value.toLowerCase().trim();
|
||||
if (!keyword) { alert("Masukkan kata kunci pencarian!"); return; }
|
||||
|
||||
let found = false;
|
||||
for (let item of searchableLayers) {
|
||||
if (item.nama && item.nama.toLowerCase().includes(keyword)) {
|
||||
found = true;
|
||||
if (item.tipe === 'titik') { map.flyTo(item.layer.getLatLng(), 17); }
|
||||
else { map.flyToBounds(item.layer.getBounds(), { maxZoom: 17 }); }
|
||||
item.layer.openPopup();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) alert("Data tidak ditemukan! Pastikan nama SPBU, Jalan, atau Pemilik sudah benar.");
|
||||
}
|
||||
|
||||
window.handleEnter = function(e) {
|
||||
if (e.key === 'Enter') cariData();
|
||||
}
|
||||
|
||||
loadSpasial();
|
||||
loadBasemapPontianak();
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
include "../config/koneksi.php";
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>WebGIS Manajemen Spasial Kota Pontianak</title>
|
||||
|
||||
<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>
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css" />
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/leaflet-geodesic"></script>
|
||||
|
||||
<link rel="stylesheet" href="../assets/css/style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="app-container">
|
||||
<div id="sidebar">
|
||||
<h2>WebGIS Manajemen Spasial Kota Pontianak</h2>
|
||||
<h4>Tugas 03 SIG: Penerapan Layer Groups & Layer Controls</h4>
|
||||
|
||||
<h4>Pencarian Data</h4>
|
||||
<div class="search-container">
|
||||
<input type="text" id="searchInput" placeholder="Cari SPBU, Jalan, Parsil..."
|
||||
onkeypress="handleEnter(event)">
|
||||
<button onclick="cariData()">Cari Lokasi</button>
|
||||
</div>
|
||||
|
||||
<h4>Alat Digitasi</h4>
|
||||
<div id="drawControlWrapper"></div>
|
||||
|
||||
<h4>Menu Layer & Peta</h4>
|
||||
<div id="layerControlWrapper"></div>
|
||||
</div>
|
||||
|
||||
<div id="map"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const DATA_SPBU = <?php
|
||||
$query = mysqli_query($koneksi, "SELECT id, nama_spbu as nama, no_wa as wa, buka_24 as buka, latitude as lat, longitude as lng FROM spbu");
|
||||
$data = [];
|
||||
if ($query) {
|
||||
while ($row = mysqli_fetch_assoc($query)) {
|
||||
$row['lat'] = (float) $row['lat'];
|
||||
$row['lng'] = (float) $row['lng'];
|
||||
$data[] = $row;
|
||||
}
|
||||
}
|
||||
echo json_encode($data);
|
||||
?>;
|
||||
</script>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,409 @@
|
||||
let tempLat, tempLng;
|
||||
|
||||
const map = L.map('map', { zoomControl: false }).setView([-0.0263, 109.3425], 13);
|
||||
L.control.zoom({ position: 'topright' }).addTo(map);
|
||||
|
||||
const baseOsm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors'
|
||||
}).addTo(map);
|
||||
|
||||
const greenIcon = new L.Icon({
|
||||
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png',
|
||||
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
|
||||
iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41]
|
||||
});
|
||||
|
||||
const redIcon = new L.Icon({
|
||||
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
|
||||
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
|
||||
iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41]
|
||||
});
|
||||
|
||||
// Layer Groups Visual
|
||||
const layerSPBU24 = L.layerGroup().addTo(map);
|
||||
const layerSPBUNon24 = L.layerGroup().addTo(map);
|
||||
const layerJalanDB = L.layerGroup().addTo(map);
|
||||
const layerParsilDB = L.layerGroup().addTo(map);
|
||||
const drawnItems = new L.FeatureGroup().addTo(map);
|
||||
|
||||
let searchableLayers = [];
|
||||
|
||||
// SETUP LEAFLET DRAW TOOLBAR
|
||||
const drawControl = new L.Control.Draw({
|
||||
draw: {
|
||||
polyline: { shapeOptions: { color: 'blue' } },
|
||||
polygon: { shapeOptions: { color: 'orange' } },
|
||||
marker: { icon: redIcon },
|
||||
circle: false, rectangle: false, circlemarker: false
|
||||
},
|
||||
edit: { featureGroup: drawnItems }
|
||||
}).addTo(map);
|
||||
|
||||
// Memindahkan UI Draw Toolbar ke dalam Sidebar
|
||||
document.getElementById('drawControlWrapper').appendChild(drawControl.getContainer());
|
||||
|
||||
// FUNGSI RENDER DATA SPBU (DENGAN KONDISI LAYER GROUP)
|
||||
function createSPBUMarker(data) {
|
||||
const statusBuka = data.buka.trim();
|
||||
// Buka 24 jam = GreenIcon, Tidak = RedIcon
|
||||
const markerIcon = (statusBuka === 'Ya') ? greenIcon : redIcon;
|
||||
const marker = L.marker([data.lat, data.lng], { icon: markerIcon, draggable: true });
|
||||
|
||||
function updatePopupContent(id, nama, wa, buka, lat, lng, leafletId) {
|
||||
return `<div class="popup-content">
|
||||
<b>${nama}</b><br>WA: ${wa}<br>Buka 24: ${buka}<br>
|
||||
Lat: ${lat.toFixed(6)}<br>Lng: ${lng.toFixed(6)}<br>
|
||||
<div class="button-group">
|
||||
<button class="btn-edit" onclick="editData(${id}, '${nama}', '${wa}', '${buka}')">Edit</button>
|
||||
<button class="btn-delete" onclick="deleteMarker(${id}, ${leafletId})">Hapus</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
marker.bindPopup(updatePopupContent(data.id, data.nama, data.wa, data.buka, data.lat, data.lng, marker._leaflet_id));
|
||||
|
||||
marker.on('dragend', async function (e) {
|
||||
const newLatLng = marker.getLatLng();
|
||||
const fd = new FormData();
|
||||
fd.append('id', data.id); fd.append('lat', newLatLng.lat); fd.append('lng', newLatLng.lng);
|
||||
await fetch('../spbu/update_koordinat.php', { method: 'POST', body: fd });
|
||||
marker.setPopupContent(updatePopupContent(data.id, data.nama, data.wa, data.buka, newLatLng.lat, newLatLng.lng, marker._leaflet_id));
|
||||
});
|
||||
|
||||
// Memasukkan marker ke kelompok layer group yang sesuai
|
||||
if (statusBuka === 'Ya') {
|
||||
marker.addTo(layerSPBU24);
|
||||
} else {
|
||||
marker.addTo(layerSPBUNon24);
|
||||
}
|
||||
|
||||
searchableLayers.push({ nama: data.nama, layer: marker, tipe: 'titik' });
|
||||
}
|
||||
|
||||
if (typeof DATA_SPBU !== 'undefined') {
|
||||
DATA_SPBU.forEach(spbu => createSPBUMarker(spbu));
|
||||
}
|
||||
|
||||
// EVENT HANDLING LEAFLET DRAW (CREATED & EDITED)
|
||||
map.on(L.Draw.Event.CREATED, async function (e) {
|
||||
const layer = e.layer;
|
||||
const type = e.layerType;
|
||||
if (type === 'polyline') {
|
||||
const coords = layer.getLatLngs();
|
||||
let panjang = 0;
|
||||
for (let i = 0; i < coords.length - 1; i++) { panjang += coords[i].distanceTo(coords[i + 1]); }
|
||||
const nama = prompt("Nama Jalan:");
|
||||
const status = prompt("Status (Nasional/Provinsi/Kabupaten):");
|
||||
if (nama && status) {
|
||||
const fd = new FormData();
|
||||
fd.append('nama', nama); fd.append('status', status); fd.append('panjang', panjang.toFixed(2)); fd.append('koordinat', JSON.stringify(coords));
|
||||
await fetch('../jalan/tambah_jalan.php', { method: 'POST', body: fd }); location.reload();
|
||||
}
|
||||
} else if (type === 'polygon') {
|
||||
const coords = layer.getLatLngs()[0];
|
||||
const luas = L.GeometryUtil.geodesicArea(coords);
|
||||
const pemilik = prompt("Nama Pemilik:");
|
||||
const status = prompt("Status (SHM/HGB/HGU/HP):");
|
||||
if (pemilik && status) {
|
||||
const fd = new FormData();
|
||||
fd.append('pemilik', pemilik); fd.append('status', status); fd.append('luas', luas.toFixed(2)); fd.append('koordinat', JSON.stringify(coords));
|
||||
await fetch('../parsil/tambah_parsil.php', { method: 'POST', body: fd }); location.reload();
|
||||
}
|
||||
} else if (type === 'marker') {
|
||||
const latlng = layer.getLatLng();
|
||||
tempLat = latlng.lat; tempLng = latlng.lng;
|
||||
const formContent = `<div class="popup-content">
|
||||
<h4>Tambah SPBU Baru</h4>
|
||||
<input type="text" id="input_nama" placeholder="Nama SPBU">
|
||||
<input type="text" id="input_wa" placeholder="No WA">
|
||||
<select id="input_buka">
|
||||
<option value="Ya">Buka 24 Jam</option>
|
||||
<option value="Tidak">Tidak 24 Jam</option>
|
||||
</select>
|
||||
<button class="btn-save" onclick="simpanData()">Simpan</button>
|
||||
</div>`;
|
||||
L.popup().setLatLng(latlng).setContent(formContent).openOn(map);
|
||||
map.removeLayer(layer);
|
||||
}
|
||||
});
|
||||
|
||||
map.on(L.Draw.Event.EDITED, async function (e) {
|
||||
const layers = e.layers;
|
||||
layers.eachLayer(async function (layer) {
|
||||
const id = layer.db_id;
|
||||
const type = layer.db_type;
|
||||
|
||||
if (id && type) {
|
||||
let coords_str = '';
|
||||
let nilai_baru = 0;
|
||||
|
||||
if (type === 'jalan') {
|
||||
const coords = layer.getLatLngs();
|
||||
let panjang = 0;
|
||||
for (let i = 0; i < coords.length - 1; i++) { panjang += coords[i].distanceTo(coords[i + 1]); }
|
||||
nilai_baru = panjang.toFixed(2);
|
||||
coords_str = JSON.stringify(coords);
|
||||
} else if (type === 'parsil') {
|
||||
const coords = layer.getLatLngs()[0];
|
||||
nilai_baru = L.GeometryUtil.geodesicArea(coords).toFixed(2);
|
||||
coords_str = JSON.stringify(coords);
|
||||
}
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append('id', id); fd.append('type', type); fd.append('nilai', nilai_baru); fd.append('koordinat', coords_str);
|
||||
await fetch('../shared/update_geometri.php', { method: 'POST', body: fd });
|
||||
}
|
||||
});
|
||||
alert("Perubahan garis/bidang berhasil disimpan!");
|
||||
location.reload();
|
||||
});
|
||||
|
||||
// CRUD SPBU
|
||||
window.simpanData = async function() {
|
||||
const formData = new FormData();
|
||||
formData.append('nama_spbu', document.getElementById('input_nama').value);
|
||||
formData.append('no_wa', document.getElementById('input_wa').value);
|
||||
formData.append('buka_24', document.getElementById('input_buka').value);
|
||||
formData.append('latitude', tempLat); formData.append('longitude', tempLng);
|
||||
const response = await fetch('../spbu/tambah_spbu.php', { method: 'POST', body: formData });
|
||||
if ((await response.json()).status === 'success') location.reload();
|
||||
}
|
||||
|
||||
window.updateData = async function(id) {
|
||||
const formData = new FormData();
|
||||
formData.append('id', id);
|
||||
formData.append('nama', document.getElementById('edit_nama').value);
|
||||
formData.append('wa', document.getElementById('edit_wa').value);
|
||||
formData.append('buka', document.getElementById('edit_buka').value);
|
||||
const response = await fetch('../spbu/update_spbu.php', { method: 'POST', body: formData });
|
||||
if ((await response.json()).status === 'success') location.reload();
|
||||
}
|
||||
|
||||
window.deleteMarker = async function(id, leafletId) {
|
||||
if (confirm("Hapus data?")) {
|
||||
const response = await fetch(`../spbu/hapus_spbu.php?id=${id}`);
|
||||
if ((await response.json()).status === 'success') location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
window.editData = function(id, nama, wa, buka) {
|
||||
const editForm = `<div class="popup-content">
|
||||
<h4>Edit SPBU</h4>
|
||||
<input type="text" id="edit_nama" value="${nama}">
|
||||
<input type="text" id="edit_wa" value="${wa}">
|
||||
<select id="edit_buka">
|
||||
<option value="Ya" ${buka === 'Ya' ? 'selected' : ''}>Buka 24 Jam</option>
|
||||
<option value="Tidak" ${buka === 'Tidak' ? 'selected' : ''}>Tidak 24 Jam</option>
|
||||
</select>
|
||||
<button class="btn-save" onclick="updateData(${id})">Update Data</button>
|
||||
</div>`;
|
||||
map.closePopup();
|
||||
L.popup().setLatLng(map.getCenter()).setContent(editForm).openOn(map);
|
||||
}
|
||||
|
||||
// CRUD JALAN & PARSIL
|
||||
window.editDataJalan = function(id, nama, status) {
|
||||
const editForm = `<div class="popup-content">
|
||||
<h4>Edit Jalan</h4>
|
||||
<input type="text" id="edit_nama_jalan" value="${nama}">
|
||||
<select id="edit_status_jalan">
|
||||
<option value="Nasional" ${status === 'Nasional' ? 'selected' : ''}>Nasional</option>
|
||||
<option value="Provinsi" ${status === 'Provinsi' ? 'selected' : ''}>Provinsi</option>
|
||||
<option value="Kabupaten" ${status === 'Kabupaten' ? 'selected' : ''}>Kabupaten</option>
|
||||
</select>
|
||||
<button class="btn-save" onclick="updateDataJalan(${id})">Update Jalan</button>
|
||||
</div>`;
|
||||
map.closePopup();
|
||||
L.popup().setLatLng(map.getCenter()).setContent(editForm).openOn(map);
|
||||
}
|
||||
|
||||
window.updateDataJalan = async function(id) {
|
||||
const fd = new FormData();
|
||||
fd.append('id', id); fd.append('nama', document.getElementById('edit_nama_jalan').value); fd.append('status', document.getElementById('edit_status_jalan').value);
|
||||
const response = await fetch('../jalan/update_jalan.php', { method: 'POST', body: fd });
|
||||
if ((await response.json()).status === 'success') location.reload();
|
||||
}
|
||||
|
||||
window.deleteJalan = async function(id) {
|
||||
if (confirm("Hapus data jalan ini?")) {
|
||||
const response = await fetch(`../jalan/hapus_jalan.php?id=${id}`);
|
||||
if ((await response.json()).status === 'success') location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
window.editDataParsil = function(id, pemilik, status) {
|
||||
const editForm = `<div class="popup-content">
|
||||
<h4>Edit Parsil</h4>
|
||||
<input type="text" id="edit_pemilik_parsil" value="${pemilik}">
|
||||
<select id="edit_status_parsil">
|
||||
<option value="SHM" ${status === 'SHM' ? 'selected' : ''}>SHM</option>
|
||||
<option value="HGB" ${status === 'HGB' ? 'selected' : ''}>HGB</option>
|
||||
<option value="HGU" ${status === 'HGU' ? 'selected' : ''}>HGU</option>
|
||||
<option value="HP" ${status === 'HP' ? 'selected' : ''}>HP</option>
|
||||
</select>
|
||||
<button class="btn-save" onclick="updateDataParsil(${id})">Update Parsil</button>
|
||||
</div>`;
|
||||
map.closePopup();
|
||||
L.popup().setLatLng(map.getCenter()).setContent(editForm).openOn(map);
|
||||
}
|
||||
|
||||
window.updateDataParsil = async function(id) {
|
||||
const fd = new FormData();
|
||||
fd.append('id', id); fd.append('pemilik', document.getElementById('edit_pemilik_parsil').value); fd.append('status', document.getElementById('edit_status_parsil').value);
|
||||
const response = await fetch('../parsil/update_parsil.php', { method: 'POST', body: fd });
|
||||
if ((await response.json()).status === 'success') location.reload();
|
||||
}
|
||||
|
||||
window.deleteParsil = async function(id) {
|
||||
if (confirm("Hapus data parsil ini?")) {
|
||||
const response = await fetch(`../parsil/hapus_parsil.php?id=${id}`);
|
||||
if ((await response.json()).status === 'success') location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
// RENDER DATA SPASIAL (DIKUNCI KE LAYER GROUP)
|
||||
function getColorJalan(status) { return status === 'Nasional' ? 'red' : status === 'Provinsi' ? 'blue' : 'green'; }
|
||||
function getColorParsil(status) {
|
||||
const colors = { 'SHM': '#8B4513', 'HGB': '#FFD700', 'HGU': '#ADFF2F', 'HP': '#87CEEB' };
|
||||
return colors[status] || '#CCCCCC';
|
||||
}
|
||||
|
||||
async function loadSpasial() {
|
||||
const resJ = await fetch('../jalan/get_jalan.php');
|
||||
const dataJ = await resJ.json();
|
||||
L.geoJSON(dataJ, {
|
||||
style: function (f) { return { color: getColorJalan(f.properties.status), weight: 4 }; },
|
||||
onEachFeature: function (f, layer) {
|
||||
layer.bindPopup(`
|
||||
<div class="popup-content">
|
||||
<b>${f.properties.nama}</b><br>
|
||||
Status: ${f.properties.status}<br>
|
||||
Panjang: ${f.properties.panjang} m<br>
|
||||
<div class="button-group">
|
||||
<button class="btn-edit" onclick="editDataJalan(${f.properties.id}, '${f.properties.nama}', '${f.properties.status}')">Edit</button>
|
||||
<button class="btn-delete" onclick="deleteJalan(${f.properties.id})">Hapus</button>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
layer.db_id = f.properties.id; layer.db_type = 'jalan';
|
||||
drawnItems.addLayer(layer);
|
||||
searchableLayers.push({ nama: f.properties.nama, layer: layer, tipe: 'garis' });
|
||||
}
|
||||
}).addTo(layerJalanDB); // Memasukkan ke Layer Group Jalan
|
||||
|
||||
const resP = await fetch('../parsil/get_parsil.php');
|
||||
const dataP = await resP.json();
|
||||
L.geoJSON(dataP, {
|
||||
style: function (f) {
|
||||
return { fillColor: getColorParsil(f.properties.status), fillOpacity: 0.6, color: "black", weight: 1 };
|
||||
},
|
||||
onEachFeature: function (f, layer) {
|
||||
layer.bindPopup(`
|
||||
<div class="popup-content">
|
||||
<b>Pemilik: ${f.properties.pemilik}</b><br>
|
||||
Status: ${f.properties.status}<br>
|
||||
Luas: ${f.properties.luas} m²<br>
|
||||
<div class="button-group">
|
||||
<button class="btn-edit" onclick="editDataParsil(${f.properties.id}, '${f.properties.pemilik}', '${f.properties.status}')">Edit</button>
|
||||
<button class="btn-delete" onclick="deleteParsil(${f.properties.id})">Hapus</button>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
layer.db_id = f.properties.id; layer.db_type = 'parsil';
|
||||
drawnItems.addLayer(layer);
|
||||
searchableLayers.push({ nama: f.properties.pemilik, layer: layer, tipe: 'bidang' });
|
||||
}
|
||||
}).addTo(layerParsilDB); // Memasukkan ke Layer Group Parsil
|
||||
}
|
||||
|
||||
// MUAT BASEMAP KECAMATAN & MENU LAYER INTERAKTIF SIDEBAR
|
||||
async function loadBasemapPontianak() {
|
||||
const adminGrp = L.layerGroup().addTo(map);
|
||||
|
||||
async function fetchGeoJSON(url) {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error(`File tidak ditemukan: ${url}`);
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
function getColorKecamatan(nama) {
|
||||
if (!nama) return "#cccccc";
|
||||
const n = nama.toUpperCase();
|
||||
if (n.includes("BARAT")) return "#e41a1c";
|
||||
if (n.includes("KOTA")) return "#377eb8";
|
||||
if (n.includes("SELATAN")) return "#4daf4a";
|
||||
if (n.includes("TENGGARA")) return "#984ea3";
|
||||
if (n.includes("TIMUR")) return "#ff7f00";
|
||||
if (n.includes("UTARA")) return "#f1c40f";
|
||||
return "#a65628";
|
||||
}
|
||||
|
||||
try {
|
||||
const dataAdmin = await fetchGeoJSON('../assets/data/Admin_Kecamatan.json');
|
||||
L.geoJSON(dataAdmin, {
|
||||
style: function (feature) {
|
||||
const namaKecamatan = feature.properties.Ket || "";
|
||||
return { color: "black", weight: 2, fillOpacity: 0.2, fillColor: getColorKecamatan(namaKecamatan) };
|
||||
},
|
||||
onEachFeature: function (feature, layer) {
|
||||
const namaKecamatan = feature.properties.Ket || "Tidak Diketahui";
|
||||
const jumlahPenduduk = feature.properties.penduduk || 0;
|
||||
const formatPenduduk = new Intl.NumberFormat('id-ID').format(jumlahPenduduk);
|
||||
|
||||
layer.bindPopup(`
|
||||
<div style="text-align: center;">
|
||||
<b>Kecamatan ${namaKecamatan}</b><br>
|
||||
<hr style="margin: 5px 0;">
|
||||
Jumlah Penduduk: <b>${formatPenduduk} Jiwa</b>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
}).addTo(adminGrp);
|
||||
|
||||
} catch (e) {
|
||||
console.error("Gagal memuat peta kecamatan:", e.message);
|
||||
}
|
||||
|
||||
const baseLayers = { "OpenStreetMap": baseOsm };
|
||||
|
||||
// Grouping Overlays
|
||||
const overlays = {
|
||||
"Batas Kecamatan": adminGrp,
|
||||
"SPBU Buka 24 Jam": layerSPBU24,
|
||||
"SPBU Tidak Buka 24 Jam": layerSPBUNon24,
|
||||
"Data Jalan (Polyline)": layerJalanDB,
|
||||
"Data Parsil (Polygon)": layerParsilDB
|
||||
};
|
||||
|
||||
// Membuat menu kontrol layer selalu terbuka (collapsed: false) dan dimasukkan ke sidebar
|
||||
const layerControl = L.control.layers(baseLayers, overlays, { collapsed: false }).addTo(map);
|
||||
document.getElementById('layerControlWrapper').appendChild(layerControl.getContainer());
|
||||
|
||||
// Memaksa peta kalkulasi ulang ukurannya di dalam layout CSS Flexbox
|
||||
setTimeout(function () { map.invalidateSize(); }, 500);
|
||||
}
|
||||
|
||||
// FUNGSI PENCARIAN DATA
|
||||
window.cariData = function() {
|
||||
const keyword = document.getElementById('searchInput').value.toLowerCase().trim();
|
||||
if (!keyword) { alert("Masukkan kata kunci pencarian!"); return; }
|
||||
|
||||
let found = false;
|
||||
for (let item of searchableLayers) {
|
||||
if (item.nama && item.nama.toLowerCase().includes(keyword)) {
|
||||
found = true;
|
||||
if (item.tipe === 'titik') { map.flyTo(item.layer.getLatLng(), 17); }
|
||||
else { map.flyToBounds(item.layer.getBounds(), { maxZoom: 17 }); }
|
||||
item.layer.openPopup();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) alert("Data tidak ditemukan! Pastikan nama SPBU, Jalan, atau Pemilik sudah benar.");
|
||||
}
|
||||
|
||||
window.handleEnter = function(e) {
|
||||
if (e.key === 'Enter') cariData();
|
||||
}
|
||||
|
||||
loadSpasial();
|
||||
loadBasemapPontianak();
|
||||
Reference in New Issue
Block a user