mirror of
https://github.com/rekywhyd/gis_tugas.git
synced 2026-07-06 18:03:07 +00:00
first commit
This commit is contained in:
+10
@@ -0,0 +1,10 @@
|
||||
FROM php:8.2-apache
|
||||
|
||||
# Install mysqli extension
|
||||
RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli
|
||||
|
||||
# Copy application files to the web server root
|
||||
COPY . /var/www/html/
|
||||
|
||||
# Expose web port
|
||||
EXPOSE 80
|
||||
@@ -0,0 +1,33 @@
|
||||
DROP DATABASE IF EXISTS gis_tugas;
|
||||
CREATE DATABASE gis_tugas;
|
||||
USE gis_tugas;
|
||||
|
||||
-- From gis_point
|
||||
CREATE TABLE data_spbu (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nama_spbu VARCHAR(255) NOT NULL,
|
||||
alamat TEXT NOT NULL,
|
||||
latitude DOUBLE NOT NULL,
|
||||
longitude DOUBLE NOT NULL,
|
||||
jenis_layanan ENUM('24 Jam', 'Reguler') NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- From gis_polyline (and gis_polygon)
|
||||
CREATE TABLE data_jalan (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nama_jalan VARCHAR(255) NOT NULL,
|
||||
status_jalan ENUM('Nasional', 'Provinsi', 'Kabupaten') NOT NULL,
|
||||
geojson_data TEXT NOT NULL,
|
||||
panjang_jalan FLOAT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- From gis_polygon
|
||||
CREATE TABLE data_parsil (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nomor_parsil VARCHAR(100),
|
||||
status_sertifikat ENUM('SHM', 'HGB', 'HGU', 'HP'),
|
||||
geojson_data TEXT,
|
||||
luas_m2 FLOAT
|
||||
);
|
||||
@@ -0,0 +1,28 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
web:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
environment:
|
||||
- DB_HOST=db
|
||||
- DB_USER=root
|
||||
- DB_PASS=coolify_gis_secure_pass
|
||||
- DB_NAME=gis_tugas
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
db:
|
||||
image: mysql:8.0
|
||||
restart: always
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=coolify_gis_secure_pass
|
||||
- MYSQL_DATABASE=gis_tugas
|
||||
volumes:
|
||||
- db-data:/var/lib/mysql
|
||||
# Automatically initialize database schema on first run
|
||||
- ./database.sql:/docker-entrypoint-initdb.d/database.sql:ro
|
||||
|
||||
volumes:
|
||||
db-data:
|
||||
@@ -0,0 +1,322 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>GeoPortal - Dashboard WebGIS</title>
|
||||
<!-- Google Fonts: Inter & Outfit -->
|
||||
<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@300;400;500;600;700&family=Outfit:wght@400;600;700;800&display=swap" rel="stylesheet">
|
||||
<!-- FontAwesome for Premium Icons -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--bg-color: #0b0f19;
|
||||
--card-bg: rgba(255, 255, 255, 0.03);
|
||||
--card-border: rgba(255, 255, 255, 0.08);
|
||||
--text-primary: #f3f4f6;
|
||||
--text-secondary: #9ca3af;
|
||||
--accent-primary: #3b82f6; /* Modern Blue */
|
||||
--accent-point: #ec4899; /* Vibrant Pink/Red */
|
||||
--accent-polyline: #8b5cf6; /* Vibrant Purple */
|
||||
--accent-polygon: #10b981; /* Vibrant Green */
|
||||
--glow-point: rgba(236, 72, 153, 0.15);
|
||||
--glow-polyline: rgba(139, 92, 246, 0.15);
|
||||
--glow-polygon: rgba(16, 185, 129, 0.15);
|
||||
--glow-kesra: rgba(59, 130, 246, 0.15);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-primary);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Abstract Premium Background Shapes */
|
||||
body::before, body::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
border-radius: 50%;
|
||||
filter: blur(150px);
|
||||
z-index: -1;
|
||||
opacity: 0.4;
|
||||
}
|
||||
body::before {
|
||||
top: -100px;
|
||||
left: -100px;
|
||||
background: radial-gradient(circle, var(--accent-polyline) 0%, transparent 80%);
|
||||
}
|
||||
body::after {
|
||||
bottom: -100px;
|
||||
right: -100px;
|
||||
background: radial-gradient(circle, var(--accent-primary) 0%, transparent 80%);
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 3rem 2rem 1rem 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: 'Outfit', sans-serif;
|
||||
font-size: 2.8rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.05em;
|
||||
background: linear-gradient(135deg, #ffffff 30%, #a5b4fc 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.1rem;
|
||||
color: var(--text-secondary);
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
main {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 1.5rem;
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 20px;
|
||||
padding: 2.5rem 2rem;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.05) 0%, transparent 100%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s ease;
|
||||
}
|
||||
|
||||
.card:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Specific card color themes */
|
||||
.card-kesra {
|
||||
--theme-color: var(--accent-primary);
|
||||
--theme-glow: var(--glow-kesra);
|
||||
}
|
||||
.card-point {
|
||||
--theme-color: var(--accent-point);
|
||||
--theme-glow: var(--glow-point);
|
||||
}
|
||||
.card-polyline {
|
||||
--theme-color: var(--accent-polyline);
|
||||
--theme-glow: var(--glow-polyline);
|
||||
}
|
||||
.card-polygon {
|
||||
--theme-color: var(--accent-polygon);
|
||||
--theme-glow: var(--glow-polygon);
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-8px);
|
||||
border-color: var(--theme-color);
|
||||
box-shadow: 0 20px 40px -15px var(--theme-glow);
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid var(--card-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 2rem;
|
||||
color: var(--theme-color);
|
||||
transition: all 0.4s ease;
|
||||
}
|
||||
|
||||
.card:hover .icon-wrapper {
|
||||
background: var(--theme-color);
|
||||
color: var(--bg-color);
|
||||
box-shadow: 0 0 20px var(--theme-color);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
font-family: 'Outfit', sans-serif;
|
||||
font-size: 1.6rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.75rem;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.card:hover h2 {
|
||||
color: var(--theme-color);
|
||||
}
|
||||
|
||||
.card p {
|
||||
font-size: 0.95rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.5;
|
||||
margin-bottom: 2rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid var(--card-border);
|
||||
color: var(--text-primary);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.card:hover .btn {
|
||||
background: var(--theme-color);
|
||||
color: var(--bg-color);
|
||||
border-color: var(--theme-color);
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: var(--accent-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
h1 {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
.grid-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<h1>Sistem Informasi Geografis</h1>
|
||||
<p class="subtitle">Platform visualisasi data pemetaan berbasis spasial.</p>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="grid-container">
|
||||
<!-- Kesra Card -->
|
||||
<a href="http://wg84wo0wsocccs4csg8w88ks.203.24.51.230.sslip.io/" class="card card-kesra" target="_blank" rel="noopener noreferrer">
|
||||
<div class="icon-wrapper">
|
||||
<i class="fa-solid fa-hand-holding-heart"></i>
|
||||
</div>
|
||||
<h2>WebGIS Kesra</h2>
|
||||
<p>Sistem Informasi Geografis terpadu untuk mendata, memetakan, dan menyalurkan bantuan kepada warga yang membutuhkan melalui jaringan penyalur yang tepercaya.</p>
|
||||
<div class="btn">
|
||||
Buka Modul <i class="fa-solid fa-arrow-right-from-bracket"></i>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Point Card -->
|
||||
<a href="point/" class="card card-point">
|
||||
<div class="icon-wrapper">
|
||||
<i class="fa-solid fa-location-dot"></i>
|
||||
</div>
|
||||
<h2>GIS Point</h2>
|
||||
<p>Visualisasi dan pengelolaan data lokasi berbentuk titik koordinat seperti tempat wisata, gedung, atau fasilitas umum.</p>
|
||||
<div class="btn">
|
||||
Buka Modul <i class="fa-solid fa-arrow-right"></i>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Polyline Card -->
|
||||
<a href="polyline/" class="card card-polyline">
|
||||
<div class="icon-wrapper">
|
||||
<i class="fa-solid fa-route"></i>
|
||||
</div>
|
||||
<h2>GIS Polyline</h2>
|
||||
<p>Visualisasi jalur, rute jalan, batas garis lintang, sungai, atau jaringan transportasi menggunakan garis penghubung.</p>
|
||||
<div class="btn">
|
||||
Buka Modul <i class="fa-solid fa-arrow-right"></i>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Polygon Card -->
|
||||
<a href="polygon/" class="card card-polygon">
|
||||
<div class="icon-wrapper">
|
||||
<i class="fa-solid fa-draw-polygon"></i>
|
||||
</div>
|
||||
<h2>GIS Polygon</h2>
|
||||
<p>Visualisasi wilayah, area zonasi teritorial, batas administrasi kelurahan, daerah rawan, maupun luasan lahan.</p>
|
||||
<div class="btn">
|
||||
Buka Modul <i class="fa-solid fa-arrow-right"></i>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2026 WebGIS Application. Deployed with <i class="fa-solid fa-heart" style="color: #ec4899;"></i> using Coolify.</p>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
$host = getenv('DB_HOST') ?: "localhost";
|
||||
$user = getenv('DB_USER') ?: "root";
|
||||
$pass = getenv('DB_PASS') ?: "";
|
||||
$name = getenv('DB_NAME') ?: "gis_tugas";
|
||||
|
||||
$conn = mysqli_connect($host, $user, $pass, $name);
|
||||
if (!$conn) { die("Koneksi gagal: " . mysqli_connect_error()); }
|
||||
?>
|
||||
+366
@@ -0,0 +1,366 @@
|
||||
<?php include 'config.php'; ?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>WebGIS SPBU - Layer Groups & Control</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css"/>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
||||
|
||||
:root {
|
||||
--primary-navy: #0F2027; /* Gelap & Elegan */
|
||||
--secondary-navy: #203A43;
|
||||
--accent-blue: #2C5364;
|
||||
--bg-color: #F8FAFC;
|
||||
--card-bg: #FFFFFF;
|
||||
--text-main: #1E293B;
|
||||
--text-muted: #64748B;
|
||||
--border-color: #E2E8F0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
margin: 0;
|
||||
background: var(--bg-color);
|
||||
color: var(--text-main);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, var(--primary-navy), var(--secondary-navy));
|
||||
color: white;
|
||||
padding: 20px 30px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header h2 {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 30px auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.panel, .table-container {
|
||||
background: var(--card-bg);
|
||||
padding: 25px 30px;
|
||||
border-radius: 16px;
|
||||
margin-bottom: 25px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
|
||||
border: 1px solid var(--border-color);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.panel:hover, .table-container:hover {
|
||||
box-shadow: 0 12px 20px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -2px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.panel h3, .table-container h3 {
|
||||
color: var(--primary-navy);
|
||||
font-weight: 700;
|
||||
margin-top: 0;
|
||||
font-size: 18px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-inline {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.input-group strong {
|
||||
font-size: 14px;
|
||||
color: var(--secondary-navy);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
input, select {
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 14px;
|
||||
color: var(--text-main);
|
||||
background-color: var(--bg-color);
|
||||
transition: all 0.3s ease;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input:focus, select:focus {
|
||||
border-color: var(--accent-blue);
|
||||
box-shadow: 0 0 0 3px rgba(44, 83, 100, 0.15);
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
small {
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
width: 100%;
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#map {
|
||||
height: 550px;
|
||||
width: 100%;
|
||||
border-radius: 16px;
|
||||
border: none;
|
||||
box-shadow: 0 10px 25px rgba(15, 32, 39, 0.1);
|
||||
z-index: 1;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
/* Tabel Styling */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
margin-top: 15px;
|
||||
}
|
||||
th, td {
|
||||
padding: 16px 20px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
th {
|
||||
background: linear-gradient(135deg, var(--primary-navy), var(--secondary-navy));
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.8px;
|
||||
}
|
||||
th:first-child { border-top-left-radius: 10px; }
|
||||
th:last-child { border-top-right-radius: 10px; }
|
||||
|
||||
tbody tr {
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
tbody tr:hover {
|
||||
background-color: #F1F5F9;
|
||||
}
|
||||
|
||||
/* Badges & Icons */
|
||||
.badge-24h {
|
||||
background: rgba(16, 185, 129, 0.1);
|
||||
color: #059669;
|
||||
padding: 6px 14px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
border: 1px solid rgba(16, 185, 129, 0.2);
|
||||
display: inline-block;
|
||||
}
|
||||
.badge-reg {
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
color: #D97706;
|
||||
padding: 6px 14px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
border: 1px solid rgba(245, 158, 11, 0.2);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.icon-24h {
|
||||
background-color: #10B981;
|
||||
border-radius: 50% 50% 50% 0;
|
||||
border: 2px solid #fff;
|
||||
width: 24px !important; height: 24px !important;
|
||||
transform: rotate(-45deg);
|
||||
margin-left: -12px; margin-top: -12px;
|
||||
box-shadow: 2px 2px 6px rgba(0,0,0,0.3);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
.icon-reg {
|
||||
background-color: #F59E0B;
|
||||
border-radius: 50% 50% 50% 0;
|
||||
border: 2px solid #fff;
|
||||
width: 24px !important; height: 24px !important;
|
||||
transform: rotate(-45deg);
|
||||
margin-left: -12px; margin-top: -12px;
|
||||
box-shadow: 2px 2px 6px rgba(0,0,0,0.3);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
/* Leaflet Overrides */
|
||||
.leaflet-control-layers, .leaflet-bar {
|
||||
border: none !important;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.1) !important;
|
||||
border-radius: 12px !important;
|
||||
}
|
||||
.leaflet-control-layers { padding: 8px !important; font-family: 'Inter', sans-serif; }
|
||||
.leaflet-bar { overflow: hidden; }
|
||||
.leaflet-bar a {
|
||||
border-bottom: 1px solid #eee !important;
|
||||
color: var(--primary-navy) !important;
|
||||
width: 32px !important;
|
||||
height: 32px !important;
|
||||
line-height: 32px !important;
|
||||
}
|
||||
.leaflet-bar a:hover {
|
||||
background-color: #f8f9fa !important;
|
||||
color: var(--accent-blue) !important;
|
||||
}
|
||||
.leaflet-popup-content-wrapper {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
.leaflet-popup-content { font-family: 'Inter', sans-serif; font-size: 14px; line-height: 1.5; }
|
||||
.leaflet-popup-content b { color: var(--primary-navy); font-size: 15px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h2>📍 Manajemen SPBU (Point)</h2>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<div class="panel">
|
||||
<h3>Input Data Baru</h3>
|
||||
<div class="form-inline">
|
||||
<div class="input-group">
|
||||
<strong>Nama SPBU:</strong>
|
||||
<input type="text" id="nama_spbu" placeholder="Masukkan Nama SPBU">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<strong>Alamat:</strong>
|
||||
<input type="text" id="alamat_spbu" placeholder="Masukkan Alamat">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<strong>Jenis Layanan:</strong>
|
||||
<select id="jenis_layanan">
|
||||
<option value="24 Jam">Buka 24 Jam</option>
|
||||
<option value="Reguler">Reguler (Tidak 24 Jam)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<small>Pilih jenis layanan, lalu gunakan ikon marker di kiri peta untuk meletakkan titik.</small>
|
||||
</div>
|
||||
|
||||
<div id="map"></div>
|
||||
|
||||
<div class="table-container">
|
||||
<h3>Data Informasi</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nama SPBU</th>
|
||||
<th>Alamat</th>
|
||||
<th>Status</th>
|
||||
<th>Koordinat</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$res = mysqli_query($conn, "SELECT * FROM data_spbu ORDER BY id DESC");
|
||||
while($row = mysqli_fetch_assoc($res)) {
|
||||
$badge = ($row['jenis_layanan'] == '24 Jam') ? 'badge-24h' : 'badge-reg';
|
||||
echo "<tr>
|
||||
<td>{$row['nama_spbu']}</td>
|
||||
<td>{$row['alamat']}</td>
|
||||
<td><span class='$badge'>{$row['jenis_layanan']}</span></td>
|
||||
<td>{$row['latitude']}, {$row['longitude']}</td>
|
||||
</tr>";
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
|
||||
|
||||
<script>
|
||||
// 1. Setup Peta & Base Maps
|
||||
var osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OSM' });
|
||||
var sat = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', { attribution: 'Esri' });
|
||||
|
||||
var map = L.map('map', {
|
||||
center: [-0.02, 109.34],
|
||||
zoom: 13,
|
||||
layers: [osm]
|
||||
});
|
||||
|
||||
// 2. Definisi Layer Groups
|
||||
var group24Jam = L.layerGroup().addTo(map);
|
||||
var groupReguler = L.layerGroup().addTo(map);
|
||||
|
||||
// 3. Layers Control (Checkbox di Pojok Kanan Atas)
|
||||
var baseMaps = { "Peta Standar": osm, "Satelit": sat };
|
||||
var overlayMaps = {
|
||||
"<span style='color:green'>SPBU 24 Jam</span>": group24Jam,
|
||||
"<span style='color:orange'>SPBU Reguler</span>": groupReguler
|
||||
};
|
||||
L.control.layers(baseMaps, overlayMaps, { collapsed: false }).addTo(map);
|
||||
|
||||
// 4. Custom Icons
|
||||
var icon24h = L.divIcon({className: 'icon-24h'});
|
||||
var iconReg = L.divIcon({className: 'icon-reg'});
|
||||
|
||||
// 5. Input Data (Drawing)
|
||||
var drawnItems = new L.FeatureGroup();
|
||||
map.addLayer(drawnItems);
|
||||
new L.Control.Draw({
|
||||
draw: { polyline:false, polygon:false, circle:false, rectangle:false, circlemarker:false, marker:true }
|
||||
}).addTo(map);
|
||||
|
||||
map.on(L.Draw.Event.CREATED, function (e) {
|
||||
var layer = e.layer;
|
||||
var ll = layer.getLatLng();
|
||||
var nama = document.getElementById('nama_spbu').value;
|
||||
var alamat = document.getElementById('alamat_spbu').value;
|
||||
var jenis = document.getElementById('jenis_layanan').value;
|
||||
|
||||
if(!nama || !alamat) return alert("Lengkapi data!");
|
||||
|
||||
var fd = new FormData();
|
||||
fd.append('nama', nama); fd.append('alamat', alamat);
|
||||
fd.append('lat', ll.lat); fd.append('lng', ll.lng); fd.append('jenis', jenis);
|
||||
|
||||
fetch('save.php', { method: 'POST', body: fd })
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if(data.status === 'success') location.reload();
|
||||
});
|
||||
});
|
||||
|
||||
// 6. Tampilkan Marker dari Database dengan Warna Berbeda
|
||||
<?php
|
||||
$res = mysqli_query($conn, "SELECT * FROM data_spbu");
|
||||
while($row = mysqli_fetch_assoc($res)) {
|
||||
$j = $row['jenis_layanan'];
|
||||
$target = ($j == '24 Jam') ? 'group24Jam' : 'groupReguler';
|
||||
$icon = ($j == '24 Jam') ? 'icon24h' : 'iconReg';
|
||||
|
||||
echo "L.marker([{$row['latitude']}, {$row['longitude']}], {icon: $icon}).addTo($target)
|
||||
.bindPopup('<b>{$row['nama_spbu']}</b><br>{$row['alamat']}');\n";
|
||||
}
|
||||
?>
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
include 'config.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
$nama = mysqli_real_escape_string($conn, $_POST['nama']);
|
||||
$alamat = mysqli_real_escape_string($conn, $_POST['alamat']);
|
||||
$lat = $_POST['lat'];
|
||||
$lng = $_POST['lng'];
|
||||
$jenis = $_POST['jenis'];
|
||||
|
||||
$query = "INSERT INTO data_spbu (nama_spbu, alamat, latitude, longitude, jenis_layanan)
|
||||
VALUES ('$nama', '$alamat', '$lat', '$lng', '$jenis')";
|
||||
|
||||
if (mysqli_query($conn, $query)) {
|
||||
echo json_encode(['status' => 'success']);
|
||||
} else {
|
||||
echo json_encode(['status' => 'error']);
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
$db_host = getenv('DB_HOST') ?: 'localhost';
|
||||
$db_user = getenv('DB_USER') ?: 'root';
|
||||
$db_pass = getenv('DB_PASS') ?: '';
|
||||
$db_name = getenv('DB_NAME') ?: 'gis_tugas';
|
||||
|
||||
$conn = mysqli_connect($db_host, $db_user, $db_pass, $db_name);
|
||||
if (!$conn) { die("Koneksi gagal: " . mysqli_connect_error()); }
|
||||
?>
|
||||
@@ -0,0 +1,267 @@
|
||||
<?php include 'config.php'; ?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebGIS Jalan & Parsil</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css"/>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
||||
|
||||
:root {
|
||||
--primary-navy: #0F2027; /* Gelap & Elegan */
|
||||
--secondary-navy: #203A43;
|
||||
--accent-blue: #2C5364;
|
||||
--bg-color: #F8FAFC;
|
||||
--card-bg: #FFFFFF;
|
||||
--text-main: #1E293B;
|
||||
--text-muted: #64748B;
|
||||
--border-color: #E2E8F0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
margin: 0;
|
||||
background: var(--bg-color);
|
||||
color: var(--text-main);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, var(--primary-navy), var(--secondary-navy));
|
||||
color: white;
|
||||
padding: 20px 30px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header h2 {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 30px auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.control-panel, .panel, .table-container {
|
||||
background: var(--card-bg);
|
||||
padding: 25px 30px;
|
||||
border-radius: 16px;
|
||||
margin-bottom: 25px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
|
||||
border: 1px solid var(--border-color);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.control-panel:hover, .panel:hover, .table-container:hover {
|
||||
box-shadow: 0 12px 20px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -2px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.input-group strong {
|
||||
font-size: 14px;
|
||||
color: var(--secondary-navy);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
input, select {
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 14px;
|
||||
color: var(--text-main);
|
||||
background-color: var(--bg-color);
|
||||
transition: all 0.3s ease;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input:focus, select:focus {
|
||||
border-color: var(--accent-blue);
|
||||
box-shadow: 0 0 0 3px rgba(44, 83, 100, 0.15);
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
#map {
|
||||
height: 600px;
|
||||
width: 100%;
|
||||
border-radius: 16px;
|
||||
border: none;
|
||||
box-shadow: 0 10px 25px rgba(15, 32, 39, 0.1);
|
||||
z-index: 1;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
/* Leaflet Overrides */
|
||||
.leaflet-control-layers, .leaflet-bar {
|
||||
border: none !important;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.1) !important;
|
||||
border-radius: 12px !important;
|
||||
}
|
||||
.leaflet-control-layers { padding: 8px !important; font-family: 'Inter', sans-serif; }
|
||||
.leaflet-bar { overflow: hidden; }
|
||||
.leaflet-bar a {
|
||||
border-bottom: 1px solid #eee !important;
|
||||
color: var(--primary-navy) !important;
|
||||
width: 32px !important;
|
||||
height: 32px !important;
|
||||
line-height: 32px !important;
|
||||
}
|
||||
.leaflet-bar a:hover {
|
||||
background-color: #f8f9fa !important;
|
||||
color: var(--accent-blue) !important;
|
||||
}
|
||||
.leaflet-popup-content-wrapper {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
.leaflet-popup-content { font-family: 'Inter', sans-serif; font-size: 14px; line-height: 1.5; }
|
||||
.leaflet-popup-content b { color: var(--primary-navy); font-size: 15px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="header">
|
||||
<h2>🗺️ Manajemen Data Geospasial (Polygon & Polyline)</h2>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="control-panel">
|
||||
<div class="input-group">
|
||||
<strong>Mode Operasi:</strong>
|
||||
<select id="mode_input">
|
||||
<option value="jalan">Manajemen Jalan (Line)</option>
|
||||
<option value="parsil">Parsil Tanah (Polygon)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<strong>Identitas Data:</strong>
|
||||
<input type="text" id="nama_input" placeholder="Masukkan Nama Jalan / No Parsil">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<strong>Status Kepemilikan/Jalan:</strong>
|
||||
<select id="status_input"></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="map"></div>
|
||||
</div>
|
||||
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@turf/turf@6/turf.min.js"></script>
|
||||
|
||||
<script>
|
||||
var map = L.map('map').setView([-0.02, 109.34], 14);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
|
||||
|
||||
// Konfigurasi Status & Warna
|
||||
const config = {
|
||||
jalan: {
|
||||
status: ['Nasional', 'Provinsi', 'Kabupaten'],
|
||||
warna: { 'Nasional': 'red', 'Provinsi': 'blue', 'Kabupaten': 'green' }
|
||||
},
|
||||
parsil: {
|
||||
status: ['SHM', 'HGB', 'HGU', 'HP'],
|
||||
warna: { 'SHM': '#f1c40f', 'HGB': '#9b59b6', 'HGU': '#e67e22', 'HP': '#1abc9c' }
|
||||
}
|
||||
};
|
||||
|
||||
// Update Dropdown Status Otomatis
|
||||
const modeSelect = document.getElementById('mode_input');
|
||||
const statusSelect = document.getElementById('status_input');
|
||||
|
||||
function updateStatusOptions() {
|
||||
const mode = modeSelect.value;
|
||||
statusSelect.innerHTML = config[mode].status.map(s => `<option value="${s}">${s}</option>`).join('');
|
||||
}
|
||||
modeSelect.onchange = updateStatusOptions;
|
||||
updateStatusOptions();
|
||||
|
||||
// Fitur Drawing
|
||||
var drawnItems = new L.FeatureGroup().addTo(map);
|
||||
var drawControl = new L.Control.Draw({
|
||||
draw: { circle: false, marker: false, circlemarker: false, rectangle: true, polyline: true, polygon: true },
|
||||
edit: { featureGroup: drawnItems }
|
||||
});
|
||||
map.addControl(drawControl);
|
||||
|
||||
map.on(L.Draw.Event.CREATED, function (e) {
|
||||
var layer = e.layer;
|
||||
var mode = modeSelect.value;
|
||||
var nama = document.getElementById('nama_input').value;
|
||||
var status = statusSelect.value;
|
||||
|
||||
if(!nama) return alert("Isi Nama/Nomor dulu!");
|
||||
|
||||
let nilaiHitung = 0;
|
||||
let latlngs = layer.getLatLngs();
|
||||
|
||||
// LOGIKA PERHITUNGAN OTOMATIS
|
||||
if (mode === 'jalan') {
|
||||
// Hitung Panjang (Linear distance)
|
||||
for (let i = 0; i < latlngs.length - 1; i++) {
|
||||
nilaiHitung += latlngs[i].distanceTo(latlngs[i+1]);
|
||||
}
|
||||
} else {
|
||||
// Hitung Luas (Area menggunakan Turf.js)
|
||||
var geojson = layer.toGeoJSON();
|
||||
nilaiHitung = turf.area(geojson);
|
||||
}
|
||||
|
||||
// AJAX Simpan ke PHP
|
||||
var fd = new FormData();
|
||||
fd.append('type', mode);
|
||||
fd.append('nama', nama);
|
||||
fd.append('status', status);
|
||||
fd.append('coords', JSON.stringify(latlngs));
|
||||
fd.append('nilai', nilaiHitung.toFixed(2));
|
||||
|
||||
fetch('save.php', { method: 'POST', body: fd })
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if(data.status === 'success') {
|
||||
layer.setStyle({ color: config[mode].warna[status], fillColor: config[mode].warna[status] });
|
||||
let unit = mode === 'jalan' ? ' Meter' : ' m²';
|
||||
layer.bindPopup(`<b>${nama}</b><br>Status: ${status}<br>Nilai: ${nilaiHitung.toFixed(2)}${unit}`).openPopup();
|
||||
drawnItems.addLayer(layer);
|
||||
alert("Berhasil disimpan!");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Load data lama dari database (Jalan)
|
||||
<?php
|
||||
$q1 = mysqli_query($conn, "SELECT * FROM data_jalan");
|
||||
while($r = mysqli_fetch_assoc($q1)) {
|
||||
echo "L.polyline({$r['geojson_data']}, {color: config.jalan.warna['{$r['status_jalan']}']}).addTo(map).bindPopup('<b>{$r['nama_jalan']}</b><br>Panjang: {$r['panjang_jalan']} m');";
|
||||
}
|
||||
// Load data lama dari database (Parsil)
|
||||
$q2 = mysqli_query($conn, "SELECT * FROM data_parsil");
|
||||
while($r = mysqli_fetch_assoc($q2)) {
|
||||
echo "L.polygon({$r['geojson_data']}, {color: config.parsil.warna['{$r['status_sertifikat']}'], fillColor: config.parsil.warna['{$r['status_sertifikat']}']}).addTo(map).bindPopup('<b>{$r['nomor_parsil']}</b><br>Luas: {$r['luas_m2']} m²');";
|
||||
}
|
||||
?>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
include 'config.php';
|
||||
|
||||
$type = $_POST['type']; // 'jalan' atau 'parsil'
|
||||
$nama = mysqli_real_escape_string($conn, $_POST['nama']);
|
||||
$status = $_POST['status'];
|
||||
$coords = $_POST['coords'];
|
||||
$nilai = $_POST['nilai'];
|
||||
|
||||
if ($type == 'jalan') {
|
||||
$sql = "INSERT INTO data_jalan (nama_jalan, status_jalan, geojson_data, panjang_jalan)
|
||||
VALUES ('$nama', '$status', '$coords', '$nilai')";
|
||||
} else {
|
||||
$sql = "INSERT INTO data_parsil (nomor_parsil, status_sertifikat, geojson_data, luas_m2)
|
||||
VALUES ('$nama', '$status', '$coords', '$nilai')";
|
||||
}
|
||||
|
||||
if (mysqli_query($conn, $sql)) {
|
||||
echo json_encode(['status' => 'success']);
|
||||
} else {
|
||||
echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
$host = getenv('DB_HOST') ?: 'localhost';
|
||||
$user = getenv('DB_USER') ?: 'root';
|
||||
$pass = getenv('DB_PASS') ?: '';
|
||||
$db = getenv('DB_NAME') ?: 'gis_tugas';
|
||||
|
||||
$conn = mysqli_connect($host, $user, $pass, $db);
|
||||
|
||||
if (!$conn) {
|
||||
die("Koneksi gagal: " . mysqli_connect_error());
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,375 @@
|
||||
<?php include 'config.php'; ?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>WebGIS Manajemen Jalan - Search & Filter</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css"/>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
||||
|
||||
:root {
|
||||
--primary-navy: #0F2027; /* Gelap & Elegan */
|
||||
--secondary-navy: #203A43;
|
||||
--accent-blue: #2C5364;
|
||||
--bg-color: #F8FAFC;
|
||||
--card-bg: #FFFFFF;
|
||||
--text-main: #1E293B;
|
||||
--text-muted: #64748B;
|
||||
--border-color: #E2E8F0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
margin: 0;
|
||||
background: var(--bg-color);
|
||||
color: var(--text-main);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, var(--primary-navy), var(--secondary-navy));
|
||||
color: white;
|
||||
padding: 20px 30px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header h2 {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 30px auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.panel, .table-container {
|
||||
background: var(--card-bg);
|
||||
padding: 25px 30px;
|
||||
border-radius: 16px;
|
||||
margin-bottom: 25px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
|
||||
border: 1px solid var(--border-color);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.panel:hover, .table-container:hover {
|
||||
box-shadow: 0 12px 20px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -2px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.panel h3, .table-container h3 {
|
||||
color: var(--primary-navy);
|
||||
font-weight: 700;
|
||||
margin-top: 0;
|
||||
font-size: 18px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-inline {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.input-group strong {
|
||||
font-size: 14px;
|
||||
color: var(--secondary-navy);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
input, select {
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 14px;
|
||||
color: var(--text-main);
|
||||
background-color: var(--bg-color);
|
||||
transition: all 0.3s ease;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input:focus, select:focus {
|
||||
border-color: var(--accent-blue);
|
||||
box-shadow: 0 0 0 3px rgba(44, 83, 100, 0.15);
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
small {
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
width: 100%;
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#map {
|
||||
height: 550px;
|
||||
width: 100%;
|
||||
border-radius: 16px;
|
||||
border: none;
|
||||
box-shadow: 0 10px 25px rgba(15, 32, 39, 0.1);
|
||||
z-index: 1;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
/* Tabel Styling */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
margin-top: 15px;
|
||||
}
|
||||
th, td {
|
||||
padding: 16px 20px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
th {
|
||||
background: linear-gradient(135deg, var(--primary-navy), var(--secondary-navy));
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.8px;
|
||||
}
|
||||
th:first-child { border-top-left-radius: 10px; }
|
||||
th:last-child { border-top-right-radius: 10px; }
|
||||
|
||||
tbody tr {
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
tbody tr:hover {
|
||||
background-color: #F1F5F9;
|
||||
}
|
||||
|
||||
.badge {
|
||||
padding: 6px 14px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
display: inline-block;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Leaflet Overrides */
|
||||
.leaflet-control-layers, .leaflet-bar {
|
||||
border: none !important;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.1) !important;
|
||||
border-radius: 12px !important;
|
||||
}
|
||||
.leaflet-control-layers { padding: 8px !important; font-family: 'Inter', sans-serif; }
|
||||
.leaflet-bar { overflow: hidden; }
|
||||
.leaflet-bar a {
|
||||
border-bottom: 1px solid #eee !important;
|
||||
color: var(--primary-navy) !important;
|
||||
width: 32px !important;
|
||||
height: 32px !important;
|
||||
line-height: 32px !important;
|
||||
}
|
||||
.leaflet-bar a:hover {
|
||||
background-color: #f8f9fa !important;
|
||||
color: var(--accent-blue) !important;
|
||||
}
|
||||
.leaflet-popup-content-wrapper {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
.leaflet-popup-content { font-family: 'Inter', sans-serif; font-size: 14px; line-height: 1.5; }
|
||||
.leaflet-popup-content b { color: var(--primary-navy); font-size: 15px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h2>🛣️ Manajemen Data Jalan (Polyline)</h2>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="panel">
|
||||
<h3>Input Data Baru</h3>
|
||||
<div class="form-inline">
|
||||
<div class="input-group">
|
||||
<strong>Nama Jalan:</strong>
|
||||
<input type="text" id="nama_jalan" placeholder="Nama Jalan Baru...">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<strong>Status Jalan:</strong>
|
||||
<select id="status_jalan">
|
||||
<option value="Nasional">Nasional (Merah)</option>
|
||||
<option value="Provinsi">Provinsi (Biru)</option>
|
||||
<option value="Kabupaten">Kabupaten (Hijau)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<small>Isi nama dan status, lalu gambar garis di peta untuk menyimpan data.</small>
|
||||
</div>
|
||||
|
||||
<div class="panel search-box">
|
||||
<h3>Pencarian & Filter</h3>
|
||||
<div class="form-inline">
|
||||
<div class="input-group">
|
||||
<strong>Cari Nama:</strong>
|
||||
<input type="text" id="search_input" placeholder="Ketik nama jalan..." onkeyup="filterData()">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<strong>Filter Status:</strong>
|
||||
<select id="filter_status" onchange="filterData()">
|
||||
<option value="Semua">Semua Status</option>
|
||||
<option value="Nasional">Nasional</option>
|
||||
<option value="Provinsi">Provinsi</option>
|
||||
<option value="Kabupaten">Kabupaten</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="map"></div>
|
||||
|
||||
<div class="table-container">
|
||||
<h3>Daftar Jalan</h3>
|
||||
<table id="tabel_jalan">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nama Jalan</th>
|
||||
<th>Status</th>
|
||||
<th>Panjang</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
|
||||
|
||||
<script>
|
||||
// 1. Inisialisasi Map
|
||||
var map = L.map('map').setView([-0.02, 109.34], 13);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
|
||||
|
||||
const gayaJalan = {
|
||||
"Nasional": { color: 'red', weight: 6 },
|
||||
"Provinsi": { color: 'blue', weight: 4 },
|
||||
"Kabupaten": { color: 'green', weight: 3 }
|
||||
};
|
||||
|
||||
// Arry untuk menampung semua data dari database agar bisa difilter di sisi client
|
||||
let dataJalanGlobal = [];
|
||||
let layerGroup = L.layerGroup().addTo(map);
|
||||
|
||||
// 2. Fungsi Load Data dari PHP ke JS
|
||||
function loadData() {
|
||||
<?php
|
||||
$res = mysqli_query($conn, "SELECT * FROM data_jalan ORDER BY id DESC");
|
||||
$data = [];
|
||||
while($row = mysqli_fetch_assoc($res)) {
|
||||
$data[] = $row;
|
||||
}
|
||||
echo "dataJalanGlobal = " . json_encode($data) . ";";
|
||||
?>
|
||||
renderData(dataJalanGlobal);
|
||||
}
|
||||
|
||||
// 3. Fungsi Menampilkan Data ke Peta dan Tabel
|
||||
function renderData(data) {
|
||||
layerGroup.clearLayers(); // Bersihkan peta
|
||||
const tbody = document.querySelector("#tabel_jalan tbody");
|
||||
tbody.innerHTML = ""; // Bersihkan tabel
|
||||
|
||||
data.forEach(item => {
|
||||
// Tambahkan ke Peta
|
||||
const coords = JSON.parse(item.geojson_data);
|
||||
const poly = L.polyline(coords, gayaJalan[item.status_jalan])
|
||||
.bindPopup(`<b>${item.nama_jalan}</b><br>Status: ${item.status_jalan}<br>Panjang: ${item.panjang_jalan} m`);
|
||||
layerGroup.addLayer(poly);
|
||||
|
||||
// Tambahkan ke Tabel
|
||||
const color = gayaJalan[item.status_jalan].color;
|
||||
const row = `<tr>
|
||||
<td>${item.nama_jalan}</td>
|
||||
<td><span class="badge" style="background:${color}">${item.status_jalan}</span></td>
|
||||
<td>${parseFloat(item.panjang_jalan).toLocaleString()} m</td>
|
||||
</tr>`;
|
||||
tbody.innerHTML += row;
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Fungsi Search dan Filter
|
||||
function filterData() {
|
||||
const keyword = document.getElementById('search_input').value.toLowerCase();
|
||||
const statusFilt = document.getElementById('filter_status').value;
|
||||
|
||||
const hasilFilter = dataJalanGlobal.filter(item => {
|
||||
const matchNama = item.nama_jalan.toLowerCase().includes(keyword);
|
||||
const matchStatus = (statusFilt === "Semua") || (item.status_jalan === statusFilt);
|
||||
return matchNama && matchStatus;
|
||||
});
|
||||
|
||||
renderData(hasilFilter);
|
||||
}
|
||||
|
||||
// 5. Fitur Menggambar (Leaflet Draw)
|
||||
var drawnItems = new L.FeatureGroup();
|
||||
map.addLayer(drawnItems);
|
||||
var drawControl = new L.Control.Draw({
|
||||
draw: { polygon: false, circle: false, marker: false, circlemarker: false, rectangle: false,
|
||||
polyline: { shapeOptions: { color: '#333' } }
|
||||
}
|
||||
});
|
||||
map.addControl(drawControl);
|
||||
|
||||
map.on(L.Draw.Event.CREATED, function (e) {
|
||||
var layer = e.layer;
|
||||
var latlngs = layer.getLatLngs();
|
||||
var totalPanjang = 0;
|
||||
for (let i = 0; i < latlngs.length - 1; i++) {
|
||||
totalPanjang += latlngs[i].distanceTo(latlngs[i + 1]);
|
||||
}
|
||||
|
||||
var nama = document.getElementById('nama_jalan').value;
|
||||
var status = document.getElementById('status_jalan').value;
|
||||
|
||||
if(!nama) { alert("Isi Nama Jalan dulu!"); return; }
|
||||
|
||||
var formData = new FormData();
|
||||
formData.append('nama', nama);
|
||||
formData.append('status', status);
|
||||
formData.append('coords', JSON.stringify(latlngs));
|
||||
formData.append('panjang', totalPanjang.toFixed(2));
|
||||
|
||||
fetch('save.php', { method: 'POST', body: formData })
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if(data.status === 'success') {
|
||||
alert("Data Berhasil Disimpan!");
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Jalankan saat pertama kali buka
|
||||
loadData();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
include 'config.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
$nama = mysqli_real_escape_string($conn, $_POST['nama']);
|
||||
$status = $_POST['status'];
|
||||
$coords = $_POST['coords'];
|
||||
$panjang = $_POST['panjang'];
|
||||
|
||||
$query = "INSERT INTO data_jalan (nama_jalan, status_jalan, geojson_data, panjang_jalan)
|
||||
VALUES ('$nama', '$status', '$coords', '$panjang')";
|
||||
|
||||
if (mysqli_query($conn, $query)) {
|
||||
echo json_encode(['status' => 'success']);
|
||||
} else {
|
||||
echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]);
|
||||
}
|
||||
}
|
||||
?>
|
||||
Reference in New Issue
Block a user