initial commit
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../koneksi.php';
|
||||||
|
|
||||||
|
$result = $conn->query("SELECT * FROM jalan ORDER BY created_at DESC");
|
||||||
|
$data = array();
|
||||||
|
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$data[] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($data);
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once __DIR__ . '/../koneksi.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Invalid request method']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = isset($_POST['id']) ? intval($_POST['id']) : 0;
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'ID tidak valid']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "DELETE FROM jalan WHERE id = ?";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
$stmt->bind_param("i", $id);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Data jalan berhasil dihapus']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once __DIR__ . '/../koneksi.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Invalid request']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$nama = trim($_POST['nama'] ?? '');
|
||||||
|
$status = trim($_POST['status'] ?? '');
|
||||||
|
$geojson = $_POST['geojson'] ?? '';
|
||||||
|
$panjang = floatval($_POST['panjang'] ?? 0);
|
||||||
|
|
||||||
|
if (empty($nama) || empty($status) || empty($geojson) || $panjang <= 0) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Data tidak lengkap']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "INSERT INTO jalan (nama, status, panjang, geojson) VALUES (?, ?, ?, ?)";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
$stmt->bind_param("ssds", $nama, $status, $panjang, $geojson);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Data berhasil disimpan']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once __DIR__ . '/../koneksi.php';
|
||||||
|
|
||||||
|
$id = intval($_POST['id'] ?? 0);
|
||||||
|
$nama = trim($_POST['nama'] ?? '');
|
||||||
|
$status = trim($_POST['status'] ?? '');
|
||||||
|
|
||||||
|
// Jika ada geojson, update geometri juga
|
||||||
|
if (isset($_POST['geojson'])) {
|
||||||
|
$geojson = $_POST['geojson'];
|
||||||
|
$panjang = floatval($_POST['panjang'] ?? 0);
|
||||||
|
$sql = "UPDATE jalan SET nama = ?, status = ?, geojson = ?, panjang = ? WHERE id = ?";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
$stmt->bind_param("sssdi", $nama, $status, $geojson, $panjang, $id);
|
||||||
|
} else {
|
||||||
|
// Hanya update info
|
||||||
|
$sql = "UPDATE jalan SET nama = ?, status = ? WHERE id = ?";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
$stmt->bind_param("ssi", $nama, $status, $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../koneksi.php';
|
||||||
|
|
||||||
|
$result = $conn->query("SELECT * FROM parsil_tanah ORDER BY created_at DESC");
|
||||||
|
$data = array();
|
||||||
|
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$data[] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($data);
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once __DIR__ . '/../koneksi.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Invalid request method']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = isset($_POST['id']) ? intval($_POST['id']) : 0;
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'ID tidak valid']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "DELETE FROM parsil_tanah WHERE id = ?";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
$stmt->bind_param("i", $id);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Data parsil berhasil dihapus']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once __DIR__ . '/../koneksi.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Invalid request']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$nama = trim($_POST['nama'] ?? '');
|
||||||
|
$status = trim($_POST['status'] ?? '');
|
||||||
|
$geojson = $_POST['geojson'] ?? '';
|
||||||
|
$luas = floatval($_POST['luas'] ?? 0);
|
||||||
|
|
||||||
|
if (empty($nama) || empty($status) || empty($geojson) || $luas <= 0) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Data tidak lengkap']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "INSERT INTO parsil_tanah (nama, status, luas, geojson) VALUES (?, ?, ?, ?)";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
$stmt->bind_param("ssds", $nama, $status, $luas, $geojson);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Data berhasil disimpan']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once __DIR__ . '/../koneksi.php';
|
||||||
|
|
||||||
|
$id = intval($_POST['id'] ?? 0);
|
||||||
|
$nama = trim($_POST['nama'] ?? '');
|
||||||
|
$status = trim($_POST['status'] ?? '');
|
||||||
|
|
||||||
|
// Jika ada geojson, update geometri juga
|
||||||
|
if (isset($_POST['geojson'])) {
|
||||||
|
$geojson = $_POST['geojson'];
|
||||||
|
$luas = floatval($_POST['luas'] ?? 0);
|
||||||
|
$sql = "UPDATE parsil_tanah SET nama = ?, status = ?, geojson = ?, luas = ? WHERE id = ?";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
$stmt->bind_param("sssdi", $nama, $status, $geojson, $luas, $id);
|
||||||
|
} else {
|
||||||
|
// Hanya update info
|
||||||
|
$sql = "UPDATE parsil_tanah SET nama = ?, status = ? WHERE id = ?";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
$stmt->bind_param("ssi", $nama, $status, $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,280 @@
|
|||||||
|
# 🗺️ WebGIS - Sistem Informasi SPBU Kota Pontianak
|
||||||
|
|
||||||
|
[](https://php.net)
|
||||||
|
[](https://mysql.com)
|
||||||
|
[](https://leafletjs.com)
|
||||||
|
[](LICENSE)
|
||||||
|
|
||||||
|
Aplikasi WebGIS untuk mengelola dan memvisualisasikan data **SPBU**, **Jaringan Jalan**, dan **Parsil Tanah** di Kota Pontianak secara interaktif.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## ✨ Fitur Utama
|
||||||
|
|
||||||
|
### 📍 Manajemen SPBU
|
||||||
|
|
||||||
|
* Tambah, edit, hapus data SPBU
|
||||||
|
* Drag & drop marker untuk update posisi
|
||||||
|
* Kategorisasi (24 jam / Tidak 24 jam)
|
||||||
|
* Informasi kontak WhatsApp
|
||||||
|
|
||||||
|
### 🛣️ Manajemen Jalan
|
||||||
|
|
||||||
|
* Gambar polyline langsung di peta
|
||||||
|
* Klasifikasi status jalan (Nasional/Provinsi/Kabupaten)
|
||||||
|
* Hitung panjang jalan otomatis
|
||||||
|
* Edit dan hapus data jalan
|
||||||
|
|
||||||
|
### 🏠 Manajemen Parsil Tanah
|
||||||
|
|
||||||
|
* Gambar polygon untuk bidang tanah
|
||||||
|
* Klasifikasi status sertifikat (SHM/HGB/HGU/HP)
|
||||||
|
* Hitung luas tanah otomatis
|
||||||
|
* Edit dan hapus data parsil
|
||||||
|
|
||||||
|
### 🗺️ Fitur Peta
|
||||||
|
|
||||||
|
* Peta interaktif dengan Leaflet.js
|
||||||
|
* Multiple layer control
|
||||||
|
* Legend interaktif
|
||||||
|
* Search dan filter data
|
||||||
|
* Zoom to feature
|
||||||
|
|
||||||
|
### ⚙️ Layer Control & Tampilan
|
||||||
|
|
||||||
|
* **Layer Control Terpisah** - SPBU 24 jam dan Non 24 jam beda layer
|
||||||
|
* **Tampilan Modern** - UI lebih elegan dengan font DM Sans
|
||||||
|
* **Popup Koordinat** - Menampilkan latitude/longitude lengkap
|
||||||
|
* **Loading State** - Animasi loading yang halus
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Teknologi yang Digunakan
|
||||||
|
|
||||||
|
| Teknologi | Kegunaan |
|
||||||
|
| --------------------- | ------------------------------ |
|
||||||
|
| PHP 7.4+ | Backend API & koneksi database |
|
||||||
|
| MySQL | Database penyimpanan data |
|
||||||
|
| Leaflet.js | Library peta interaktif |
|
||||||
|
| Turf.js | Kalkulasi geometri |
|
||||||
|
| Leaflet Draw | Drawing tools |
|
||||||
|
| Leaflet Extra Markers | Marker kustom |
|
||||||
|
| HTML5/CSS3/JS | Frontend |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 Struktur Folder
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Instalasi
|
||||||
|
|
||||||
|
### Prasyarat
|
||||||
|
|
||||||
|
* XAMPP / Laragon / MAMP
|
||||||
|
* PHP ≥ 7.4
|
||||||
|
* MySQL ≥ 5.7
|
||||||
|
* Browser modern
|
||||||
|
|
||||||
|
### Langkah Instalasi
|
||||||
|
|
||||||
|
#### 1. Clone Repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/username/SPBU.git
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Pindahkan ke htdocs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
C:/xampp/htdocs/webgis/SPBU/
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Import Database
|
||||||
|
|
||||||
|
**phpMyAdmin**
|
||||||
|
|
||||||
|
* http://localhost/phpmyadmin
|
||||||
|
* Buat database: `webgis`
|
||||||
|
* Import `webgis.sql`
|
||||||
|
|
||||||
|
**Command Line**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mysql -u root -p
|
||||||
|
CREATE DATABASE webgis;
|
||||||
|
USE webgis;
|
||||||
|
SOURCE webgis.sql;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. Konfigurasi Database
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
$host = 'localhost';
|
||||||
|
$user = 'root';
|
||||||
|
$password = '';
|
||||||
|
$database = 'webgis';
|
||||||
|
|
||||||
|
$conn = new mysqli($host, $user, $password, $database);
|
||||||
|
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
die(json_encode(['error' => true, 'message' => $conn->connect_error]));
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5. Jalankan Aplikasi
|
||||||
|
|
||||||
|
```
|
||||||
|
http://localhost/webgis/01SPBU/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📖 Cara Penggunaan
|
||||||
|
|
||||||
|
### Mode Navigasi
|
||||||
|
|
||||||
|
* Klik objek untuk melihat detail
|
||||||
|
* Klik list untuk zoom lokasi
|
||||||
|
|
||||||
|
### Tambah SPBU
|
||||||
|
|
||||||
|
1. Klik tombol SPBU
|
||||||
|
2. Klik peta
|
||||||
|
3. Isi form
|
||||||
|
4. Simpan
|
||||||
|
|
||||||
|
### Tambah Jalan
|
||||||
|
|
||||||
|
1. Klik Jalan
|
||||||
|
2. Klik titik di peta
|
||||||
|
3. Double click selesai
|
||||||
|
4. Simpan
|
||||||
|
|
||||||
|
### Tambah Parsil
|
||||||
|
|
||||||
|
1. Klik Parsil
|
||||||
|
2. Klik titik
|
||||||
|
3. Selesaikan polygon
|
||||||
|
4. Simpan
|
||||||
|
|
||||||
|
### Edit & Hapus
|
||||||
|
|
||||||
|
* Edit: tombol ✏️
|
||||||
|
* Hapus: tombol 🗑️
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 API Endpoints
|
||||||
|
|
||||||
|
### SPBU
|
||||||
|
|
||||||
|
| Endpoint | Method |
|
||||||
|
| -------------------------- | ------ |
|
||||||
|
| /SPBU/ambil.php | GET |
|
||||||
|
| /SPBU/simpan.php | POST |
|
||||||
|
| /SPBU/hapus.php | POST |
|
||||||
|
| /SPBU/update_lokasi.php | POST |
|
||||||
|
| /SPBU/update_spbu_info.php | POST |
|
||||||
|
|
||||||
|
### Jalan
|
||||||
|
|
||||||
|
| Endpoint | Method |
|
||||||
|
| ----------------------- | ------ |
|
||||||
|
| /Jalan/ambil_jalan.php | GET |
|
||||||
|
| /Jalan/simpan_jalan.php | POST |
|
||||||
|
| /Jalan/hapus_jalan.php | POST |
|
||||||
|
|
||||||
|
### Parsil
|
||||||
|
|
||||||
|
| Endpoint | Method |
|
||||||
|
| ------------------------- | ------ |
|
||||||
|
| /Parsil/ambil_parsil.php | GET |
|
||||||
|
| /Parsil/simpan_parsil.php | POST |
|
||||||
|
| /Parsil/hapus_parsil.php | POST |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Database Structure
|
||||||
|
|
||||||
|
### spbu
|
||||||
|
|
||||||
|
| Field | Type |
|
||||||
|
| --------- | ------- |
|
||||||
|
| id | INT |
|
||||||
|
| nama | VARCHAR |
|
||||||
|
| nomor | VARCHAR |
|
||||||
|
| kategori | ENUM |
|
||||||
|
| latitude | DECIMAL |
|
||||||
|
| longitude | DECIMAL |
|
||||||
|
|
||||||
|
### jalan
|
||||||
|
|
||||||
|
| Field | Type |
|
||||||
|
| ------- | -------- |
|
||||||
|
| id | INT |
|
||||||
|
| nama | VARCHAR |
|
||||||
|
| status | VARCHAR |
|
||||||
|
| panjang | DOUBLE |
|
||||||
|
| geojson | LONGTEXT |
|
||||||
|
|
||||||
|
### parsil_tanah
|
||||||
|
|
||||||
|
| Field | Type |
|
||||||
|
| ------- | -------- |
|
||||||
|
| id | INT |
|
||||||
|
| nama | VARCHAR |
|
||||||
|
| status | VARCHAR |
|
||||||
|
| luas | DOUBLE |
|
||||||
|
| geojson | LONGTEXT |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
|
**Data tidak muncul**
|
||||||
|
|
||||||
|
```php
|
||||||
|
require_once __DIR__ . '/../koneksi.php';
|
||||||
|
```
|
||||||
|
|
||||||
|
**Path error**
|
||||||
|
|
||||||
|
* Gunakan `__DIR__`
|
||||||
|
|
||||||
|
**Peta tidak muncul**
|
||||||
|
|
||||||
|
* Cek internet & console
|
||||||
|
|
||||||
|
**Data gagal simpan**
|
||||||
|
|
||||||
|
* Cek DB & permission
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 👥 Kontributor
|
||||||
|
|
||||||
|
* Cindy Dyra Islamay — Fullstack Developer
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Lisensi
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 Kontak
|
||||||
|
|
||||||
|
* Email: [d1041231045@student.untan.ac.id]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
⭐ Jika project ini membantu, beri bintang di repository.
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
require_once __DIR__ . '/../koneksi.php';
|
||||||
|
|
||||||
|
// Cek koneksi
|
||||||
|
if (!isset($conn) || $conn->connect_error) {
|
||||||
|
throw new Exception("Database connection failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $conn->query("SELECT * FROM spbu ORDER BY created_at DESC");
|
||||||
|
|
||||||
|
if (!$result) {
|
||||||
|
throw new Exception("Query failed: " . $conn->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = array();
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$data[] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($data);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode([
|
||||||
|
'error' => true,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($conn)) {
|
||||||
|
$conn->close();
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once __DIR__ . '/../koneksi.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Invalid request method']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = intval($_POST['id'] ?? 0);
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'ID tidak valid']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "DELETE FROM spbu WHERE id = ?";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
$stmt->bind_param("i", $id);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Data SPBU berhasil dihapus']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
require_once __DIR__ . '/../koneksi.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
throw new Exception('Invalid request method');
|
||||||
|
}
|
||||||
|
|
||||||
|
$nama = isset($_POST['nama']) ? trim($_POST['nama']) : '';
|
||||||
|
$nomor = isset($_POST['nomor']) ? trim($_POST['nomor']) : '';
|
||||||
|
$kategori = isset($_POST['kategori']) ? trim($_POST['kategori']) : '';
|
||||||
|
$lat = isset($_POST['latitude']) ? floatval($_POST['latitude']) : null;
|
||||||
|
$lng = isset($_POST['longitude']) ? floatval($_POST['longitude']) : null;
|
||||||
|
|
||||||
|
if (empty($nama) || empty($nomor) || empty($kategori)) {
|
||||||
|
throw new Exception('Semua field harus diisi');
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "INSERT INTO spbu (nama, nomor, kategori, latitude, longitude) VALUES (?, ?, ?, ?, ?)";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
throw new Exception('Prepare failed: ' . $conn->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->bind_param("sssdd", $nama, $nomor, $kategori, $lat, $lng);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Data berhasil disimpan',
|
||||||
|
'id' => $conn->insert_id
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
throw new Exception('Execute failed: ' . $stmt->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($conn)) {
|
||||||
|
$conn->close();
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once __DIR__ . '/../koneksi.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Invalid request method']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = intval($_POST['id'] ?? 0);
|
||||||
|
$latitude = floatval($_POST['latitude'] ?? 0);
|
||||||
|
$longitude = floatval($_POST['longitude'] ?? 0);
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'ID tidak valid']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "UPDATE spbu SET latitude = ?, longitude = ? WHERE id = ?";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $conn->error]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->bind_param("ddi", $latitude, $longitude, $id);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Lokasi diupdate']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once __DIR__ . '/../koneksi.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Invalid request']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = intval($_POST['id'] ?? 0);
|
||||||
|
$nama = trim($_POST['nama'] ?? '');
|
||||||
|
$nomor = trim($_POST['nomor'] ?? '');
|
||||||
|
$kategori = trim($_POST['kategori'] ?? '');
|
||||||
|
|
||||||
|
if ($id <= 0 || empty($nama) || empty($nomor) || empty($kategori)) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Data tidak lengkap']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "UPDATE spbu SET nama = ?, nomor = ?, kategori = ? WHERE id = ?";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
$stmt->bind_param("sssi", $nama, $nomor, $kategori, $id);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Data SPBU diupdate']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
-- phpMyAdmin SQL Dump
|
||||||
|
-- version 5.2.1
|
||||||
|
-- https://www.phpmyadmin.net/
|
||||||
|
--
|
||||||
|
-- Host: 127.0.0.1
|
||||||
|
-- Generation Time: Jun 07, 2026 at 07:10 PM
|
||||||
|
-- Server version: 10.4.32-MariaDB
|
||||||
|
-- PHP Version: 8.2.12
|
||||||
|
|
||||||
|
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
||||||
|
START TRANSACTION;
|
||||||
|
SET time_zone = "+00:00";
|
||||||
|
|
||||||
|
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||||
|
/*!40101 SET NAMES utf8mb4 */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Database: `webgis`
|
||||||
|
--
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `jalan`
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE `jalan` (
|
||||||
|
`id` int(11) NOT NULL,
|
||||||
|
`nama` varchar(255) NOT NULL COMMENT 'Nama ruas jalan',
|
||||||
|
`status` varchar(100) NOT NULL COMMENT 'Status jalan (e.g. Nasional, Provinsi, Kabupaten, Desa)',
|
||||||
|
`panjang` double NOT NULL DEFAULT 0 COMMENT 'Panjang jalan dalam meter',
|
||||||
|
`geojson` longtext NOT NULL COMMENT 'Geometri jalan dalam format GeoJSON',
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT current_timestamp()
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Data segmen jalan (polyline) dalam format GeoJSON';
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `jalan`
|
||||||
|
--
|
||||||
|
|
||||||
|
INSERT INTO `jalan` (`id`, `nama`, `status`, `panjang`, `geojson`, `created_at`) VALUES
|
||||||
|
(1, 'Jalan Sepakat 2', 'Kabupaten', 1784.791473379715, '{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[109.35178399086,-0.0583629161931721],[109.35063600540163,-0.05999276433692213],[109.34970259666444,-0.06140815873824388],[109.3490481376648,-0.062394645723090975],[109.34783577919008,-0.06381004006026729],[109.34582948684694,-0.06598674491163233],[109.3437159061432,-0.06820159083180335],[109.34262156486513,-0.06938108597800444],[109.34150576591493,-0.07064203975056207],[109.34150576591493,-0.07064203975056207],[109.34150576591493,-0.07064203975056207]]},\"properties\":{}}', '2026-06-07 16:09:11');
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `parsil_tanah`
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE `parsil_tanah` (
|
||||||
|
`id` int(11) NOT NULL,
|
||||||
|
`nama` varchar(255) NOT NULL COMMENT 'Nama parsil / bidang tanah',
|
||||||
|
`status` varchar(50) NOT NULL COMMENT 'Status hak (SHM | HGB | HGU | HP)',
|
||||||
|
`luas` double NOT NULL DEFAULT 0 COMMENT 'Luas area dalam meter persegi',
|
||||||
|
`geojson` longtext NOT NULL COMMENT 'Geometri parsil dalam format GeoJSON',
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT current_timestamp()
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Data parsil tanah (polygon) dalam format GeoJSON';
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `parsil_tanah`
|
||||||
|
--
|
||||||
|
|
||||||
|
INSERT INTO `parsil_tanah` (`id`, `nama`, `status`, `luas`, `geojson`, `created_at`) VALUES
|
||||||
|
(1, 'Pengadilan Tinggi Negeri', 'HGB', 1312.2666173966293, '{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[109.35532987117769,-0.06302106891085976],[109.35539424419404,-0.06308540501714478],[109.35543715953828,-0.06303715293742466],[109.35552835464479,-0.06313365709682675],[109.35549080371857,-0.06317654783426951],[109.35555517673494,-0.06327841333559274],[109.35537278652193,-0.06346069896896436],[109.35512065887453,-0.06321407722949855],[109.3551367521286,-0.06319263186079624],[109.3551367521286,-0.06319263186079624],[109.35532987117769,-0.06302106891085976]]]},\"properties\":{}}', '2026-06-07 16:02:58'),
|
||||||
|
(2, 'Hutan Sylva UNTAN', 'HP', 31066.903716160716, '{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[109.34868872165681,-0.05657792397283722],[109.34927880764009,-0.05576299985790386],[109.3494075536728,-0.055827335972649154],[109.34962749481203,-0.055784445229494106],[109.35099005699159,-0.0574571841892975],[109.35042142868042,-0.05825066290960069],[109.34964895248415,-0.05740893210459027],[109.34938609600069,-0.057092612881665034],[109.34924125671388,-0.05700147005438797],[109.34868872165681,-0.05657792397283722]]]},\"properties\":{}}', '2026-06-07 17:07:13');
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `polygons`
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE `polygons` (
|
||||||
|
`id` int(11) NOT NULL,
|
||||||
|
`nama` varchar(255) NOT NULL COMMENT 'Nama polygon',
|
||||||
|
`coordinates` longtext NOT NULL COMMENT 'Koordinat polygon (JSON string)',
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT current_timestamp()
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Layer polygon generik (koordinat sebagai teks JSON)';
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `polylines`
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE `polylines` (
|
||||||
|
`id` int(11) NOT NULL,
|
||||||
|
`nama` varchar(255) NOT NULL COMMENT 'Nama polyline',
|
||||||
|
`coordinates` longtext NOT NULL COMMENT 'Koordinat polyline (JSON string)',
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT current_timestamp()
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Layer polyline generik (koordinat sebagai teks JSON)';
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `spbu`
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE `spbu` (
|
||||||
|
`id` int(11) NOT NULL,
|
||||||
|
`nama` varchar(255) NOT NULL COMMENT 'Nama SPBU',
|
||||||
|
`nomor` varchar(100) NOT NULL COMMENT 'Nomor WhatsApp / kontak',
|
||||||
|
`kategori` varchar(50) NOT NULL COMMENT '24_jam | tidak_24_jam',
|
||||||
|
`latitude` decimal(10,8) DEFAULT NULL COMMENT 'Koordinat lintang',
|
||||||
|
`longitude` decimal(11,8) DEFAULT NULL COMMENT 'Koordinat bujur',
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT current_timestamp()
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Data titik lokasi SPBU';
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `spbu`
|
||||||
|
--
|
||||||
|
|
||||||
|
INSERT INTO `spbu` (`id`, `nama`, `nomor`, `kategori`, `latitude`, `longitude`, `created_at`) VALUES
|
||||||
|
(1, 'SPBU Parit Husein', '0824245345', 'tidak', -0.06431884, 109.35623646, '2026-06-07 16:02:20'),
|
||||||
|
(2, 'SPBU Kota Baru', '085154545', '24_jam', -0.04773790, 109.31863725, '2026-06-07 17:10:26');
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Indexes for dumped tables
|
||||||
|
--
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Indexes for table `jalan`
|
||||||
|
--
|
||||||
|
ALTER TABLE `jalan`
|
||||||
|
ADD PRIMARY KEY (`id`),
|
||||||
|
ADD KEY `idx_jalan_status` (`status`),
|
||||||
|
ADD KEY `idx_jalan_created_at` (`created_at`);
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Indexes for table `parsil_tanah`
|
||||||
|
--
|
||||||
|
ALTER TABLE `parsil_tanah`
|
||||||
|
ADD PRIMARY KEY (`id`),
|
||||||
|
ADD KEY `idx_parsil_status` (`status`),
|
||||||
|
ADD KEY `idx_parsil_created_at` (`created_at`);
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Indexes for table `polygons`
|
||||||
|
--
|
||||||
|
ALTER TABLE `polygons`
|
||||||
|
ADD PRIMARY KEY (`id`),
|
||||||
|
ADD KEY `idx_polygons_created_at` (`created_at`);
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Indexes for table `polylines`
|
||||||
|
--
|
||||||
|
ALTER TABLE `polylines`
|
||||||
|
ADD PRIMARY KEY (`id`),
|
||||||
|
ADD KEY `idx_polylines_created_at` (`created_at`);
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Indexes for table `spbu`
|
||||||
|
--
|
||||||
|
ALTER TABLE `spbu`
|
||||||
|
ADD PRIMARY KEY (`id`),
|
||||||
|
ADD KEY `idx_spbu_kategori` (`kategori`),
|
||||||
|
ADD KEY `idx_spbu_created_at` (`created_at`);
|
||||||
|
|
||||||
|
--
|
||||||
|
-- AUTO_INCREMENT for dumped tables
|
||||||
|
--
|
||||||
|
|
||||||
|
--
|
||||||
|
-- AUTO_INCREMENT for table `jalan`
|
||||||
|
--
|
||||||
|
ALTER TABLE `jalan`
|
||||||
|
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- AUTO_INCREMENT for table `parsil_tanah`
|
||||||
|
--
|
||||||
|
ALTER TABLE `parsil_tanah`
|
||||||
|
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- AUTO_INCREMENT for table `polygons`
|
||||||
|
--
|
||||||
|
ALTER TABLE `polygons`
|
||||||
|
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- AUTO_INCREMENT for table `polylines`
|
||||||
|
--
|
||||||
|
ALTER TABLE `polylines`
|
||||||
|
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- AUTO_INCREMENT for table `spbu`
|
||||||
|
--
|
||||||
|
ALTER TABLE `spbu`
|
||||||
|
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||||
+1793
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
// Set header JSON untuk semua response
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$host = 'localhost';
|
||||||
|
$user = 'root';
|
||||||
|
$password = ''; // Pastikan password MySQL Anda
|
||||||
|
$database = 'webgis';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Buat koneksi
|
||||||
|
$conn = new mysqli($host, $user, $password, $database);
|
||||||
|
|
||||||
|
// Cek koneksi
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
throw new Exception("Koneksi gagal: " . $conn->connect_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set charset
|
||||||
|
$conn->set_charset("utf8");
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// Jika error, tetap kembalikan JSON
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,432 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<base target="_top">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Choropleth Peta Kepadatan Penduduk - Kota Pontianak</title>
|
||||||
|
|
||||||
|
<!-- Leaflet CSS & JS -->
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
overflow: hidden; /* Mencegah scroll */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Peta full screen */
|
||||||
|
#map {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Info panel tetap di atas peta */
|
||||||
|
.info {
|
||||||
|
padding: 8px 12px;
|
||||||
|
font: 15px/1.4 'Segoe UI', 'Arial', sans-serif;
|
||||||
|
background: white;
|
||||||
|
background: rgba(255,255,255,0.95);
|
||||||
|
box-shadow: 0 2px 12px rgba(0,0,0,0.2);
|
||||||
|
border-radius: 8px;
|
||||||
|
border-left: 4px solid #2c7da0;
|
||||||
|
min-width: 200px;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
text-align: left;
|
||||||
|
line-height: 20px;
|
||||||
|
color: #2c3e2f;
|
||||||
|
background: rgba(255,255,255,0.92);
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend i {
|
||||||
|
width: 24px;
|
||||||
|
height: 18px;
|
||||||
|
float: left;
|
||||||
|
margin-right: 8px;
|
||||||
|
opacity: 0.85;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 0.5px solid rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend strong {
|
||||||
|
font-size: 13px;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
color: #1f3b2c;
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 1px dashed #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Title note floating di atas peta */
|
||||||
|
.title-note {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 15px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 1000;
|
||||||
|
background: rgba(0,0,0,0.75);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
padding: 8px 20px;
|
||||||
|
border-radius: 40px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
||||||
|
pointer-events: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-family: 'Segoe UI', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
.info { font-size: 12px; min-width: 160px; }
|
||||||
|
.legend { font-size: 11px; }
|
||||||
|
.title-note { font-size: 11px; padding: 5px 12px; white-space: nowrap; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="map-container">
|
||||||
|
<div id="map"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Data GeoJSON terpisah: pontianak.js -->
|
||||||
|
<script type="text/javascript" src="pontianak.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
// ==============================
|
||||||
|
// 1. INISIALISASI PETA
|
||||||
|
// ==============================
|
||||||
|
// Koordinat pusat Kota Pontianak (sekitar -0.020, 109.342)
|
||||||
|
const pontianakCenter = [-0.020, 109.342];
|
||||||
|
const initialZoom = 12;
|
||||||
|
|
||||||
|
const map = L.map('map').setView(pontianakCenter, initialZoom);
|
||||||
|
|
||||||
|
// Base map tiles (OpenStreetMap)
|
||||||
|
const tiles = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
maxZoom: 19,
|
||||||
|
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
// ==============================
|
||||||
|
// 2. INFO CONTROL (hover / klik)
|
||||||
|
// ==============================
|
||||||
|
const info = L.control();
|
||||||
|
|
||||||
|
info.onAdd = function () {
|
||||||
|
this._div = L.DomUtil.create('div', 'info');
|
||||||
|
this.update();
|
||||||
|
return this._div;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fungsi update info panel (menampilkan data kecamatan & jumlah penduduk)
|
||||||
|
info.update = function (props) {
|
||||||
|
let content = '';
|
||||||
|
if (props) {
|
||||||
|
// Menggunakan properti "Ket" untuk nama kecamatan, "Penduduk" untuk jumlah penduduk
|
||||||
|
const kecamatan = props.Ket || props.name || 'Tidak diketahui';
|
||||||
|
const penduduk = props.Penduduk !== undefined && props.Penduduk !== null ? props.Penduduk.toLocaleString('id-ID') : 'Data tidak tersedia';
|
||||||
|
content = `<b>🏙️ ${kecamatan}</b><br/>👥 Jumlah Penduduk: <strong>${penduduk}</strong> jiwa`;
|
||||||
|
} else {
|
||||||
|
content = 'Arahkan kursor ke wilayah kecamatan';
|
||||||
|
}
|
||||||
|
this._div.innerHTML = `<h4>📊 Kependudukan Kota Pontianak</h4>${content}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
info.addTo(map);
|
||||||
|
|
||||||
|
// ==============================
|
||||||
|
// 3. FUNGSI WARNA BERDASARKAN JUMLAH PENDUDUK
|
||||||
|
// ==============================
|
||||||
|
// Menentukan warna berdasarkan kelas populasi (disesuaikan dengan data Pontianak)
|
||||||
|
// Data contoh: Pontianak Timur ~112.440, kita buat rentang sampai >150rb (jika ada)
|
||||||
|
function getColor(population) {
|
||||||
|
if (population === undefined || population === null) return '#cccccc'; // abu-abu untuk data kosong
|
||||||
|
|
||||||
|
if (population > 150000) return '#4d004b'; // ungu tua
|
||||||
|
if (population > 100000) return '#810f7c'; // ungu sedang
|
||||||
|
if (population > 70000) return '#c51b8a'; // magenta
|
||||||
|
if (population > 50000) return '#de77ae'; // pink
|
||||||
|
if (population > 30000) return '#f1b6da'; // pink muda
|
||||||
|
if (population > 10000) return '#fde0ef'; // sangat muda
|
||||||
|
return '#fff7fb'; // putih kebiruan
|
||||||
|
}
|
||||||
|
|
||||||
|
// Style untuk setiap feature (polygon)
|
||||||
|
function style(feature) {
|
||||||
|
const pop = feature?.properties?.Penduduk;
|
||||||
|
return {
|
||||||
|
weight: 1.8,
|
||||||
|
opacity: 0.9,
|
||||||
|
color: '#2c3e50',
|
||||||
|
dashArray: null,
|
||||||
|
fillOpacity: 0.75,
|
||||||
|
fillColor: getColor(pop)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==============================
|
||||||
|
// 4. INTERAKSI (HOVER, KLIK ZOOM)
|
||||||
|
// ==============================
|
||||||
|
let currentHighlightedLayer = null;
|
||||||
|
|
||||||
|
function highlightFeature(e) {
|
||||||
|
const layer = e.target;
|
||||||
|
|
||||||
|
// Reset style layer sebelumnya jika ada (opsional, tapi biar rapi)
|
||||||
|
if (currentHighlightedLayer && currentHighlightedLayer !== layer) {
|
||||||
|
geojsonLayer.resetStyle(currentHighlightedLayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
layer.setStyle({
|
||||||
|
weight: 3.5,
|
||||||
|
color: '#ffaa33',
|
||||||
|
dashArray: '',
|
||||||
|
fillOpacity: 0.85,
|
||||||
|
fillColor: layer.feature.properties.Penduduk ? getColor(layer.feature.properties.Penduduk) : '#aaa'
|
||||||
|
});
|
||||||
|
|
||||||
|
layer.bringToFront();
|
||||||
|
|
||||||
|
// Update info panel dengan properti dari feature
|
||||||
|
info.update(layer.feature.properties);
|
||||||
|
currentHighlightedLayer = layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetHighlight(e) {
|
||||||
|
const layer = e.target;
|
||||||
|
geojsonLayer.resetStyle(layer);
|
||||||
|
// Jangan langsung hapus info, tapi update ke default tanpa properti
|
||||||
|
// Agar tidak menghilang saat mouse out, kita update info ke teks default.
|
||||||
|
// (opsi: biarkan info terakhir agar tidak kosong, namun lebih baik tampilkan petunjuk)
|
||||||
|
info.update(null);
|
||||||
|
currentHighlightedLayer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoomToFeature(e) {
|
||||||
|
const layer = e.target;
|
||||||
|
map.fitBounds(layer.getBounds());
|
||||||
|
// setelah zoom, tetap tampilkan info
|
||||||
|
info.update(layer.feature.properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEachFeature(feature, layer) {
|
||||||
|
// pastikan properti yang diperlukan ada (fallback)
|
||||||
|
if (!feature.properties) feature.properties = {};
|
||||||
|
if (!feature.properties.Ket && feature.properties.Plan_Area) {
|
||||||
|
// alternatif jika nama kecamatan tidak pakai "Ket", bisa pakai "Plan_Area"? Tidak. Tapi kita pastikan
|
||||||
|
feature.properties.Ket = feature.properties.Ket || 'Kecamatan';
|
||||||
|
}
|
||||||
|
|
||||||
|
layer.on({
|
||||||
|
mouseover: highlightFeature,
|
||||||
|
mouseout: resetHighlight,
|
||||||
|
click: zoomToFeature
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tambahkan tooltip sederhana (opsional) saat hover tanpa harus selalu pakai info
|
||||||
|
layer.bindTooltip(`${feature.properties.Ket || 'Kecamatan'} : ${(feature.properties.Penduduk || 0).toLocaleString('id-ID')} jiwa`, {
|
||||||
|
sticky: true,
|
||||||
|
className: 'custom-tooltip',
|
||||||
|
offset: [0, -5]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==============================
|
||||||
|
// 5. LOAD DATA GEOJSON (PONTIANAK.JS)
|
||||||
|
// ==============================
|
||||||
|
// Variabel global dari file pontianak.js harus bernama "pontianakData" atau "statesData"?
|
||||||
|
// Pada contoh dari user, file pontianak.js berisi object JSON langsung (tidak dibungkus var?)
|
||||||
|
// Tapi agar aman: kita akan deteksi apakah ada variabel global bernama "pontianakData" atau "statesData"
|
||||||
|
// Berdasarkan instruksi: file pontianak.js memiliki struktur seperti contoh (FeatureCollection) dengan properti Penduduk, Ket.
|
||||||
|
// Agar kode fleksibel, kita akan menggunakan data dari window jika tersedia.
|
||||||
|
// Namun user hanya menyebut "pontianak.js ada di file terpisah dengan index.html" dan isinya diawali dengan { ... }
|
||||||
|
// Biasanya file js berisi: var pontianakGeoJSON = { ... }; atau langsung window.data = ...
|
||||||
|
// Agar kompatibel, kita baca dari global scope: cek apakah ada variable "pontianakData", "statesData", atau "geoJsonPontianak".
|
||||||
|
// Kita juga bisa fallback jika file js langsung mengekspos window.pontianakData.
|
||||||
|
// Karena tidak dipastikan nama variabelnya, kita akan cari object FeatureCollection yang terdaftar.
|
||||||
|
|
||||||
|
let geoJsonData = null;
|
||||||
|
|
||||||
|
// Cek beberapa kemungkinan nama variabel global dari pontianak.js
|
||||||
|
if (typeof pontianakData !== 'undefined') {
|
||||||
|
geoJsonData = pontianakData;
|
||||||
|
} else if (typeof statesData !== 'undefined') {
|
||||||
|
// Hati-hati: statesData mungkin dari contoh US, tapi jika user mengganti isi pontianak.js dengan statesData, kita gunakan.
|
||||||
|
// Namun pastikan propertinya sesuai: cek apakah fiturnya punya "Penduduk"
|
||||||
|
if (statesData && statesData.features && statesData.features[0] && statesData.features[0].properties &&
|
||||||
|
(statesData.features[0].properties.Penduduk !== undefined || statesData.features[0].properties.Ket)) {
|
||||||
|
geoJsonData = statesData;
|
||||||
|
} else {
|
||||||
|
console.warn("Variabel statesData tersedia tapi bukan data Pontianak (tidak memiliki Penduduk).");
|
||||||
|
}
|
||||||
|
} else if (typeof pontianakGeoJSON !== 'undefined') {
|
||||||
|
geoJsonData = pontianakGeoJSON;
|
||||||
|
} else if (typeof dataPontianak !== 'undefined') {
|
||||||
|
geoJsonData = dataPontianak;
|
||||||
|
} else {
|
||||||
|
// Cek apakah ada variabel global lain yang mungkin berisi FeatureCollection dengan properti Penduduk?
|
||||||
|
// Kita loop properti window untuk debugging, tapi hati-hati performa.
|
||||||
|
for (let key in window) {
|
||||||
|
if (window.hasOwnProperty(key) && window[key] && window[key].type === 'FeatureCollection' && window[key].features) {
|
||||||
|
// cek sampel feature apakah mengandung Penduduk
|
||||||
|
const sample = window[key].features[0];
|
||||||
|
if (sample && sample.properties && (sample.properties.Penduduk !== undefined || sample.properties.Ket)) {
|
||||||
|
geoJsonData = window[key];
|
||||||
|
console.log(`Data GeoJSON ditemukan melalui variabel global: ${key}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika masih null, beri pesan error dan tampilkan peta kosong dengan notifikasi
|
||||||
|
if (!geoJsonData) {
|
||||||
|
console.error("Gagal memuat data GeoJSON. Pastikan file pontianak.js memiliki variabel global seperti 'pontianakData', 'statesData', atau langsung mengekspos FeatureCollection.");
|
||||||
|
// Tampilkan overlay peringatan di peta
|
||||||
|
const errorMsg = L.control({ position: 'bottomleft' });
|
||||||
|
errorMsg.onAdd = function() {
|
||||||
|
const div = L.DomUtil.create('div', 'info');
|
||||||
|
div.style.background = '#d9534f';
|
||||||
|
div.style.color = 'white';
|
||||||
|
div.style.fontWeight = 'bold';
|
||||||
|
div.style.padding = '8px 12px';
|
||||||
|
div.style.borderRadius = '6px';
|
||||||
|
div.innerHTML = '⚠️ Data GeoJSON (pontianak.js) tidak ditemukan. Pastikan file tersedia dan berisi variabel global dengan format FeatureCollection.';
|
||||||
|
return div;
|
||||||
|
};
|
||||||
|
errorMsg.addTo(map);
|
||||||
|
|
||||||
|
// Tambahkan layer kosong agar peta tetap interaktif
|
||||||
|
var geojsonLayer = L.geoJson(null).addTo(map);
|
||||||
|
window.geojsonLayer = geojsonLayer;
|
||||||
|
} else {
|
||||||
|
// Validasi dan tambahkan ke peta
|
||||||
|
try {
|
||||||
|
// Pastikan koordinat polygon sesuai (EPSG:4326)
|
||||||
|
const geojsonLayer = L.geoJson(geoJsonData, {
|
||||||
|
style: style,
|
||||||
|
onEachFeature: onEachFeature
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
// Simpan layer ke variabel global untuk reset style
|
||||||
|
window.geojsonLayer = geojsonLayer;
|
||||||
|
|
||||||
|
// Optional: sesuaikan zoom agar pas dengan bounding box seluruh kecamatan Pontianak
|
||||||
|
if (geojsonLayer.getBounds().isValid()) {
|
||||||
|
map.fitBounds(geojsonLayer.getBounds());
|
||||||
|
// sedikit zoom out agar tidak terlalu ketat (opsi)
|
||||||
|
map.setZoom(map.getZoom() - 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tambahkan atribusi sumber data (sesuai kebutuhan)
|
||||||
|
map.attributionControl.addAttribution('Data Kependudukan & GeoJSON: Pemerintah Kota Pontianak (simulasi)');
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error saat memproses GeoJSON:", err);
|
||||||
|
const errorControl = L.control({ position: 'bottomleft' });
|
||||||
|
errorControl.onAdd = function() {
|
||||||
|
const div = L.DomUtil.create('div', 'info');
|
||||||
|
div.style.background = '#f0ad4e';
|
||||||
|
div.innerHTML = '⚠️ Format GeoJSON tidak valid. Periksa struktur pontianak.js.';
|
||||||
|
return div;
|
||||||
|
};
|
||||||
|
errorControl.addTo(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==============================
|
||||||
|
// 6. LEGEND (Berdasarkan Jumlah Penduduk)
|
||||||
|
// ==============================
|
||||||
|
const legend = L.control({ position: 'bottomright' });
|
||||||
|
|
||||||
|
legend.onAdd = function () {
|
||||||
|
const div = L.DomUtil.create('div', 'info legend');
|
||||||
|
// Rentang populasi sesuai data Pontianak (disesuaikan)
|
||||||
|
const populationRanges = [
|
||||||
|
{ min: 0, max: 10000, label: '< 10.000', color: '#fff7fb' },
|
||||||
|
{ min: 10000, max: 30000, label: '10.000 - 30.000', color: '#fde0ef' },
|
||||||
|
{ min: 30000, max: 50000, label: '30.000 - 50.000', color: '#f1b6da' },
|
||||||
|
{ min: 50000, max: 70000, label: '50.000 - 70.000', color: '#de77ae' },
|
||||||
|
{ min: 70000, max: 100000, label: '70.000 - 100.000', color: '#c51b8a' },
|
||||||
|
{ min: 100000, max: 150000, label: '100.000 - 150.000', color: '#810f7c' },
|
||||||
|
{ min: 150000, max: Infinity, label: '> 150.000', color: '#4d004b' }
|
||||||
|
];
|
||||||
|
|
||||||
|
div.innerHTML = '<strong>👥 JUMLAH PENDUDUK</strong><br>';
|
||||||
|
|
||||||
|
for (let range of populationRanges) {
|
||||||
|
const color = range.color;
|
||||||
|
const label = range.label;
|
||||||
|
div.innerHTML +=
|
||||||
|
'<i style="background:' + color + '; width: 24px; height: 18px; display: inline-block; margin-right: 8px; border: 0.5px solid #777;"></i> ' +
|
||||||
|
label + '<br>';
|
||||||
|
}
|
||||||
|
div.innerHTML += '<br><small style="font-size:10px;">Hover/klik polygon untuk detail</small>';
|
||||||
|
return div;
|
||||||
|
};
|
||||||
|
|
||||||
|
legend.addTo(map);
|
||||||
|
|
||||||
|
// Tambahkan kontrol skala (optional)
|
||||||
|
L.control.scale({ metric: true, imperial: false, position: 'bottomleft' }).addTo(map);
|
||||||
|
|
||||||
|
// ==============================
|
||||||
|
// 7. ADDITIONAL HANDLE: Jika data tidak punya properti 'Ket', coba gunakan 'Plan_Area' atau 'Id'
|
||||||
|
// ==============================
|
||||||
|
// Fungsi tambahan: pastikan info bisa membaca properti apapun yang relevan
|
||||||
|
// Override sedikit agar saat hover info properti bekerja
|
||||||
|
// sudah dilakukan di onEachFeature dan info.update, tapi kita patch info.update agar lebih fleksibel
|
||||||
|
const originalUpdate = info.update;
|
||||||
|
info.update = function(props) {
|
||||||
|
if (props) {
|
||||||
|
let name = props.Ket || props.NAMA_KEC || props.name || props.kecamatan || 'Kecamatan Pontianak';
|
||||||
|
let population = props.Penduduk !== undefined ? props.Penduduk : (props.JUMLAH_PENDUDUK || props.populasi || 0);
|
||||||
|
if (typeof population === 'string') population = parseInt(population, 10);
|
||||||
|
if (isNaN(population)) population = 0;
|
||||||
|
const formattedPop = population.toLocaleString('id-ID');
|
||||||
|
this._div.innerHTML = `<h4>📊 Data Kependudukan</h4><b>🏘️ ${name}</b><br/>👥 Jumlah Penduduk: <strong>${formattedPop}</strong> jiwa`;
|
||||||
|
} else {
|
||||||
|
this._div.innerHTML = `<h4>📊 Kependudukan Kota Pontianak</h4>Arahkan kursor ke wilayah kecamatan`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Jika data belum dimuat karena tidak ada global, kita bisa coba fetch manual? Tidak diperlukan karena menggunakan script tag.
|
||||||
|
// Tambahkan log sukses jika geojsonLayer berhasil.
|
||||||
|
setTimeout(() => {
|
||||||
|
if (window.geojsonLayer && window.geojsonLayer.getLayers().length > 0) {
|
||||||
|
console.log(`✅ Choropleth Pontianak siap. Jumlah kecamatan yang ditampilkan: ${window.geojsonLayer.getLayers().length}`);
|
||||||
|
} else if (!geoJsonData) {
|
||||||
|
console.warn("⚠️ Periksa kembali file pontianak.js. Pastikan file memiliki variabel global seperti: var pontianakData = { type: 'FeatureCollection', features: [...] }");
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
File diff suppressed because it is too large
Load Diff
Submodule
+1
Submodule 03PovertyMapping added at 7f555a2494
+494
@@ -0,0 +1,494 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Portfolio WebGIS – Cindy Dyra Islamay</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
|
||||||
|
:root{
|
||||||
|
--mint:#2ECC8E;--mint-soft:#E6F9F2;--mint-mid:#A8EDD0;
|
||||||
|
--sky:#4FAAED;--sky-soft:#EAF4FD;
|
||||||
|
--coral:#FF7E6B;--coral-soft:#FFF0EE;
|
||||||
|
--amber:#F5A623;--amber-soft:#FEF5E7;
|
||||||
|
--white:#FFFFFF;--surface:#F7FAFA;--border:#E4EEF0;
|
||||||
|
--text-primary:#1A2B38;--text-secondary:#4A6274;--text-muted:#8AACBA;
|
||||||
|
--r-sm:10px;--r-md:16px;--r-lg:24px;--r-xl:32px
|
||||||
|
}
|
||||||
|
html{scroll-behavior:smooth}
|
||||||
|
body{font-family:'Plus Jakarta Sans',sans-serif;background:var(--white);color:var(--text-primary);overflow-x:hidden;line-height:1.6}
|
||||||
|
|
||||||
|
/* ── NAV ── */
|
||||||
|
nav{position:sticky;top:0;z-index:100;background:rgba(255,255,255,0.88);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);border-bottom:1px solid var(--border);padding:0 clamp(20px,5vw,80px)}
|
||||||
|
.nav-inner{max-width:1120px;margin:0 auto;display:flex;align-items:center;justify-content:space-between;height:60px}
|
||||||
|
.nav-brand{font-size:15px;font-weight:700;color:var(--text-primary);text-decoration:none;display:flex;align-items:center;gap:8px}
|
||||||
|
.nav-dot{width:8px;height:8px;border-radius:50%;background:var(--mint)}
|
||||||
|
.nav-badge{font-size:12px;font-weight:600;color:var(--mint);background:var(--mint-soft);padding:4px 12px;border-radius:100px;border:1px solid var(--mint-mid)}
|
||||||
|
|
||||||
|
/* ── HERO ── */
|
||||||
|
.hero{background:var(--white);padding:80px clamp(20px,5vw,80px) 0;position:relative;overflow:hidden}
|
||||||
|
.hero::after{content:'';position:absolute;bottom:0;left:0;right:0;height:120px;background:linear-gradient(to bottom,transparent,var(--surface))}
|
||||||
|
.hero-bg-circle{position:absolute;border-radius:50%;pointer-events:none}
|
||||||
|
.hero-bg-circle.c1{width:480px;height:480px;background:radial-gradient(circle,#D4F5E8 0%,transparent 70%);top:-80px;right:-60px}
|
||||||
|
.hero-bg-circle.c2{width:320px;height:320px;background:radial-gradient(circle,#DFF0FC 0%,transparent 70%);bottom:40px;left:-80px}
|
||||||
|
.hero-inner{max-width:1120px;margin:0 auto;display:grid;grid-template-columns:1fr 1fr;gap:60px;align-items:center;position:relative;z-index:1}
|
||||||
|
.hero-text{}
|
||||||
|
.hero-pill{display:inline-flex;align-items:center;gap:8px;background:var(--mint-soft);color:#1A7A52;font-size:12px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;padding:6px 14px;border-radius:100px;border:1px solid var(--mint-mid);margin-bottom:24px}
|
||||||
|
.hero-pill-dot{width:6px;height:6px;border-radius:50%;background:var(--mint);animation:blink 2s ease-in-out infinite}
|
||||||
|
@keyframes blink{0%,100%{opacity:1}50%{opacity:.3}}
|
||||||
|
.hero-title{font-size:clamp(2rem,4.5vw,3.2rem);font-weight:800;line-height:1.1;color:var(--text-primary);margin-bottom:20px}
|
||||||
|
.hero-title em{font-style:normal;color:var(--mint)}
|
||||||
|
.hero-subtitle{font-size:1.05rem;color:var(--text-secondary);line-height:1.75;margin-bottom:36px;max-width:460px}
|
||||||
|
.hero-identity{display:flex;align-items:center;gap:14px}
|
||||||
|
.hero-avatar{width:48px;height:48px;border-radius:50%;background:linear-gradient(135deg,var(--mint),var(--sky));display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:800;color:#fff;flex-shrink:0}
|
||||||
|
.hero-name{font-size:.95rem;font-weight:700;color:var(--text-primary)}
|
||||||
|
.hero-nim{font-size:.82rem;color:var(--text-muted);margin-top:2px}
|
||||||
|
.hero-visual{display:flex;justify-content:center;align-items:center;padding-bottom:40px}
|
||||||
|
.hero-globe-wrap{position:relative;width:360px;height:360px}
|
||||||
|
.globe-float{animation:float 6s ease-in-out infinite}
|
||||||
|
@keyframes float{0%,100%{transform:translateY(0)}50%{transform:translateY(-16px)}}
|
||||||
|
.globe-ring{position:absolute;border-radius:50%;border:1.5px dashed}
|
||||||
|
.globe-ring.r1{width:420px;height:420px;top:50%;left:50%;transform:translate(-50%,-50%);border-color:rgba(46,204,142,.15);animation:spin 24s linear infinite}
|
||||||
|
.globe-ring.r2{width:500px;height:500px;top:50%;left:50%;transform:translate(-50%,-50%);border-color:rgba(79,170,237,.1);animation:spin 36s linear infinite reverse}
|
||||||
|
@keyframes spin{from{transform:translate(-50%,-50%) rotate(0deg)}to{transform:translate(-50%,-50%) rotate(360deg)}}
|
||||||
|
|
||||||
|
/* ── SECTION BRIDGE ── */
|
||||||
|
.section-bridge{background:var(--surface);padding:48px clamp(20px,5vw,80px)}
|
||||||
|
.bridge-inner{max-width:1120px;margin:0 auto;display:flex;gap:24px;flex-wrap:wrap}
|
||||||
|
.bridge-card{flex:1;min-width:200px;background:var(--white);border-radius:var(--r-md);border:1px solid var(--border);padding:24px;display:flex;align-items:flex-start;gap:16px;transition:transform .25s,box-shadow .25s}
|
||||||
|
.bridge-card:hover{transform:translateY(-4px);box-shadow:0 12px 32px rgba(0,80,60,.07)}
|
||||||
|
.bridge-icon{width:44px;height:44px;border-radius:12px;display:flex;align-items:center;justify-content:center;flex-shrink:0}
|
||||||
|
.bridge-icon svg{width:22px;height:22px}
|
||||||
|
.bridge-label{font-size:.8rem;color:var(--text-muted);font-weight:600;letter-spacing:.04em;text-transform:uppercase;margin-bottom:4px}
|
||||||
|
.bridge-value{font-size:1.1rem;font-weight:700;color:var(--text-primary)}
|
||||||
|
|
||||||
|
/* ── PROJECTS SECTION ── */
|
||||||
|
.projects{background:var(--white);padding:80px clamp(20px,5vw,80px)}
|
||||||
|
.section-head{text-align:center;margin-bottom:60px}
|
||||||
|
.section-eyebrow{font-size:12px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--mint);margin-bottom:12px}
|
||||||
|
.section-title{font-size:clamp(1.6rem,3vw,2.2rem);font-weight:800;color:var(--text-primary);line-height:1.2}
|
||||||
|
.section-desc{font-size:.98rem;color:var(--text-secondary);margin-top:12px;max-width:480px;margin-left:auto;margin-right:auto;line-height:1.7}
|
||||||
|
|
||||||
|
/* ── CARDS ── */
|
||||||
|
.cards{max-width:1120px;margin:0 auto;display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:28px}
|
||||||
|
.card{background:var(--white);border:1px solid var(--border);border-radius:var(--r-xl);overflow:hidden;display:flex;flex-direction:column;transition:transform .3s cubic-bezier(.25,.8,.25,1),box-shadow .3s;opacity:0;transform:translateY(32px)}
|
||||||
|
.card.in{opacity:1;transform:translateY(0);transition:opacity .55s ease,transform .55s ease,box-shadow .3s,border-color .3s}
|
||||||
|
.card:hover{transform:translateY(-8px)!important;box-shadow:0 24px 56px rgba(0,0,0,.1)}
|
||||||
|
.card-thumb{height:180px;position:relative;overflow:hidden;flex-shrink:0;display:flex;align-items:center;justify-content:center}
|
||||||
|
.card-thumb-dots{position:absolute;inset:0;background-image:radial-gradient(circle,rgba(255,255,255,.5) 1px,transparent 1px);background-size:20px 20px}
|
||||||
|
.card-number{position:absolute;top:16px;left:16px;width:32px;height:32px;border-radius:10px;background:rgba(255,255,255,.9);display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:800;color:var(--text-primary)}
|
||||||
|
.card-icon-wrap{width:80px;height:80px;border-radius:24px;background:rgba(255,255,255,.9);display:flex;align-items:center;justify-content:center;position:relative;z-index:1;transition:transform .3s}
|
||||||
|
.card:hover .card-icon-wrap{transform:scale(1.08) rotate(-3deg)}
|
||||||
|
.card-icon-wrap svg{width:40px;height:40px}
|
||||||
|
.card-body{padding:28px;flex:1;display:flex;flex-direction:column}
|
||||||
|
.card-tag{display:inline-block;font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;padding:4px 11px;border-radius:100px;margin-bottom:14px}
|
||||||
|
.card-title{font-size:1.15rem;font-weight:800;color:var(--text-primary);line-height:1.3;margin-bottom:10px}
|
||||||
|
.card-desc{font-size:.9rem;color:var(--text-secondary);line-height:1.75;flex:1;margin-bottom:24px}
|
||||||
|
.card-btn{display:inline-flex;align-items:center;gap:8px;font-size:.88rem;font-weight:700;text-decoration:none;padding:12px 22px;border-radius:100px;transition:gap .2s,opacity .2s;align-self:flex-start}
|
||||||
|
.card-btn:hover{gap:14px;opacity:.88}
|
||||||
|
.card-btn svg{width:15px;height:15px;transition:transform .2s}
|
||||||
|
.card-btn:hover svg{transform:translateX(3px)}
|
||||||
|
|
||||||
|
/* card color themes */
|
||||||
|
.card.theme-green .card-thumb{background:linear-gradient(135deg,#D0F5E6,#A8EDD6)}
|
||||||
|
.card.theme-green .card-tag{background:#D4F5E8;color:#0E6640}
|
||||||
|
.card.theme-green .card-btn{background:#2ECC8E;color:#fff}
|
||||||
|
.card.theme-blue .card-thumb{background:linear-gradient(135deg,#D6EEFF,#B8DAFF)}
|
||||||
|
.card.theme-blue .card-tag{background:#DFF0FC;color:#1155A0}
|
||||||
|
.card.theme-blue .card-btn{background:#4FAAED;color:#fff}
|
||||||
|
.card.theme-coral .card-thumb{background:linear-gradient(135deg,#FFE8E4,#FFCFC8)}
|
||||||
|
.card.theme-coral .card-tag{background:#FFF0EE;color:#B33922}
|
||||||
|
.card.theme-coral .card-btn{background:#FF7E6B;color:#fff}
|
||||||
|
|
||||||
|
/* ── FEATURES STRIP ── */
|
||||||
|
.features{background:var(--surface);padding:72px clamp(20px,5vw,80px)}
|
||||||
|
.features-inner{max-width:1120px;margin:0 auto;display:grid;grid-template-columns:1fr 1fr;gap:60px;align-items:center}
|
||||||
|
.features-text .section-eyebrow{text-align:left}
|
||||||
|
.features-text .section-title{text-align:left}
|
||||||
|
.features-list{margin-top:32px;display:flex;flex-direction:column;gap:20px}
|
||||||
|
.feature-item{display:flex;align-items:flex-start;gap:16px}
|
||||||
|
.feature-check{width:36px;height:36px;border-radius:10px;background:var(--mint-soft);border:1px solid var(--mint-mid);display:flex;align-items:center;justify-content:center;flex-shrink:0}
|
||||||
|
.feature-check svg{width:18px;height:18px;color:var(--mint)}
|
||||||
|
.feature-title{font-size:.95rem;font-weight:700;color:var(--text-primary);margin-bottom:3px}
|
||||||
|
.feature-desc{font-size:.86rem;color:var(--text-secondary);line-height:1.65}
|
||||||
|
.features-visual{background:var(--white);border-radius:var(--r-xl);border:1px solid var(--border);padding:32px;display:flex;flex-direction:column;gap:16px}
|
||||||
|
.feat-bar-label{display:flex;justify-content:space-between;align-items:center;font-size:.82rem}
|
||||||
|
.feat-bar-name{color:var(--text-secondary);font-weight:600}
|
||||||
|
.feat-bar-val{color:var(--text-primary);font-weight:700}
|
||||||
|
.feat-bar-track{height:8px;background:#EDF2F5;border-radius:100px;overflow:hidden}
|
||||||
|
.feat-bar-fill{height:100%;border-radius:100px;transform:scaleX(0);transform-origin:left;transition:transform 1.2s cubic-bezier(.4,0,.2,1)}
|
||||||
|
.feat-bar-fill.go{transform:scaleX(1)}
|
||||||
|
.fv-projects{display:flex;flex-direction:column;gap:0}
|
||||||
|
.fv-row{padding:18px 0}
|
||||||
|
.fv-divider{height:1px;background:var(--border)}
|
||||||
|
.fv-row-header{display:flex;align-items:center;gap:10px;margin-bottom:12px}
|
||||||
|
.fv-dot{width:9px;height:9px;border-radius:50%;flex-shrink:0}
|
||||||
|
.fv-row-name{font-size:.9rem;font-weight:700;color:var(--text-primary)}
|
||||||
|
.fv-tags{display:flex;flex-wrap:wrap;gap:8px}
|
||||||
|
.fv-tag{font-size:.78rem;font-weight:600;padding:4px 12px;border-radius:100px;letter-spacing:.02em}
|
||||||
|
|
||||||
|
/* ── FOOTER ── */
|
||||||
|
footer{background:#0f6543;padding:48px clamp(20px,5vw,80px);text-align:center}
|
||||||
|
.footer-logo{font-size:1.1rem;font-weight:800;color:var(--white);margin-bottom:8px}
|
||||||
|
.footer-logo span{color:var(--white)}
|
||||||
|
.footer-sub{font-size:.85rem;color:#ffffff;margin-top:4px}
|
||||||
|
.footer-line{width:40px;height:2px;background:var(--white);border-radius:2px;margin:20px auto}
|
||||||
|
|
||||||
|
/* ── RESPONSIVE ── */
|
||||||
|
@media(max-width:900px){
|
||||||
|
.hero-inner{grid-template-columns:1fr;text-align:center;gap:40px}
|
||||||
|
.hero-identity{justify-content:center}
|
||||||
|
.hero-subtitle{margin-left:auto;margin-right:auto}
|
||||||
|
.hero-visual{padding-bottom:0}
|
||||||
|
.hero-globe-wrap{width:260px;height:260px}
|
||||||
|
.features-inner{grid-template-columns:1fr}
|
||||||
|
}
|
||||||
|
@media(max-width:600px){
|
||||||
|
.hero{padding-top:52px}
|
||||||
|
.projects{padding:52px 16px}
|
||||||
|
.features{padding:52px 16px}
|
||||||
|
.section-bridge{padding:32px 16px}
|
||||||
|
.cards{grid-template-columns:1fr;gap:20px}
|
||||||
|
.bridge-card{min-width:150px}
|
||||||
|
footer{padding:40px 16px}
|
||||||
|
}
|
||||||
|
@media(prefers-reduced-motion:reduce){
|
||||||
|
.globe-float,.globe-ring,.hero-bg-circle,.feat-bar-fill,.card,.blink{animation:none;transition:none}
|
||||||
|
.card{opacity:1;transform:none}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- NAV -->
|
||||||
|
<nav>
|
||||||
|
<div class="nav-inner">
|
||||||
|
<a class="nav-brand" href="#">
|
||||||
|
<span class="nav-dot"></span>
|
||||||
|
WebGIS Portfolio
|
||||||
|
</a>
|
||||||
|
<span class="nav-badge">SIG · 2026</span>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- HERO -->
|
||||||
|
<section class="hero">
|
||||||
|
<div class="hero-bg-circle c1" aria-hidden="true"></div>
|
||||||
|
<div class="hero-bg-circle c2" aria-hidden="true"></div>
|
||||||
|
<div class="hero-inner">
|
||||||
|
<div class="hero-text">
|
||||||
|
<div class="hero-pill">
|
||||||
|
<span class="hero-pill-dot"></span>
|
||||||
|
Sistem Informasi Geografis
|
||||||
|
</div>
|
||||||
|
<h1 class="hero-title">Portfolio Proyek<br><em>WebGIS</em></h1>
|
||||||
|
<p class="hero-subtitle">
|
||||||
|
Kumpulan aplikasi pemetaan berbasis web yang dikembangkan untuk
|
||||||
|
memvisualisasikan dan menganalisis data geografis menggunakan
|
||||||
|
teknologi WebGIS modern.
|
||||||
|
</p>
|
||||||
|
<div class="hero-identity">
|
||||||
|
<div class="hero-avatar">CD</div>
|
||||||
|
<div>
|
||||||
|
<div class="hero-name">Cindy Dyra Islamay</div>
|
||||||
|
<div class="hero-nim">NIM: D1041231045</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="hero-visual" aria-hidden="true">
|
||||||
|
<div class="hero-globe-wrap">
|
||||||
|
<div class="globe-ring r1"></div>
|
||||||
|
<div class="globe-ring r2"></div>
|
||||||
|
<svg class="globe-float" viewBox="0 0 360 360" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<!-- soft background disc -->
|
||||||
|
<circle cx="180" cy="180" r="160" fill="#EDF8F3"/>
|
||||||
|
<circle cx="180" cy="180" r="140" fill="#F5FBF8"/>
|
||||||
|
<!-- grid lines -->
|
||||||
|
<ellipse cx="180" cy="180" rx="140" ry="56" stroke="#B0E8CF" stroke-width="1.5" fill="none"/>
|
||||||
|
<ellipse cx="180" cy="180" rx="140" ry="100" stroke="#B0E8CF" stroke-width="1" fill="none"/>
|
||||||
|
<line x1="180" y1="40" x2="180" y2="320" stroke="#B0E8CF" stroke-width="1" stroke-dasharray="6,5"/>
|
||||||
|
<line x1="40" y1="180" x2="320" y2="180" stroke="#B0E8CF" stroke-width="1"/>
|
||||||
|
<line x1="68" y1="100" x2="292" y2="100" stroke="#C8EEE0" stroke-width="1" stroke-dasharray="4,4"/>
|
||||||
|
<line x1="68" y1="260" x2="292" y2="260" stroke="#C8EEE0" stroke-width="1" stroke-dasharray="4,4"/>
|
||||||
|
<!-- landmass blobs -->
|
||||||
|
<path d="M120,140 Q140,120 170,128 Q195,120 205,135 Q220,130 228,148 Q240,155 232,170 Q240,185 225,192 Q215,210 200,202 Q185,215 168,208 Q148,212 138,198 Q118,195 112,180 Q100,165 120,140Z" fill="#A8DDBB" opacity=".9"/>
|
||||||
|
<path d="M200,150 Q215,144 226,152 Q238,148 242,162 Q252,170 244,180 Q248,192 236,196 Q226,206 216,198 Q204,202 198,190 Q188,182 196,170 Q188,158 200,150Z" fill="#90D5AC" opacity=".8"/>
|
||||||
|
<path d="M130,200 Q144,190 156,196 Q166,188 175,198 Q185,196 188,208 Q194,218 184,224 Q178,234 166,228 Q154,234 144,224 Q132,222 128,212 Q122,204 130,200Z" fill="#A8DDBB" opacity=".75"/>
|
||||||
|
<path d="M105,156 Q112,148 122,154 Q130,148 134,160 Q140,168 132,174 Q134,184 124,186 Q114,190 106,182 Q98,176 100,166 Q96,158 105,156Z" fill="#90D5AC" opacity=".7"/>
|
||||||
|
<!-- pin markers -->
|
||||||
|
<g filter="url(#pinShadow)">
|
||||||
|
<circle cx="180" cy="165" r="10" fill="#2ECC8E"/>
|
||||||
|
<circle cx="180" cy="165" r="5" fill="white"/>
|
||||||
|
<circle cx="180" cy="165" r="18" fill="#2ECC8E" opacity=".2"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle cx="145" cy="175" r="7" fill="#4FAAED"/>
|
||||||
|
<circle cx="145" cy="175" r="3.5" fill="white"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle cx="218" cy="170" r="7" fill="#FF7E6B"/>
|
||||||
|
<circle cx="218" cy="170" r="3.5" fill="white"/>
|
||||||
|
</g>
|
||||||
|
<!-- outer border -->
|
||||||
|
<circle cx="180" cy="180" r="140" stroke="#7DD8B0" stroke-width="2" fill="none"/>
|
||||||
|
<defs>
|
||||||
|
<filter id="pinShadow" x="-50%" y="-50%" width="200%" height="200%">
|
||||||
|
<feDropShadow dx="0" dy="4" stdDeviation="4" flood-color="#2ECC8E" flood-opacity=".35"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- BRIDGE STRIP -->
|
||||||
|
<div class="section-bridge">
|
||||||
|
<div class="bridge-inner">
|
||||||
|
<div class="bridge-card">
|
||||||
|
<div class="bridge-icon" style="background:#E6F9F2">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="#2ECC8E" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="bridge-label">Total Proyek</div>
|
||||||
|
<div class="bridge-value">3 Aplikasi WebGIS</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bridge-card">
|
||||||
|
<div class="bridge-icon" style="background:#EAF4FD">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="#4FAAED" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="bridge-label">Wilayah Studi</div>
|
||||||
|
<div class="bridge-value">Kota Pontianak</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bridge-card">
|
||||||
|
<div class="bridge-icon" style="background:#FEF5E7">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="#F5A623" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="bridge-label">Teknologi</div>
|
||||||
|
<div class="bridge-value">Leaflet.js · GeoJSON</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bridge-card">
|
||||||
|
<div class="bridge-icon" style="background:#FFF0EE">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="#FF7E6B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="bridge-label">Mata Kuliah</div>
|
||||||
|
<div class="bridge-value">Sistem Informasi Geografis C</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PROJECTS -->
|
||||||
|
<section class="projects">
|
||||||
|
<div class="section-head">
|
||||||
|
<div class="section-eyebrow">Koleksi Karya</div>
|
||||||
|
<h2 class="section-title">Proyek yang Dikembangkan</h2>
|
||||||
|
<p class="section-desc">Setiap aplikasi dirancang untuk menjawab kebutuhan analisis spasial yang nyata di wilayah Kota Pontianak.</p>
|
||||||
|
</div>
|
||||||
|
<div class="cards">
|
||||||
|
|
||||||
|
<!-- Card 1 -->
|
||||||
|
<article class="card theme-green" data-delay="0">
|
||||||
|
<div class="card-thumb">
|
||||||
|
<div class="card-thumb-dots"></div>
|
||||||
|
<span class="card-number">01</span>
|
||||||
|
<div class="card-icon-wrap">
|
||||||
|
<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="24" cy="24" r="22" fill="#D0F5E6"/>
|
||||||
|
<path d="M24 12C18.48 12 14 16.48 14 22c0 7.5 10 18 10 18s10-10.5 10-18c0-5.52-4.48-10-10-10zm0 13a3 3 0 1 1 0-6 3 3 0 0 1 0 6z" fill="#2ECC8E"/>
|
||||||
|
<circle cx="33" cy="14" r="4" fill="#A8EDD0" stroke="#2ECC8E" stroke-width="1.5"/>
|
||||||
|
<circle cx="15" cy="32" r="3" fill="#A8EDD0" stroke="#2ECC8E" stroke-width="1.5"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<span class="card-tag">Pemetaan Fasilitas</span>
|
||||||
|
<h3 class="card-title">Mapping SPBU</h3>
|
||||||
|
<p class="card-desc">Aplikasi WebGIS untuk pemetaan lokasi stasiun pengisian bahan bakar umum (SPBU) dengan fitur visualisasi interaktif dan manajemen data geografis.</p>
|
||||||
|
<a href="http://localhost/webgis/01SPBU/index.html" class="card-btn" target="_blank" rel="noopener">
|
||||||
|
Buka Proyek
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<!-- Card 2 -->
|
||||||
|
<article class="card theme-blue" data-delay="120">
|
||||||
|
<div class="card-thumb">
|
||||||
|
<div class="card-thumb-dots"></div>
|
||||||
|
<span class="card-number">02</span>
|
||||||
|
<div class="card-icon-wrap">
|
||||||
|
<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="24" cy="24" r="22" fill="#D6EEFF"/>
|
||||||
|
<rect x="10" y="14" width="12" height="10" rx="2" fill="#B8DAFF"/>
|
||||||
|
<rect x="26" y="10" width="12" height="14" rx="2" fill="#4FAAED"/>
|
||||||
|
<rect x="10" y="28" width="12" height="10" rx="2" fill="#4FAAED" opacity=".6"/>
|
||||||
|
<rect x="26" y="28" width="12" height="10" rx="2" fill="#B8DAFF"/>
|
||||||
|
<line x1="10" y1="38" x2="38" y2="38" stroke="#4FAAED" stroke-width="1.5"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<span class="card-tag">Demografi</span>
|
||||||
|
<h3 class="card-title">Choropleth Map – Kepadatan Penduduk Kota Pontianak</h3>
|
||||||
|
<p class="card-desc">Visualisasi peta choropleth yang menampilkan distribusi kepadatan penduduk di Kota Pontianak menggunakan gradasi warna untuk analisis demografis.</p>
|
||||||
|
<a href="http://localhost/webgis/02Cloropeth/index.html" class="card-btn" target="_blank" rel="noopener">
|
||||||
|
Buka Proyek
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<!-- Card 3 -->
|
||||||
|
<article class="card theme-coral" data-delay="240">
|
||||||
|
<div class="card-thumb">
|
||||||
|
<div class="card-thumb-dots"></div>
|
||||||
|
<span class="card-number">03</span>
|
||||||
|
<div class="card-icon-wrap">
|
||||||
|
<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="24" cy="24" r="22" fill="#FFE8E4"/>
|
||||||
|
<circle cx="16" cy="20" r="5" fill="#FF7E6B" opacity=".7"/>
|
||||||
|
<circle cx="30" cy="16" r="4" fill="#FFCFC8" stroke="#FF7E6B" stroke-width="1.5"/>
|
||||||
|
<circle cx="26" cy="30" r="4" fill="#FFCFC8" stroke="#FF7E6B" stroke-width="1.5"/>
|
||||||
|
<line x1="16" y1="20" x2="30" y2="16" stroke="#FF7E6B" stroke-width="1.5" stroke-dasharray="4,3"/>
|
||||||
|
<line x1="30" y1="16" x2="26" y2="30" stroke="#FF7E6B" stroke-width="1.5" stroke-dasharray="4,3"/>
|
||||||
|
<line x1="16" y1="20" x2="26" y2="30" stroke="#FF7E6B" stroke-width="1.5" stroke-dasharray="4,3"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<span class="card-tag">Analisis Sosial</span>
|
||||||
|
<h3 class="card-title">WebGIS Pemetaan Kemiskinan</h3>
|
||||||
|
<p class="card-desc">Aplikasi WebGIS yang memetakan distribusi spasial rumah tangga berpenghasilan rendah dan menghubungkannya dengan tempat ibadah sebagai pusat distribusi bantuan.</p>
|
||||||
|
<a href="http://localhost/webgis/03PovertyMapping/login.html" class="card-btn" target="_blank" rel="noopener">
|
||||||
|
Buka Proyek
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- FEATURES STRIP -->
|
||||||
|
<section class="features">
|
||||||
|
<div class="features-inner">
|
||||||
|
<div class="features-text">
|
||||||
|
<div class="section-eyebrow">Kemampuan Teknis</div>
|
||||||
|
<h2 class="section-title">Dibangun dengan Teknologi WebGIS Modern</h2>
|
||||||
|
<div class="features-list">
|
||||||
|
<div class="feature-item">
|
||||||
|
<div class="feature-check">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="feature-title">Peta Interaktif Berbasis Web</div>
|
||||||
|
<div class="feature-desc">Visualisasi data geografis yang dapat dijelajahi langsung di browser tanpa instalasi tambahan.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="feature-item">
|
||||||
|
<div class="feature-check">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="feature-title">Analisis Data Spasial</div>
|
||||||
|
<div class="feature-desc">Pengolahan dan interpretasi data geografis untuk menghasilkan wawasan berbasis lokasi.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="feature-item">
|
||||||
|
<div class="feature-check">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="feature-title">Desain Responsif & Aksesibel</div>
|
||||||
|
<div class="feature-desc">Antarmuka yang menyesuaikan tampilan di berbagai ukuran layar dari desktop hingga ponsel.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="features-visual">
|
||||||
|
<p style="font-size:.82rem;font-weight:700;color:var(--text-muted);letter-spacing:.06em;text-transform:uppercase;margin-bottom:20px">Stack Teknologi per Proyek</p>
|
||||||
|
<div class="fv-projects">
|
||||||
|
|
||||||
|
<div class="fv-row">
|
||||||
|
<div class="fv-row-header">
|
||||||
|
<span class="fv-dot" style="background:#2ECC8E"></span>
|
||||||
|
<span class="fv-row-name">Mapping SPBU</span>
|
||||||
|
</div>
|
||||||
|
<div class="fv-tags">
|
||||||
|
<span class="fv-tag" style="background:#E6F9F2;color:#0E6640">Leaflet.js</span>
|
||||||
|
<span class="fv-tag" style="background:#E6F9F2;color:#0E6640">GeoJSON</span>
|
||||||
|
<span class="fv-tag" style="background:#E6F9F2;color:#0E6640">Layer Control</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="fv-divider"></div>
|
||||||
|
|
||||||
|
<div class="fv-row">
|
||||||
|
<div class="fv-row-header">
|
||||||
|
<span class="fv-dot" style="background:#4FAAED"></span>
|
||||||
|
<span class="fv-row-name">Choropleth Kepadatan</span>
|
||||||
|
</div>
|
||||||
|
<div class="fv-tags">
|
||||||
|
<span class="fv-tag" style="background:#EAF4FD;color:#1155A0">Leaflet.js</span>
|
||||||
|
<span class="fv-tag" style="background:#EAF4FD;color:#1155A0">Choropleth</span>
|
||||||
|
<span class="fv-tag" style="background:#EAF4FD;color:#1155A0">GeoJSON</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="fv-divider"></div>
|
||||||
|
|
||||||
|
<div class="fv-row">
|
||||||
|
<div class="fv-row-header">
|
||||||
|
<span class="fv-dot" style="background:#FF7E6B"></span>
|
||||||
|
<span class="fv-row-name">Pemetaan Kemiskinan</span>
|
||||||
|
</div>
|
||||||
|
<div class="fv-tags">
|
||||||
|
<span class="fv-tag" style="background:#FFF0EE;color:#B33922">Leaflet.js</span>
|
||||||
|
<span class="fv-tag" style="background:#FFF0EE;color:#B33922">Chart.js</span>
|
||||||
|
<span class="fv-tag" style="background:#FFF0EE;color:#B33922">GeoJSON</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- FOOTER -->
|
||||||
|
<footer>
|
||||||
|
<div class="footer-logo">WebGIS<span>.</span></div>
|
||||||
|
<div class="footer-sub">Cindy Dyra Islamay · NIM D1041231045</div>
|
||||||
|
<div class="footer-line"></div>
|
||||||
|
<div style="font-size:.82rem;color:#ffffff">© 2026 Portfolio Proyek Sistem Informasi Geografis</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function(){
|
||||||
|
// card entrance
|
||||||
|
var cards=document.querySelectorAll('.card');
|
||||||
|
var io=new IntersectionObserver(function(entries){
|
||||||
|
entries.forEach(function(e){
|
||||||
|
if(!e.isIntersecting)return;
|
||||||
|
var d=parseInt(e.target.dataset.delay||0);
|
||||||
|
setTimeout(function(){e.target.classList.add('in')},d);
|
||||||
|
io.unobserve(e.target);
|
||||||
|
});
|
||||||
|
},{threshold:0.1});
|
||||||
|
cards.forEach(function(c){io.observe(c)});
|
||||||
|
|
||||||
|
// nav shadow on scroll
|
||||||
|
var nav=document.querySelector('nav');
|
||||||
|
window.addEventListener('scroll',function(){
|
||||||
|
nav.style.boxShadow=window.scrollY>10?'0 2px 16px rgba(0,60,40,.07)':'none';
|
||||||
|
},{passive:true});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user